diff --git a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java index a119e7fc..88814863 100644 --- a/Clover/app/src/main/java/org/floens/chan/controller/Controller.java +++ b/Clover/app/src/main/java/org/floens/chan/controller/Controller.java @@ -18,6 +18,7 @@ package org.floens.chan.controller; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -135,6 +136,9 @@ public abstract class Controller { controller.navigationController = navigationController; } controller.onCreate(); + if (controller.view == null) { + throw new IllegalArgumentException("Controller has no view"); + } } public boolean removeChildController(Controller controller) { @@ -186,6 +190,12 @@ public abstract class Controller { return false; } + public void onActivityResult(int requestCode, int resultCode, Intent data) { + for (Controller childController : childControllers) { + childController.onActivityResult(requestCode, resultCode, data); + } + } + public void presentController(Controller controller) { presentController(controller, true); } diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java index 74d4815a..5d85350e 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java @@ -19,15 +19,14 @@ package org.floens.chan.core.saver; import android.content.Intent; import android.graphics.Bitmap; -import android.media.MediaScannerConnection; import android.net.Uri; -import org.floens.chan.core.cache.FileCacheListener; import org.floens.chan.core.cache.FileCache; import org.floens.chan.core.cache.FileCacheDownloader; +import org.floens.chan.core.cache.FileCacheListener; import org.floens.chan.core.model.PostImage; +import org.floens.chan.core.storage.StorageFile; import org.floens.chan.utils.AndroidUtils; -import org.floens.chan.utils.IOUtils; import org.floens.chan.utils.ImageDecoder; import org.floens.chan.utils.Logger; @@ -38,7 +37,6 @@ import javax.inject.Inject; import static org.floens.chan.Chan.inject; import static org.floens.chan.utils.AndroidUtils.dp; -import static org.floens.chan.utils.AndroidUtils.getAppContext; public class ImageSaveTask extends FileCacheListener implements Runnable { private static final String TAG = "ImageSaveTask"; @@ -48,7 +46,7 @@ public class ImageSaveTask extends FileCacheListener implements Runnable { private PostImage postImage; private ImageSaveTaskCallback callback; - private File destination; + private StorageFile destination; private boolean share; private boolean makeBitmap; private Bitmap bitmap; @@ -78,11 +76,11 @@ public class ImageSaveTask extends FileCacheListener implements Runnable { return postImage; } - public void setDestination(File destination) { + public void setDestination(StorageFile destination) { this.destination = destination; } - public File getDestination() { + public StorageFile getDestination() { return destination; } @@ -165,46 +163,32 @@ public class ImageSaveTask extends FileCacheListener implements Runnable { success = true; scanDestination(); if (makeBitmap) { - bitmap = ImageDecoder.decodeFile(destination, dp(512), dp(256)); + try { + bitmap = ImageDecoder.decodeFile(destination.inputStream(), dp(512), dp(256)); + } catch (IOException e) { + Logger.e(TAG, "onDestination decodeFile", e); + bitmap = null; + } } } private boolean copyToDestination(File source) { - boolean result = false; - try { - File parent = destination.getParentFile(); - if (!parent.mkdirs() && !parent.isDirectory()) { - throw new IOException("Could not create parent directory"); - } - - if (destination.isDirectory()) { - throw new IOException("Destination file is already a directory"); - } - - IOUtils.copyFile(source, destination); - - result = true; + destination.copyFrom(source); + return true; } catch (IOException e) { - Logger.e(TAG, "Error writing to file", e); + Logger.e(TAG, "copyToDestination copyFrom", e); } - return result; + return false; } private void scanDestination() { - MediaScannerConnection.scanFile(getAppContext(), new String[]{destination.getAbsolutePath()}, null, new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, final Uri uri) { - // Runs on a binder thread - AndroidUtils.runOnUiThread(new Runnable() { - @Override - public void run() { - afterScan(uri); - } - }); - } - }); + // TODO +// MediaScannerConnection.scanFile(getAppContext(), new String[]{destination.getAbsolutePath()}, null, (path, uri) -> { +// Runs on a binder thread +// AndroidUtils.runOnUiThread(() -> afterScan(uri)); +// }); } private void afterScan(final Uri uri) { 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 ef99fb31..d145a032 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 @@ -27,6 +27,8 @@ import android.widget.Toast; import org.floens.chan.R; import org.floens.chan.core.model.PostImage; 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.service.SavingNotification; @@ -37,11 +39,15 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; +import javax.inject.Inject; +import javax.inject.Singleton; + import de.greenrobot.event.EventBus; import static org.floens.chan.utils.AndroidUtils.getAppContext; import static org.floens.chan.utils.AndroidUtils.getString; +@Singleton public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { private static final String TAG = "ImageSaver"; private static final int MAX_RENAME_TRIES = 500; @@ -49,18 +55,18 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { private static final int MAX_NAME_LENGTH = 50; private static final Pattern REPEATED_UNDERSCORES_PATTERN = Pattern.compile("_+"); private static final Pattern SAFE_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9._]"); - private static final ImageSaver instance = new ImageSaver(); private NotificationManager notificationManager; private ExecutorService executor = Executors.newSingleThreadExecutor(); private int doneTasks = 0; private int totalTasks = 0; private Toast toast; - public static ImageSaver getInstance() { - return instance; - } + private Storage storage; + + @Inject + public ImageSaver(Storage storage) { + this.storage = storage; - private ImageSaver() { EventBus.getDefault().register(this); notificationManager = (NotificationManager) getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); } @@ -68,8 +74,13 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { public void startDownloadTask(Context context, final ImageSaveTask task) { PostImage postImage = task.getPostImage(); String name = ChanSettings.saveOriginalFilename.get() ? postImage.originalName : postImage.filename; - String fileName = filterName(name + "." + postImage.extension); - task.setDestination(findUnusedFileName(new File(getSaveLocation(task), fileName), false)); +// String fileName = filterName(name + "." + postImage.extension); + + // TODO + StorageFile file = storage.obtainStorageFileForName(name + "." + postImage.extension); + task.setDestination(file); + +// task.setDestination(findUnusedFileName(new File(getSaveLocation(task), fileName), false)); // task.setMakeBitmap(true); task.setShowToast(true); @@ -163,9 +174,9 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { for (ImageSaveTask task : tasks) { PostImage postImage = task.getPostImage(); String fileName = filterName(postImage.originalName + "." + postImage.extension); - task.setDestination(new File(getSaveLocation(task) + File.separator + subFolder + File.separator + fileName)); - - startTask(task); + // TODO +// task.setDestination(new File(getSaveLocation(task) + File.separator + subFolder + File.separator + fileName)); +// startTask(task); } updateNotification(); } @@ -194,7 +205,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { NotificationCompat.Builder builder = new NotificationCompat.Builder(getAppContext()); builder.setSmallIcon(R.drawable.ic_stat_notify); builder.setContentTitle(getString(R.string.image_save_saved)); - String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().getName()); + String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().name()); builder.setContentText(savedAs); builder.setPriority(NotificationCompat.PRIORITY_HIGH); builder.setStyle(new NotificationCompat.BigPictureStyle() @@ -210,7 +221,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { } String text = success ? - getAppContext().getString(R.string.image_save_as, task.getDestination().getName()) : + getAppContext().getString(R.string.image_save_as, task.getDestination().name()) : getString(R.string.image_save_failed); toast = Toast.makeText(getAppContext(), text, Toast.LENGTH_LONG); toast.show(); diff --git a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java index 2dd373e9..acf07d7c 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java +++ b/Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java @@ -115,6 +115,7 @@ public class ChanSettings { public static final BooleanSetting developer; public static final StringSetting saveLocation; + public static final StringSetting saveLocationTreeUri; public static final BooleanSetting saveOriginalFilename; public static final BooleanSetting shareUrl; public static final BooleanSetting enableReplyFab; @@ -197,6 +198,7 @@ public class ChanSettings { saveLocation = new StringSetting(p, "preference_image_save_location", Environment.getExternalStorageDirectory() + File.separator + "Clover"); saveLocation.addCallback((setting, value) -> EventBus.getDefault().post(new SettingChanged<>(saveLocation))); + saveLocationTreeUri = new StringSetting(p, "preference_image_save_tree_uri", ""); saveOriginalFilename = new BooleanSetting(p, "preference_image_save_original", false); shareUrl = new BooleanSetting(p, "preference_image_share_url", false); enableReplyFab = new BooleanSetting(p, "preference_enable_reply_fab", true); 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 new file mode 100644 index 00000000..cc9b509a --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java @@ -0,0 +1,38 @@ +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 new file mode 100644 index 00000000..3d4ab143 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java @@ -0,0 +1,106 @@ +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 846f7569..c7fe9c66 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,66 +1,58 @@ package org.floens.chan.core.storage; -import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Build; -import android.os.storage.StorageManager; -import android.os.storage.StorageVolume; - -import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Abstraction of the storage APIs available in Android. + *

+ * This is used primarily for saving images, especially on removable storage. + *

+ * First, a good read: + * https://commonsware.com/blog/2017/11/13/storage-situation-internal-storage.html + * https://commonsware.com/blog/2017/11/14/storage-situation-external-storage.html + * https://commonsware.com/blog/2017/11/15/storage-situation-removable-storage.html + *

+ * The Android Storage Access Framework can be used from Android 5.0 and higher. Since Android 5.0 + * it has support for granting permissions for a directory, which we want to save our files to. + *

+ * Otherwise a fallback is provided for only saving on the primary volume with the older APIs. + */ +@Singleton public class Storage { - private static final Storage instance; + private StorageImpl impl; - static { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - instance = new Storage(new BaseStorageImpl()); + @Inject + public Storage(Context applicationContext) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + impl = new BaseStorageImpl(applicationContext); } else { - instance = new Storage(new NougatStorageImpl()); + impl = new LollipopStorageImpl(applicationContext); } - - } - - public static Storage getInstance() { - return instance; } - private StorageImpl impl; - - public Storage(StorageImpl impl) { - this.impl = impl; + public boolean supportsExternalStorage() { + return impl.supportsExternalStorage(); } - public Intent requestExternalPermission(Context applicationContext) { - return impl.requestExternalPermission(applicationContext); + public Intent getOpenTreeIntent() { + return impl.getOpenTreeIntent(); } - public interface StorageImpl { - Intent requestExternalPermission(Context applicationContext); + public void handleOpenTreeIntent(Uri uri) { + impl.handleOpenTreeIntent(uri); } - public static class BaseStorageImpl implements StorageImpl { - @Override - public Intent requestExternalPermission(Context applicationContext) { - throw new UnsupportedOperationException(); - } + public StorageFile obtainStorageFileForName(String name) { + return impl.obtainStorageFileForName(name); } - @TargetApi(Build.VERSION_CODES.N) - public static class NougatStorageImpl extends BaseStorageImpl { - @Override - public Intent requestExternalPermission(Context applicationContext) { - StorageManager sm = (StorageManager) - applicationContext.getSystemService(Context.STORAGE_SERVICE); - Objects.requireNonNull(sm); - for (StorageVolume storageVolume : sm.getStorageVolumes()) { - if (!storageVolume.isPrimary()) { - Intent accessIntent = storageVolume.createAccessIntent(null); - return accessIntent; - } - } - - return null; - } + public String currentStorageName() { + return impl.currentStorageName(); } } diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/StorageFile.java b/Clover/app/src/main/java/org/floens/chan/core/storage/StorageFile.java new file mode 100644 index 00000000..da06db48 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/StorageFile.java @@ -0,0 +1,93 @@ +package org.floens.chan.core.storage; + +import android.content.ContentResolver; +import android.net.Uri; + +import org.floens.chan.utils.IOUtils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class StorageFile { + private final ContentResolver contentResolver; + private final Uri uriOpenableByContentResolvers; + private final File file; + + public static StorageFile fromFile(File file) { + return new StorageFile(file); + } + + public static StorageFile fromUri(ContentResolver contentResolver, Uri uriOpenableByContentResolvers) { + return new StorageFile(contentResolver, uriOpenableByContentResolvers); + } + + private StorageFile(ContentResolver contentResolver, Uri uriOpenableByContentResolvers) { + this.contentResolver = contentResolver; + this.uriOpenableByContentResolvers = uriOpenableByContentResolvers; + this.file = null; + } + + private StorageFile(File file) { + this.contentResolver = null; + this.uriOpenableByContentResolvers = null; + this.file = file; + } + + public InputStream inputStream() throws IOException { + if (file != null) { + return new FileInputStream(file); + } else { + return contentResolver.openInputStream(uriOpenableByContentResolvers); + } + } + + public OutputStream outputStream() throws IOException { + if (file != null) { + File parent = file.getParentFile(); + if (!parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("Could not create parent directory"); + } + + if (file.isDirectory()) { + throw new IOException("Destination not a file"); + } + + return new FileOutputStream(file); + } else { + return contentResolver.openOutputStream(uriOpenableByContentResolvers); + } + } + + public String name() { + return "dummy name"; + } + + public boolean exists() { + // TODO + return false; + } + + public boolean delete() { + // TODO + return true; + } + + public void copyFrom(File source) throws IOException { + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(new FileInputStream(source)); + os = new BufferedOutputStream(outputStream()); + IOUtils.copy(is, os); + } finally { + IOUtils.closeQuietly(is); + IOUtils.closeQuietly(os); + } + } +} 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 new file mode 100644 index 00000000..8bf3e711 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java @@ -0,0 +1,16 @@ +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/StartActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java index 03dd2ea5..3f7fc470 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 @@ -51,6 +51,7 @@ 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.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; @@ -164,6 +165,8 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat } else { browseController.loadWithDefaultBoard(); } + + mainNavigationController.pushController(new StorageSetupController(this), false); } private boolean restoreFromUrl() { @@ -487,6 +490,8 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat super.onActivityResult(requestCode, resultCode, data); imagePickDelegate.onActivityResult(requestCode, resultCode, data); + + drawerController.onActivityResult(requestCode, resultCode, data); } private Controller stackTop() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java index 1450f8fa..d007e8ab 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java @@ -46,6 +46,9 @@ import org.floens.chan.utils.RecyclerUtils; import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + +import static org.floens.chan.Chan.inject; import static org.floens.chan.ui.theme.ThemeHelper.theme; import static org.floens.chan.utils.AndroidUtils.dp; @@ -59,6 +62,8 @@ public class AlbumDownloadController extends Controller implements View.OnClickL private boolean allChecked = true; private AlbumAdapter adapter; + + @Inject private ImageSaver imageSaver; public AlbumDownloadController(Context context) { @@ -69,7 +74,7 @@ public class AlbumDownloadController extends Controller implements View.OnClickL public void onCreate() { super.onCreate(); - imageSaver = ImageSaver.getInstance(); + inject(this); view = inflateRes(R.layout.controller_album_download); diff --git a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java index 7a05c547..d2324495 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java @@ -92,6 +92,9 @@ public class ImageViewerController extends Controller implements ImageViewerPres @Inject ImageLoader imageLoader; + @Inject + ImageSaver imageSaver; + private int statusBarColorPrevious; private AnimatorSet startAnimation; private AnimatorSet endAnimation; @@ -226,7 +229,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres File.separator + presenter.getLoadable().boardCode); } - ImageSaver.getInstance().startDownloadTask(context, task); + imageSaver.startDownloadTask(context, task); } } 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 bd1acd51..70e48c4e 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 @@ -18,9 +18,7 @@ package org.floens.chan.ui.controller; import android.Manifest; -import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.support.design.widget.FloatingActionButton; import android.view.View; @@ -28,7 +26,6 @@ import org.floens.chan.R; import org.floens.chan.controller.Controller; import org.floens.chan.core.saver.FileWatcher; import org.floens.chan.core.settings.ChanSettings; -import org.floens.chan.core.storage.Storage; import org.floens.chan.ui.activity.StartActivity; import org.floens.chan.ui.adapter.FilesAdapter; import org.floens.chan.ui.helper.RuntimePermissionsHelper; @@ -77,18 +74,6 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi } else { requestPermission(); } - - test(); - } - - private void test() { - Storage storage = Storage.getInstance(); - Intent intent = storage.requestExternalPermission(context.getApplicationContext()); - if (intent != null) { - if (intent.resolveActivity(context.getPackageManager()) != null) { - ((Activity) context).startActivityForResult(intent, 10024); - } - } } @Override 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 new file mode 100644 index 00000000..e867ad7c --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java @@ -0,0 +1,79 @@ +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/utils/ImageDecoder.java b/Clover/app/src/main/java/org/floens/chan/utils/ImageDecoder.java index be6bf4f6..0c7138ad 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/ImageDecoder.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/ImageDecoder.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; /** * Simple ImageDecoder. Taken from Volley ImageRequest. @@ -55,26 +56,27 @@ public class ImageDecoder { if (!file.exists()) return null; - FileInputStream fis; - try { - fis = new FileInputStream(file); + InputStream fis = new FileInputStream(file); + return decodeFile(fis, maxWidth, maxHeight); } catch (FileNotFoundException e) { e.printStackTrace(); return null; } + } + public static Bitmap decodeFile(InputStream is, int maxWidth, int maxHeight) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Bitmap bitmap = null; try { - IOUtils.copy(fis, baos); + IOUtils.copy(is, baos); bitmap = decode(baos.toByteArray(), maxWidth, maxHeight); } catch (IOException | OutOfMemoryError e) { e.printStackTrace(); } finally { - IOUtils.closeQuietly(fis); + IOUtils.closeQuietly(is); IOUtils.closeQuietly(baos); } diff --git a/Clover/app/src/main/res/layout/controller_storage_setup.xml b/Clover/app/src/main/res/layout/controller_storage_setup.xml new file mode 100644 index 00000000..257d22a4 --- /dev/null +++ b/Clover/app/src/main/res/layout/controller_storage_setup.xml @@ -0,0 +1,25 @@ + + + + + + + +