add activity result helper as an abstract for the activity results

external-storage-support
Floens 7 years ago
parent 5e09cd398b
commit 3db2ac07de
  1. 17
      Clover/app/src/main/java/org/floens/chan/Chan.java
  2. 2
      Clover/app/src/main/java/org/floens/chan/core/presenter/ReplyPresenter.java
  3. 63
      Clover/app/src/main/java/org/floens/chan/core/presenter/StorageSetupPresenter.java
  4. 2
      Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java
  5. 38
      Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java
  6. 106
      Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java
  7. 191
      Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java
  8. 16
      Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java
  9. 156
      Clover/app/src/main/java/org/floens/chan/ui/activity/ActivityResultHelper.java
  10. 2
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImagePickDelegate.java
  11. 2
      Clover/app/src/main/java/org/floens/chan/ui/activity/RuntimePermissionsHelper.java
  12. 61
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  13. 47
      Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java
  14. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java
  15. 79
      Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java
  16. 1
      Clover/app/src/main/java/org/floens/chan/ui/helper/VersionHandler.java
  17. 2
      Clover/app/src/main/java/org/floens/chan/ui/layout/ReplyLayout.java
  18. 25
      Clover/app/src/main/res/layout/controller_storage_setup.xml
  19. 4
      Clover/app/src/main/res/values/strings.xml

@ -33,11 +33,14 @@ import org.floens.chan.core.di.NetModule;
import org.floens.chan.core.di.UserAgentProvider; import org.floens.chan.core.di.UserAgentProvider;
import org.floens.chan.core.manager.BoardManager; import org.floens.chan.core.manager.BoardManager;
import org.floens.chan.core.site.SiteService; 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.AndroidUtils;
import org.floens.chan.utils.LocaleUtils; import org.floens.chan.utils.LocaleUtils;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Time; import org.floens.chan.utils.Time;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
@ -45,7 +48,10 @@ import javax.inject.Inject;
import de.greenrobot.event.EventBus; import de.greenrobot.event.EventBus;
@SuppressLint("Registered") // extended by ChanApplication, which is registered in the manifest. @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"; private static final String TAG = "ChanApplication";
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
@ -63,6 +69,8 @@ public class Chan extends Application implements UserAgentProvider, Application.
@Inject @Inject
BoardManager boardManager; BoardManager boardManager;
private List<Activity> activities = new ArrayList<>();
private Feather feather; private Feather feather;
public Chan() { public Chan() {
@ -189,8 +197,14 @@ public class Chan extends Application implements UserAgentProvider, Application.
return getString(R.string.app_name) + "/" + version; return getString(R.string.app_name) + "/" + version;
} }
@Override
public List<Activity> getActivities() {
return activities;
}
@Override @Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) { public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
activities.add(activity);
} }
@Override @Override
@ -217,5 +231,6 @@ public class Chan extends Application implements UserAgentProvider, Application.
@Override @Override
public void onActivityDestroyed(Activity activity) { public void onActivityDestroyed(Activity activity) {
activities.remove(activity);
} }
} }

@ -37,7 +37,7 @@ import org.floens.chan.core.site.http.Reply;
import org.floens.chan.core.site.http.ReplyResponse; import org.floens.chan.core.site.http.ReplyResponse;
import org.floens.chan.ui.captcha.AuthenticationLayoutCallback; import org.floens.chan.ui.captcha.AuthenticationLayoutCallback;
import org.floens.chan.ui.captcha.AuthenticationLayoutInterface; 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 org.floens.chan.utils.Logger;
import java.io.File; import java.io.File;

@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

@ -30,7 +30,7 @@ import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.storage.Storage; import org.floens.chan.core.storage.Storage;
import org.floens.chan.core.storage.StorageFile; import org.floens.chan.core.storage.StorageFile;
import org.floens.chan.ui.activity.StartActivity; 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 org.floens.chan.ui.service.SavingNotification;
import java.io.File; import java.io.File;

@ -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();
}
}

