From e22cfcf8eed5b0ebbd1752d3feec411773614753 Mon Sep 17 00:00:00 2001 From: Floens Date: Sun, 12 Aug 2018 11:10:18 +0200 Subject: [PATCH] implement file and saf abstractions. --- .../core/presenter/StorageSetupPresenter.java | 29 +++++---- .../chan/core/settings/ChanSettings.java | 6 +- .../org/floens/chan/core/storage/Storage.java | 62 ++++++++++++++++--- .../floens/chan/core/storage/StorageFile.java | 23 +++++-- .../controller/MediaSettingsController.java | 38 +++++++++++- 5 files changed, 125 insertions(+), 33 deletions(-) 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 index 84febe92..32ac163c 100644 --- 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 @@ -17,11 +17,9 @@ */ package org.floens.chan.core.presenter; -import android.app.Activity; -import android.content.Intent; +import android.os.Build; import org.floens.chan.core.storage.Storage; -import org.floens.chan.ui.activity.ActivityResultHelper; import javax.inject.Inject; @@ -29,12 +27,10 @@ public class StorageSetupPresenter { private Callback callback; private Storage storage; - private ActivityResultHelper results; @Inject - public StorageSetupPresenter(Storage storage, ActivityResultHelper results) { + public StorageSetupPresenter(Storage storage) { this.storage = storage; - this.results = results; } public void create(Callback callback) { @@ -43,13 +39,20 @@ public class StorageSetupPresenter { updateDescription(); } - public void saveLocationClicked() { - Intent openTreeIntent = storage.getOpenTreeIntent(); - results.getResultFromIntent(openTreeIntent, (resultCode, result) -> { - if (resultCode == Activity.RESULT_OK) { - storage.handleOpenTreeIntent(result); + public void saveLocationClicked(boolean forceFile) { + if (!forceFile && + storage.getModeForNewLocation() == Storage.Mode.STORAGE_ACCESS_FRAMEWORK) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // lint + storage.setupNewSAFSaveLocation(this::updateDescription); } - }); + } else { + String fileSaveLocation = storage.getFileSaveLocation(); + callback.showPathDialog(fileSaveLocation); + } + } + + public void saveLocationEntered(String input) { + storage.setFileSaveLocation(input); } private void updateDescription() { @@ -59,5 +62,7 @@ public class StorageSetupPresenter { public interface Callback { void setSaveLocationDescription(String description); + + void showPathDialog(String path); } } 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 acf07d7c..dff4c45f 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 @@ -17,7 +17,6 @@ */ package org.floens.chan.core.settings; -import android.os.Environment; import android.text.TextUtils; import org.floens.chan.BuildConfig; @@ -27,7 +26,6 @@ import org.floens.chan.core.update.UpdateManager; import org.floens.chan.ui.adapter.PostsFilter; import org.floens.chan.utils.AndroidUtils; -import java.io.File; import java.net.InetSocketAddress; import java.net.Proxy; @@ -195,9 +193,7 @@ public class ChanSettings { developer = new BooleanSetting(p, "preference_developer", false); - saveLocation = new StringSetting(p, "preference_image_save_location", Environment.getExternalStorageDirectory() + File.separator + "Clover"); - saveLocation.addCallback((setting, value) -> - EventBus.getDefault().post(new SettingChanged<>(saveLocation))); + saveLocation = new StringSetting(p, "preference_image_save_location", ""); 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); 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 2cf7831d..6f52af9b 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 @@ -17,20 +17,24 @@ */ package org.floens.chan.core.storage; +import android.app.Activity; 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.os.Environment; 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.ui.activity.ActivityResultHelper; import org.floens.chan.utils.IOUtils; import org.floens.chan.utils.Logger; +import java.io.File; import java.io.FileNotFoundException; import javax.inject.Inject; @@ -55,6 +59,8 @@ import javax.inject.Singleton; public class Storage { private static final String TAG = "Storage"; + private static final String DEFAULT_DIRECTORY_NAME = "Clover"; + /** * The current mode of the Storage. */ @@ -76,13 +82,15 @@ public class Storage { } private Context applicationContext; + private ActivityResultHelper results; private final StringSetting saveLocation; private final StringSetting saveLocationTreeUri; @Inject - public Storage(Context applicationContext) { + public Storage(Context applicationContext, ActivityResultHelper results) { this.applicationContext = applicationContext; + this.results = results; saveLocation = ChanSettings.saveLocation; saveLocationTreeUri = ChanSettings.saveLocationTreeUri; @@ -97,11 +105,34 @@ public class Storage { return Mode.FILE; } - if (!saveLocation.get().isEmpty()) { + // File by default. + if (saveLocation.get().isEmpty() && saveLocationTreeUri.get().isEmpty()) { return Mode.FILE; } - return Mode.STORAGE_ACCESS_FRAMEWORK; + if (!saveLocationTreeUri.get().isEmpty()) { + return Mode.STORAGE_ACCESS_FRAMEWORK; + } + + return Mode.FILE; + } + + public Mode getModeForNewLocation() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return Mode.FILE; + } else { + return Mode.STORAGE_ACCESS_FRAMEWORK; + } + } + + public String getFileSaveLocation() { + prepareDefaultFileSaveLocation(); + return saveLocation.get(); + } + + public void setFileSaveLocation(String location) { + saveLocation.set(location); + saveLocationTreeUri.set(""); } public String currentStorageName() { @@ -112,7 +143,7 @@ public class Storage { case STORAGE_ACCESS_FRAMEWORK: { String uriString = saveLocationTreeUri.get(); Uri treeUri = Uri.parse(uriString); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // lint return queryTreeName(treeUri); } } @@ -120,14 +151,27 @@ public class Storage { throw new IllegalStateException(); } - public Mode getModeForNewLocation() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - return Mode.FILE; - } else { - return Mode.STORAGE_ACCESS_FRAMEWORK; + private void prepareDefaultFileSaveLocation() { + if (saveLocation.get().isEmpty()) { + File pictures = Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_PICTURES); + File directory = new File(pictures, DEFAULT_DIRECTORY_NAME); + String absolutePath = directory.getAbsolutePath(); + saveLocation.set(absolutePath); } } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void setupNewSAFSaveLocation(Runnable handled) { + Intent openTreeIntent = getOpenTreeIntent(); + results.getResultFromIntent(openTreeIntent, (resultCode, result) -> { + if (resultCode == Activity.RESULT_OK) { + handleOpenTreeIntent(result); + handled.run(); + } + }); + } + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public Intent getOpenTreeIntent() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); 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 index da06db48..c84fdb4a 100644 --- 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 @@ -40,7 +40,7 @@ public class StorageFile { } public InputStream inputStream() throws IOException { - if (file != null) { + if (isFile()) { return new FileInputStream(file); } else { return contentResolver.openInputStream(uriOpenableByContentResolvers); @@ -48,7 +48,7 @@ public class StorageFile { } public OutputStream outputStream() throws IOException { - if (file != null) { + if (isFile()) { File parent = file.getParentFile(); if (!parent.mkdirs() && !parent.isDirectory()) { throw new IOException("Could not create parent directory"); @@ -69,13 +69,20 @@ public class StorageFile { } public boolean exists() { - // TODO - return false; + if (isFile()) { + return file.exists() && file.isFile(); + } else { + return false; // we don't know? + } } public boolean delete() { - // TODO - return true; + if (isFile()) { + return file.delete(); + } else { + // TODO + return true; + } } public void copyFrom(File source) throws IOException { @@ -90,4 +97,8 @@ public class StorageFile { IOUtils.closeQuietly(os); } } + + private boolean isFile() { + return file != null; + } } 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 c33f091d..ccc0daf7 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 @@ -18,8 +18,12 @@ package org.floens.chan.ui.controller; import android.app.Activity; +import android.app.AlertDialog; import android.content.Context; import android.content.Intent; +import android.view.View; +import android.widget.EditText; +import android.widget.FrameLayout; import org.floens.chan.R; import org.floens.chan.core.presenter.StorageSetupPresenter; @@ -37,6 +41,7 @@ import java.util.List; 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.getString; public class MediaSettingsController extends SettingsController implements @@ -102,6 +107,26 @@ public class MediaSettingsController extends SettingsController implements saveLocation.setDescription(description); } + @Override + public void showPathDialog(String path) { + FrameLayout container = new FrameLayout(context); + EditText dialogView = new EditText(context); + dialogView.setText(path); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT + ); + lp.leftMargin = lp.rightMargin = dp(20); + container.addView(dialogView, lp); + new AlertDialog.Builder(context) + .setTitle(R.string.save_location_screen) + .setView(container) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.ok, (dialog, which) -> { + presenter.saveLocationEntered(dialogView.getText().toString()); + }) + .show(); + } + private void populatePreferences() { // Media group { @@ -215,8 +240,19 @@ public class MediaSettingsController extends SettingsController implements } private void setupSaveLocationSetting(SettingsGroup media) { + // Register a normal click listener and a long click listener that sets the + // force file option to true. saveLocation = (LinkSettingView) media.add(new LinkSettingView(this, R.string.save_location_screen, 0, - v -> presenter.saveLocationClicked())); + v -> presenter.saveLocationClicked(false)) { + @Override + public void setView(View view) { + super.setView(view); + view.setOnLongClickListener(v -> { + presenter.saveLocationClicked(true); + return true; + }); + } + }); } }