diff --git a/Clover/app/src/main/java/org/floens/chan/Chan.java b/Clover/app/src/main/java/org/floens/chan/Chan.java index 98384eff..309a07a6 100644 --- a/Clover/app/src/main/java/org/floens/chan/Chan.java +++ b/Clover/app/src/main/java/org/floens/chan/Chan.java @@ -33,11 +33,14 @@ import org.floens.chan.core.di.NetModule; import org.floens.chan.core.di.UserAgentProvider; import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.site.SiteService; +import org.floens.chan.ui.activity.ActivityResultHelper; import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.LocaleUtils; import org.floens.chan.utils.Logger; import org.floens.chan.utils.Time; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import javax.inject.Inject; @@ -45,7 +48,10 @@ import javax.inject.Inject; import de.greenrobot.event.EventBus; @SuppressLint("Registered") // extended by ChanApplication, which is registered in the manifest. -public class Chan extends Application implements UserAgentProvider, Application.ActivityLifecycleCallbacks { +public class Chan extends Application implements + UserAgentProvider, + ActivityResultHelper.ApplicationActivitiesProvider, + Application.ActivityLifecycleCallbacks { private static final String TAG = "ChanApplication"; @SuppressLint("StaticFieldLeak") @@ -63,6 +69,8 @@ public class Chan extends Application implements UserAgentProvider, Application. @Inject BoardManager boardManager; + private List activities = new ArrayList<>(); + private Feather feather; public Chan() { @@ -189,8 +197,14 @@ public class Chan extends Application implements UserAgentProvider, Application. return getString(R.string.app_name) + "/" + version; } + @Override + public List getActivities() { + return activities; + } + @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + activities.add(activity); } @Override @@ -217,5 +231,6 @@ public class Chan extends Application implements UserAgentProvider, Application. @Override public void onActivityDestroyed(Activity activity) { + activities.remove(activity); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java index f2c2a8b2..10f070b0 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java @@ -37,7 +37,7 @@ import org.floens.chan.core.site.http.Reply; import org.floens.chan.core.site.http.ReplyResponse; import org.floens.chan.ui.captcha.AuthenticationLayoutCallback; import org.floens.chan.ui.captcha.AuthenticationLayoutInterface; -import org.floens.chan.ui.helper.ImagePickDelegate; +import org.floens.chan.ui.activity.ImagePickDelegate; import org.floens.chan.utils.Logger; import java.io.File; diff --git a/Clover/app/src/main/java/org/floens/chan/core/presenter/StorageSetupPresenter.java b/Clover/app/src/main/java/org/floens/chan/core/presenter/StorageSetupPresenter.java new file mode 100644 index 00000000..84febe92 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/presenter/StorageSetupPresenter.java @@ -0,0 +1,63 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.core.presenter; + +import android.app.Activity; +import android.content.Intent; + +import org.floens.chan.core.storage.Storage; +import org.floens.chan.ui.activity.ActivityResultHelper; + +import javax.inject.Inject; + +public class StorageSetupPresenter { + private Callback callback; + + private Storage storage; + private ActivityResultHelper results; + + @Inject + public StorageSetupPresenter(Storage storage, ActivityResultHelper results) { + this.storage = storage; + this.results = results; + } + + public void create(Callback callback) { + this.callback = callback; + + updateDescription(); + } + + public void saveLocationClicked() { + Intent openTreeIntent = storage.getOpenTreeIntent(); + results.getResultFromIntent(openTreeIntent, (resultCode, result) -> { + if (resultCode == Activity.RESULT_OK) { + storage.handleOpenTreeIntent(result); + } + }); + } + + private void updateDescription() { + String description = storage.currentStorageName(); + callback.setSaveLocationDescription(description); + } + + public interface Callback { + void setSaveLocationDescription(String description); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java index d145a032..2dd22d4b 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java @@ -30,7 +30,7 @@ import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.storage.Storage; import org.floens.chan.core.storage.StorageFile; import org.floens.chan.ui.activity.StartActivity; -import org.floens.chan.ui.helper.RuntimePermissionsHelper; +import org.floens.chan.ui.activity.RuntimePermissionsHelper; import org.floens.chan.ui.service.SavingNotification; import java.io.File; diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java b/Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java deleted file mode 100644 index cc9b509a..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.floens.chan.core.storage; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -public class BaseStorageImpl implements StorageImpl { - protected Context applicationContext; - - public BaseStorageImpl(Context applicationContext) { - this.applicationContext = applicationContext; - } - - @Override - public boolean supportsExternalStorage() { - return false; - } - - @Override - public Intent getOpenTreeIntent() { - throw new UnsupportedOperationException(); - } - - @Override - public void handleOpenTreeIntent(Uri uri) { - throw new UnsupportedOperationException(); - } - - @Override - public StorageFile obtainStorageFileForName(String name) { - throw new UnsupportedOperationException(); - } - - @Override - public String currentStorageName() { - throw new UnsupportedOperationException(); - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java b/Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java deleted file mode 100644 index 3d4ab143..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.floens.chan.core.storage; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.provider.DocumentsContract; -import android.support.annotation.RequiresApi; - -import org.floens.chan.core.settings.ChanSettings; -import org.floens.chan.utils.IOUtils; -import org.floens.chan.utils.Logger; - -import java.io.FileNotFoundException; - -@RequiresApi(Build.VERSION_CODES.LOLLIPOP) -public class LollipopStorageImpl extends BaseStorageImpl { - private static final String TAG = "LollipopStorageImpl"; - - public LollipopStorageImpl(Context applicationContext) { - super(applicationContext); - } - - @Override - public boolean supportsExternalStorage() { - return true; - } - - @Override - public Intent getOpenTreeIntent() { - return new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - } - - @Override - public void handleOpenTreeIntent(Uri uri) { - String documentId = DocumentsContract.getTreeDocumentId(uri); - Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId); - - ChanSettings.saveLocationTreeUri.set(treeDocumentUri.toString()); - } - - @Override - public StorageFile obtainStorageFileForName(String name) { - String uriString = ChanSettings.saveLocationTreeUri.get(); - if (uriString.isEmpty()) { - return null; - } - - Uri treeUri = Uri.parse(uriString); - - ContentResolver contentResolver = applicationContext.getContentResolver(); - - String documentId = DocumentsContract.getTreeDocumentId(treeUri); - Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId); - - Uri docUri; - try { - docUri = DocumentsContract.createDocument(contentResolver, treeDocumentUri, - "text", name); - } catch (FileNotFoundException e) { - Logger.e(TAG, "obtainStorageFileForName createDocument", e); - return null; - } - - return StorageFile.fromUri(contentResolver, docUri); - } - - @Override - public String currentStorageName() { - String uriString = ChanSettings.saveLocationTreeUri.get(); - if (uriString.isEmpty()) { - return null; - } - - Uri treeUri = Uri.parse(uriString); - return queryTreeName(treeUri); - } - - private String queryTreeName(Uri uri) { - ContentResolver contentResolver = applicationContext.getContentResolver(); - - Cursor c = null; - String name = null; - try { - c = contentResolver.query(uri, new String[]{ - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE - }, null, null, null); - - if (c != null && c.moveToNext()) { - name = c.getString(0); - // mime = c.getString(1); - } - - return name; - } catch (Exception e) { - Logger.e(TAG, "queryTreeName", e); - } finally { - IOUtils.closeQuietly(c); - } - - return null; - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java b/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java index c7fe9c66..2cf7831d 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java @@ -1,9 +1,37 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package org.floens.chan.core.storage; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; import android.os.Build; +import android.provider.DocumentsContract; +import android.support.annotation.RequiresApi; + +import org.floens.chan.core.settings.ChanSettings; +import org.floens.chan.core.settings.StringSetting; +import org.floens.chan.utils.IOUtils; +import org.floens.chan.utils.Logger; + +import java.io.FileNotFoundException; import javax.inject.Inject; import javax.inject.Singleton; @@ -25,34 +53,173 @@ import javax.inject.Singleton; */ @Singleton public class Storage { - private StorageImpl impl; + private static final String TAG = "Storage"; + + /** + * The current mode of the Storage. + */ + public enum Mode { + /** + * File mode is used on devices under Lollipop, or by default when the user hasn't changed + * the save locaton yet. + *