@ -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;
}
}

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.core.storage; package org.floens.chan.core.storage;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; 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.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -25,34 +53,173 @@ import javax.inject.Singleton;
*/ */
@Singleton @Singleton
public class Storage { 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.
* <p>
* Uses the File api for internal/external storage.
*/
FILE,
/**
* Used on lollipop and higher.
* <p>
* 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 @Inject
public Storage(Context applicationContext) { 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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
impl = new BaseStorageImpl(applicationContext); return Mode.FILE;
} else {
impl = new LollipopStorageImpl(applicationContext);
} }
if (!saveLocation.get().isEmpty()) {
return Mode.FILE;
}
return Mode.STORAGE_ACCESS_FRAMEWORK;
} }
public boolean supportsExternalStorage() { public String currentStorageName() {
return impl.supportsExternalStorage(); 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() { 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) { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
impl.handleOpenTreeIntent(uri); 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) { 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() { @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
return impl.currentStorageName(); 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;
} }
} }

@ -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();
}

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Activity> activities = ((ApplicationActivitiesProvider) applicationContext).getActivities();
List<ActivityResultStarter> 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<Activity> 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<ActivityResultCallback> 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;
}
}
}

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.ui.helper; package org.floens.chan.ui.activity;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package org.floens.chan.ui.helper; package org.floens.chan.ui.activity;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;

@ -50,13 +50,11 @@ import org.floens.chan.core.site.SiteService;
import org.floens.chan.ui.controller.BrowseController; import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DoubleNavigationController; import org.floens.chan.ui.controller.DoubleNavigationController;
import org.floens.chan.ui.controller.DrawerController; 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.SplitNavigationController;
import org.floens.chan.ui.controller.StorageSetupController;
import org.floens.chan.ui.controller.StyledToolbarNavigationController; import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ThreadSlideController; import org.floens.chan.ui.controller.ThreadSlideController;
import org.floens.chan.ui.controller.ViewThreadController; 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.helper.VersionHandler;
import org.floens.chan.ui.state.ChanState; import org.floens.chan.ui.state.ChanState;
import org.floens.chan.ui.theme.ThemeHelper; import org.floens.chan.ui.theme.ThemeHelper;
@ -71,7 +69,9 @@ import javax.inject.Inject;
import static org.floens.chan.Chan.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 TAG = "StartActivity";
private static final String STATE_KEY = "chan_state"; private static final String STATE_KEY = "chan_state";
@ -85,6 +85,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
private ImagePickDelegate imagePickDelegate; private ImagePickDelegate imagePickDelegate;
private RuntimePermissionsHelper runtimePermissionsHelper; private RuntimePermissionsHelper runtimePermissionsHelper;
private ActivityResultHelper.ActivityStarterHelper resultHelper;
private VersionHandler versionHandler; private VersionHandler versionHandler;
private boolean intentMismatchWorkaroundActive = false; private boolean intentMismatchWorkaroundActive = false;
@ -116,6 +117,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
imagePickDelegate = new ImagePickDelegate(this); imagePickDelegate = new ImagePickDelegate(this);
runtimePermissionsHelper = new RuntimePermissionsHelper(this); runtimePermissionsHelper = new RuntimePermissionsHelper(this);
resultHelper = new ActivityResultHelper.ActivityStarterHelper();
versionHandler = new VersionHandler(this, runtimePermissionsHelper); versionHandler = new VersionHandler(this, runtimePermissionsHelper);
contentView = findViewById(android.R.id.content); contentView = findViewById(android.R.id.content);
@ -144,6 +146,32 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
versionHandler.run(); 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) { private void setupFromStateOrFreshLaunch(Bundle savedInstanceState) {
boolean handled; boolean handled;
if (savedInstanceState != null) { if (savedInstanceState != null) {
@ -166,7 +194,9 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
browseController.loadWithDefaultBoard(); browseController.loadWithDefaultBoard();
} }
mainNavigationController.pushController(new StorageSetupController(this), false); contentView.post(() -> {
mainNavigationController.pushController(new MediaSettingsController(this), false);
});
} }
private boolean restoreFromUrl() { private boolean restoreFromUrl() {
@ -472,25 +502,26 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
} }
@Override @Override
protected void onDestroy() { public void startActivityForResultWithCallback(
super.onDestroy(); Intent intent, int requestCode, ActivityResultHelper.ActivityResultCallback callback) {
resultHelper.startActivityForResult(this, intent, requestCode, callback);
if (intentMismatchWorkaround()) { }
return;
}
// TODO: clear whole stack? @Override
stackTop().onHide(); public boolean isActivityResumed() {
stackTop().onDestroy(); return resultHelper.isActivityResumed();
stack.clear();
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
resultHelper.onActivityResult(requestCode, resultCode, data);
// TODO: move to resultHelper.
imagePickDelegate.onActivityResult(requestCode, resultCode, data); imagePickDelegate.onActivityResult(requestCode, resultCode, data);
// Go through the controller stack.
drawerController.onActivityResult(requestCode, resultCode, data); drawerController.onActivityResult(requestCode, resultCode, data);
} }

@ -17,9 +17,12 @@
*/ */
package org.floens.chan.ui.controller; package org.floens.chan.ui.controller;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.presenter.StorageSetupPresenter;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.settings.BooleanSettingView; import org.floens.chan.ui.settings.BooleanSettingView;
import org.floens.chan.ui.settings.LinkSettingView; 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.ArrayList;
import java.util.List; 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; 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 // Special setting views
private LinkSettingView saveLocation; private LinkSettingView saveLocation;
private ListSettingView<ChanSettings.MediaAutoLoadMode> imageAutoLoadView; private ListSettingView<ChanSettings.MediaAutoLoadMode> imageAutoLoadView;
@ -49,7 +59,7 @@ public class MediaSettingsController extends SettingsController {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
EventBus.getDefault().register(this); inject(this);
navigation.setTitle(R.string.settings_screen_media); navigation.setTitle(R.string.settings_screen_media);
@ -60,13 +70,22 @@ public class MediaSettingsController extends SettingsController {
buildPreferences(); buildPreferences();
onPreferenceChange(imageAutoLoadView); onPreferenceChange(imageAutoLoadView);
presenter.create(this);
} }
@Override private void requestTree() {
public void onDestroy() { // Intent i = storage.getOpenTreeIntent();
super.onDestroy(); // ((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 @Override
@ -78,10 +97,9 @@ public class MediaSettingsController extends SettingsController {
} }
} }
public void onEvent(ChanSettings.SettingChanged setting) { @Override
if (setting.setting == ChanSettings.saveLocation) { public void setSaveLocationDescription(String description) {
updateSaveLocationSetting(); saveLocation.setDescription(description);
}
} }
private void populatePreferences() { private void populatePreferences() {
@ -199,11 +217,6 @@ public class MediaSettingsController extends SettingsController {
private void setupSaveLocationSetting(SettingsGroup media) { private void setupSaveLocationSetting(SettingsGroup media) {
saveLocation = (LinkSettingView) media.add(new LinkSettingView(this, saveLocation = (LinkSettingView) media.add(new LinkSettingView(this,
R.string.save_location_screen, 0, R.string.save_location_screen, 0,
v -> navigationController.pushController(new SaveLocationController(context)))); v -> presenter.saveLocationClicked()));
updateSaveLocationSetting();
}
private void updateSaveLocationSetting() {
saveLocation.setDescription(ChanSettings.saveLocation.get());
} }
} }

@ -28,7 +28,7 @@ import org.floens.chan.core.saver.FileWatcher;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.adapter.FilesAdapter; 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 org.floens.chan.ui.layout.FilesLayout;
import java.io.File; import java.io.File;

@ -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();
}
}
}

@ -34,6 +34,7 @@ import org.floens.chan.R;
import org.floens.chan.core.net.UpdateApiRequest; import org.floens.chan.core.net.UpdateApiRequest;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.update.UpdateManager; 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.AndroidUtils;
import org.floens.chan.utils.Logger; import org.floens.chan.utils.Logger;

@ -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.v1.CaptchaNojsLayoutV1;
import org.floens.chan.ui.captcha.v2.CaptchaNoJsLayoutV2; import org.floens.chan.ui.captcha.v2.CaptchaNoJsLayoutV2;
import org.floens.chan.ui.drawable.DropdownArrowDrawable; 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.LoadView;
import org.floens.chan.ui.view.SelectionListeningEditText; import org.floens.chan.ui.view.SelectionListeningEditText;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="open" />
</LinearLayout>
</ScrollView>

@ -546,10 +546,6 @@ Re-enable this permission in the app settings if you permanently disabled it."
<string name="setting_folder_navigate_up">Up</string> <string name="setting_folder_navigate_up">Up</string>
<!-- Storage setup settings -->
<string name="storage_setup_screen">Setup storage</string>
<!-- Theme settings --> <!-- Theme settings -->
<string name="setting_theme_explanation"> <string name="setting_theme_explanation">
"Swipe to change the theme. "Swipe to change the theme.

Loading…
Cancel
Save