+ * Uses the File api for internal/external storage. + */ + FILE, + + /** + * Used on lollipop and higher. + *

+ * Uses the Android Storage Access Framework for internal, external and removable storage. + */ + STORAGE_ACCESS_FRAMEWORK + } + + private Context applicationContext; + + private final StringSetting saveLocation; + private final StringSetting saveLocationTreeUri; @Inject public Storage(Context applicationContext) { + this.applicationContext = applicationContext; + + saveLocation = ChanSettings.saveLocation; + saveLocationTreeUri = ChanSettings.saveLocationTreeUri; + } + + /** + * The mode of storage changes depending on the api level and if the user migrated their + * legacy install settings. + */ + public Mode mode() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - impl = new BaseStorageImpl(applicationContext); - } else { - impl = new LollipopStorageImpl(applicationContext); + return Mode.FILE; } + + if (!saveLocation.get().isEmpty()) { + return Mode.FILE; + } + + return Mode.STORAGE_ACCESS_FRAMEWORK; } - public boolean supportsExternalStorage() { - return impl.supportsExternalStorage(); + public String currentStorageName() { + switch (mode()) { + case FILE: { + return saveLocation.get(); + } + case STORAGE_ACCESS_FRAMEWORK: { + String uriString = saveLocationTreeUri.get(); + Uri treeUri = Uri.parse(uriString); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return queryTreeName(treeUri); + } + } + } + throw new IllegalStateException(); } + public Mode getModeForNewLocation() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return Mode.FILE; + } else { + return Mode.STORAGE_ACCESS_FRAMEWORK; + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public Intent getOpenTreeIntent() { - return impl.getOpenTreeIntent(); + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | + Intent.FLAG_GRANT_READ_URI_PERMISSION | + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); +// intent.addCategory(Intent.CATEGORY_OPENABLE); + return intent; } - public void handleOpenTreeIntent(Uri uri) { - impl.handleOpenTreeIntent(uri); + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void handleOpenTreeIntent(Intent intent) { + boolean read = (intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0; + boolean write = (intent.getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0; + + if (!read) { + Logger.e(TAG, "No grant read uri permission given"); + return; + } + + if (!write) { + Logger.e(TAG, "No grant write uri permission given"); + return; + } + + Uri uri = intent.getData(); + if (uri == null) { + Logger.e(TAG, "intent.getData() == null"); + return; + } + + String documentId = DocumentsContract.getTreeDocumentId(uri); + Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId); + + ContentResolver contentResolver = applicationContext.getContentResolver(); + + int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + contentResolver.takePersistableUriPermission(uri, flags); + + saveLocationTreeUri.set(treeDocumentUri.toString()); } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public StorageFile obtainStorageFileForName(String name) { - return impl.obtainStorageFileForName(name); + String uriString = saveLocationTreeUri.get(); + if (uriString.isEmpty()) { + return null; + } + + Uri treeUri = Uri.parse(uriString); + + ContentResolver contentResolver = applicationContext.getContentResolver(); + + String documentId = DocumentsContract.getTreeDocumentId(treeUri); + Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId); + + Uri docUri; + try { + docUri = DocumentsContract.createDocument(contentResolver, treeDocumentUri, + "text", name); + } catch (FileNotFoundException e) { + Logger.e(TAG, "obtainStorageFileForName createDocument", e); + return null; + } + + return StorageFile.fromUri(contentResolver, docUri); } - public String currentStorageName() { - return impl.currentStorageName(); + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private String queryTreeName(Uri uri) { + ContentResolver contentResolver = applicationContext.getContentResolver(); + + Cursor c = null; + String name = null; + try { + c = contentResolver.query(uri, new String[]{ + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_MIME_TYPE + }, null, null, null); + + if (c != null && c.moveToNext()) { + name = c.getString(0); + // mime = c.getString(1); + } + + return name; + } catch (Exception e) { + Logger.e(TAG, "queryTreeName", e); + } finally { + IOUtils.closeQuietly(c); + } + + return null; } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java b/Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java deleted file mode 100644 index 8bf3e711..00000000 --- a/Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.floens.chan.core.storage; - -import android.content.Intent; -import android.net.Uri; - -public interface StorageImpl { - boolean supportsExternalStorage(); - - Intent getOpenTreeIntent(); - - void handleOpenTreeIntent(Uri uri); - - StorageFile obtainStorageFileForName(String name); - - String currentStorageName(); -} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/ActivityResultHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/ActivityResultHelper.java new file mode 100644 index 00000000..0e84d991 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/ActivityResultHelper.java @@ -0,0 +1,156 @@ +/* + * Clover - 4chan browser https://github.com/Floens/Clover/ + * Copyright (C) 2014 Floens + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.floens.chan.ui.activity; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.util.SparseArray; + +import org.floens.chan.utils.Logger; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +/** + * Abstraction of the startActivityForResult and onActivityResult calls. It autogenerates + * a result code and adds a callback interface. Activities must implement the + * ActivityResultStarter interface and the application must implement the + * ApplicationActivitiesProvider interface. + */ +public class ActivityResultHelper { + private static final String TAG = "ActivityResultHelper"; + + private Context applicationContext; + + private int resultCounter = 100; + + @Inject + public ActivityResultHelper(Context applicationContext) { + this.applicationContext = applicationContext; + } + + public void getResultFromIntent(Intent intent, ActivityResultCallback callback) { + resultCounter++; + + ActivityResultStarter starter = findStarter(); + if (starter == null) { + Logger.e(TAG, "Could not find an active activity to use."); + callback.onActivityResult(Activity.RESULT_CANCELED, null); + return; + } + + starter.startActivityForResultWithCallback(intent, resultCounter, callback); + } + + private ActivityResultStarter findStarter() { + List activities = ((ApplicationActivitiesProvider) applicationContext).getActivities(); + List starters = new ArrayList<>(1); + for (Activity activity : activities) { + if (activity instanceof ActivityResultStarter) { + starters.add((ActivityResultStarter) activity); + } + } + + if (starters.isEmpty()) { + return null; + } + + if (starters.size() > 1) { + // Give priority to the resumed activities. + for (ActivityResultStarter starter : starters) { + if (starter.isActivityResumed()) { + return starter; + } + } + } + + return starters.get(0); + } + + public interface ActivityResultStarter { + boolean isActivityResumed(); + + void startActivityForResultWithCallback(Intent intent, int requestCode, + ActivityResultCallback callback); + } + + public interface ApplicationActivitiesProvider { + /** + * Get all created activities. You can use Application.ActivityLifecycleCallbacks + * to track the activities that are between the onCreate and onDestroy stage. + * + * @return a list of created activities. + */ + List getActivities(); + } + + public interface ActivityResultCallback { + void onActivityResult(int resultCode, Intent result); + } + + /** + * Helper class for Activities that implement the ActivityResultStarter interface. + */ + public static class ActivityStarterHelper { + private SparseArray activityResultCallbacks + = new SparseArray<>(); + private boolean isResumed = false; + + public void onResume() { + isResumed = true; + } + + public void onPause() { + isResumed = false; + } + + public boolean isActivityResumed() { + return isResumed; + } + + public void startActivityForResult(Activity activity, Intent intent, + int requestCode, ActivityResultCallback callback) { + if (activityResultCallbacks.indexOfKey(requestCode) >= 0) { + throw new IllegalArgumentException("requestCode " + requestCode + " already used"); + } + if (intent.resolveActivity(activity.getPackageManager()) != null) { + activity.startActivityForResult(intent, requestCode); + activityResultCallbacks.put(requestCode, callback); + } else { + Logger.e(TAG, "Can't start activity for result, intent does not resolve."); + callback.onActivityResult(Activity.RESULT_CANCELED, null); + } + } + + public boolean onActivityResult(int requestCode, int resultCode, Intent data) { + if (activityResultCallbacks.indexOfKey(requestCode) >= 0) { + ActivityResultHelper.ActivityResultCallback callback = + activityResultCallbacks.get(requestCode); + activityResultCallbacks.delete(requestCode); + + callback.onActivityResult(resultCode, data); + return true; + } + + return false; + } + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/ImagePickDelegate.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImagePickDelegate.java similarity index 99% rename from Clover/app/src/main/java/org/floens/chan/ui/helper/ImagePickDelegate.java rename to Clover/app/src/main/java/org/floens/chan/ui/activity/ImagePickDelegate.java index 7bc88ac1..c0168408 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/ImagePickDelegate.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImagePickDelegate.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.ui.helper; +package org.floens.chan.ui.activity; import android.app.Activity; import android.content.Intent; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/RuntimePermissionsHelper.java similarity index 99% rename from Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java rename to Clover/app/src/main/java/org/floens/chan/ui/activity/RuntimePermissionsHelper.java index c665c250..051757b1 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/RuntimePermissionsHelper.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.floens.chan.ui.helper; +package org.floens.chan.ui.activity; import android.app.Activity; import android.content.Context; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 3f7fc470..c164d06a 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java @@ -50,13 +50,11 @@ import org.floens.chan.core.site.SiteService; import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.DoubleNavigationController; import org.floens.chan.ui.controller.DrawerController; +import org.floens.chan.ui.controller.MediaSettingsController; import org.floens.chan.ui.controller.SplitNavigationController; -import org.floens.chan.ui.controller.StorageSetupController; import org.floens.chan.ui.controller.StyledToolbarNavigationController; import org.floens.chan.ui.controller.ThreadSlideController; import org.floens.chan.ui.controller.ViewThreadController; -import org.floens.chan.ui.helper.ImagePickDelegate; -import org.floens.chan.ui.helper.RuntimePermissionsHelper; import org.floens.chan.ui.helper.VersionHandler; import org.floens.chan.ui.state.ChanState; import org.floens.chan.ui.theme.ThemeHelper; @@ -71,7 +69,9 @@ import javax.inject.Inject; import static org.floens.chan.Chan.inject; -public class StartActivity extends AppCompatActivity implements NfcAdapter.CreateNdefMessageCallback { +public class StartActivity extends AppCompatActivity implements + NfcAdapter.CreateNdefMessageCallback, + ActivityResultHelper.ActivityResultStarter { private static final String TAG = "StartActivity"; private static final String STATE_KEY = "chan_state"; @@ -85,6 +85,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat private ImagePickDelegate imagePickDelegate; private RuntimePermissionsHelper runtimePermissionsHelper; + private ActivityResultHelper.ActivityStarterHelper resultHelper; private VersionHandler versionHandler; private boolean intentMismatchWorkaroundActive = false; @@ -116,6 +117,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat imagePickDelegate = new ImagePickDelegate(this); runtimePermissionsHelper = new RuntimePermissionsHelper(this); + resultHelper = new ActivityResultHelper.ActivityStarterHelper(); versionHandler = new VersionHandler(this, runtimePermissionsHelper); contentView = findViewById(android.R.id.content); @@ -144,6 +146,32 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat versionHandler.run(); } + @Override + protected void onDestroy() { + super.onDestroy(); + + if (intentMismatchWorkaround()) { + return; + } + + // TODO: clear whole stack? + stackTop().onHide(); + stackTop().onDestroy(); + stack.clear(); + } + + @Override + protected void onResume() { + super.onResume(); + resultHelper.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + resultHelper.onPause(); + } + private void setupFromStateOrFreshLaunch(Bundle savedInstanceState) { boolean handled; if (savedInstanceState != null) { @@ -166,7 +194,9 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat browseController.loadWithDefaultBoard(); } - mainNavigationController.pushController(new StorageSetupController(this), false); + contentView.post(() -> { + mainNavigationController.pushController(new MediaSettingsController(this), false); + }); } private boolean restoreFromUrl() { @@ -472,25 +502,26 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat } @Override - protected void onDestroy() { - super.onDestroy(); - - if (intentMismatchWorkaround()) { - return; - } + public void startActivityForResultWithCallback( + Intent intent, int requestCode, ActivityResultHelper.ActivityResultCallback callback) { + resultHelper.startActivityForResult(this, intent, requestCode, callback); + } - // TODO: clear whole stack? - stackTop().onHide(); - stackTop().onDestroy(); - stack.clear(); + @Override + public boolean isActivityResumed() { + return resultHelper.isActivityResumed(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + resultHelper.onActivityResult(requestCode, resultCode, data); + + // TODO: move to resultHelper. imagePickDelegate.onActivityResult(requestCode, resultCode, data); + // Go through the controller stack. drawerController.onActivityResult(requestCode, resultCode, data); } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java index ea561c4d..c33f091d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java @@ -17,9 +17,12 @@ */ package org.floens.chan.ui.controller; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import org.floens.chan.R; +import org.floens.chan.core.presenter.StorageSetupPresenter; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.settings.BooleanSettingView; import org.floens.chan.ui.settings.LinkSettingView; @@ -31,11 +34,18 @@ import org.floens.chan.ui.settings.SettingsGroup; import java.util.ArrayList; import java.util.List; -import de.greenrobot.event.EventBus; +import javax.inject.Inject; +import static org.floens.chan.Chan.inject; import static org.floens.chan.utils.AndroidUtils.getString; -public class MediaSettingsController extends SettingsController { +public class MediaSettingsController extends SettingsController implements + StorageSetupPresenter.Callback { + private static final int OPEN_TREE_INTENT_RESULT_ID = 101; + + @Inject + private StorageSetupPresenter presenter; + // Special setting views private LinkSettingView saveLocation; private ListSettingView imageAutoLoadView; @@ -49,7 +59,7 @@ public class MediaSettingsController extends SettingsController { public void onCreate() { super.onCreate(); - EventBus.getDefault().register(this); + inject(this); navigation.setTitle(R.string.settings_screen_media); @@ -60,13 +70,22 @@ public class MediaSettingsController extends SettingsController { buildPreferences(); onPreferenceChange(imageAutoLoadView); + + presenter.create(this); } - @Override - public void onDestroy() { - super.onDestroy(); + private void requestTree() { +// Intent i = storage.getOpenTreeIntent(); +// ((Activity) context).startActivityForResult(i, OPEN_TREE_INTENT_RESULT_ID); +// updateName(); + } - EventBus.getDefault().unregister(this); + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == OPEN_TREE_INTENT_RESULT_ID && resultCode == Activity.RESULT_OK) { +// storage.handleOpenTreeIntent(intent); +// updateName(); + } } @Override @@ -78,10 +97,9 @@ public class MediaSettingsController extends SettingsController { } } - public void onEvent(ChanSettings.SettingChanged setting) { - if (setting.setting == ChanSettings.saveLocation) { - updateSaveLocationSetting(); - } + @Override + public void setSaveLocationDescription(String description) { + saveLocation.setDescription(description); } private void populatePreferences() { @@ -199,11 +217,6 @@ public class MediaSettingsController extends SettingsController { private void setupSaveLocationSetting(SettingsGroup media) { saveLocation = (LinkSettingView) media.add(new LinkSettingView(this, R.string.save_location_screen, 0, - v -> navigationController.pushController(new SaveLocationController(context)))); - updateSaveLocationSetting(); - } - - private void updateSaveLocationSetting() { - saveLocation.setDescription(ChanSettings.saveLocation.get()); + v -> presenter.saveLocationClicked())); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java index 70e48c4e..305c5d1d 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java @@ -28,7 +28,7 @@ import org.floens.chan.core.saver.FileWatcher; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.adapter.FilesAdapter; -import org.floens.chan.ui.helper.RuntimePermissionsHelper; +import org.floens.chan.ui.activity.RuntimePermissionsHelper; import org.floens.chan.ui.layout.FilesLayout; import java.io.File; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java deleted file mode 100644 index e867ad7c..00000000 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.floens.chan.ui.controller; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import org.floens.chan.R; -import org.floens.chan.controller.Controller; -import org.floens.chan.core.storage.Storage; - -import javax.inject.Inject; - -import static org.floens.chan.Chan.inject; - -public class StorageSetupController extends Controller implements View.OnClickListener { - private static final int OPEN_TREE_INTENT_RESULT_ID = 101; - - private static final String TAG = "StorageSetupController"; - - @Inject - private Storage storage; - - private TextView text; - private Button button; - - public StorageSetupController(Context context) { - super(context); - } - - @Override - public void onCreate() { - super.onCreate(); - - inject(this); - - // Navigation - navigation.setTitle(R.string.storage_setup_screen); - - // View inflation - view = inflateRes(R.layout.controller_storage_setup); - - // View binding - text = view.findViewById(R.id.text); - button = view.findViewById(R.id.button); - - // View setup - button.setOnClickListener(this); - - updateName(); - } - - @Override - public void onClick(View v) { - if (v == button) { - requestTree(); - } - } - - private void requestTree() { - Intent i = storage.getOpenTreeIntent(); - ((Activity) context).startActivityForResult(i, OPEN_TREE_INTENT_RESULT_ID); - updateName(); - } - - private void updateName() { - text.setText(storage.currentStorageName()); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == OPEN_TREE_INTENT_RESULT_ID && resultCode == Activity.RESULT_OK) { - storage.handleOpenTreeIntent(data.getData()); - updateName(); - } - } -} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java index db2db282..5867bf34 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java @@ -34,6 +34,7 @@ import org.floens.chan.R; import org.floens.chan.core.net.UpdateApiRequest; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.update.UpdateManager; +import org.floens.chan.ui.activity.RuntimePermissionsHelper; import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.Logger; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java index 9ff48145..519f4363 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java @@ -54,7 +54,7 @@ import org.floens.chan.ui.captcha.LegacyCaptchaLayout; import org.floens.chan.ui.captcha.v1.CaptchaNojsLayoutV1; import org.floens.chan.ui.captcha.v2.CaptchaNoJsLayoutV2; import org.floens.chan.ui.drawable.DropdownArrowDrawable; -import org.floens.chan.ui.helper.ImagePickDelegate; +import org.floens.chan.ui.activity.ImagePickDelegate; import org.floens.chan.ui.view.LoadView; import org.floens.chan.ui.view.SelectionListeningEditText; import org.floens.chan.utils.AndroidUtils; diff --git a/Clover/app/src/main/res/layout/controller_storage_setup.xml b/Clover/app/src/main/res/layout/controller_storage_setup.xml deleted file mode 100644 index 257d22a4..00000000 --- a/Clover/app/src/main/res/layout/controller_storage_setup.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - -