From e29fecb4b94a94c362aea5526f9dbfe79aa6a529 Mon Sep 17 00:00:00 2001 From: Florens Douwes Date: Mon, 21 Jul 2014 18:17:21 +0200 Subject: [PATCH] Refactored imagesaver Add option to select the folder to save to. --- .../org/floens/chan/core/ChanPreferences.java | 23 +- .../ui/activity/AdvancedSettingsActivity.java | 33 +++ .../chan/ui/activity/ImageViewActivity.java | 24 +- .../chan/ui/fragment/FolderPickFragment.java | 196 +++++++++++++++ .../chan/ui/fragment/ImageViewFragment.java | 4 +- .../chan/ui/fragment/SettingsFragment.java | 1 - .../org/floens/chan/utils/ImageSaver.java | 231 +++++++++++------- .../res/drawable-hdpi/ic_action_cancel.png | Bin 0 -> 438 bytes .../drawable-hdpi/ic_action_cancel_dark.png | Bin 0 -> 353 bytes .../res/drawable-mdpi/ic_action_cancel.png | Bin 0 -> 328 bytes .../drawable-mdpi/ic_action_cancel_dark.png | Bin 0 -> 272 bytes .../res/drawable-xhdpi/ic_action_cancel.png | Bin 0 -> 513 bytes .../drawable-xhdpi/ic_action_cancel_dark.png | Bin 0 -> 415 bytes .../res/drawable-xxhdpi/ic_action_cancel.png | Bin 0 -> 567 bytes .../drawable-xxhdpi/ic_action_cancel_dark.png | Bin 0 -> 574 bytes .../app/src/main/res/layout/folder_pick.xml | 95 +++++++ .../app/src/main/res/layout/post_replies.xml | 4 +- Clover/app/src/main/res/values/strings.xml | 6 +- Clover/app/src/main/res/xml/preference.xml | 33 ++- .../src/main/res/xml/preference_advanced.xml | 20 +- 20 files changed, 539 insertions(+), 131 deletions(-) create mode 100644 Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java create mode 100644 Clover/app/src/main/res/drawable-hdpi/ic_action_cancel.png create mode 100644 Clover/app/src/main/res/drawable-hdpi/ic_action_cancel_dark.png create mode 100644 Clover/app/src/main/res/drawable-mdpi/ic_action_cancel.png create mode 100644 Clover/app/src/main/res/drawable-mdpi/ic_action_cancel_dark.png create mode 100644 Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel.png create mode 100644 Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel_dark.png create mode 100644 Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png create mode 100644 Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel_dark.png create mode 100644 Clover/app/src/main/res/layout/folder_pick.xml diff --git a/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java b/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java index a06cd526..d39bbdd5 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java +++ b/Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java @@ -18,9 +18,12 @@ package org.floens.chan.core; import android.content.SharedPreferences; +import android.os.Environment; import org.floens.chan.ChanApplication; +import java.io.File; + public class ChanPreferences { private static SharedPreferences p() { return ChanApplication.getPreferences(); @@ -46,8 +49,24 @@ public class ChanPreferences { p().edit().putBoolean("preference_developer", developer).commit(); } - public static String getImageSaveDirectory() { - return "Clover"; + public static File getImageSaveDirectory() { + String path = p().getString("preference_image_save_location", null); + File file; + if (path == null) { + file = new File(Environment.getExternalStorageDirectory() + File.separator + "Clover"); + } else { + file = new File(path); + } + + return file; + } + + public static void setImageSaveDirectory(File file) { + p().edit().putString("preference_image_save_location", file.getAbsolutePath()).commit(); + } + + public static boolean getImageSaveOriginalFilename() { + return p().getBoolean("preference_image_save_original", false); } public static boolean getWatchEnabled() { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java index a04bd923..4cab2111 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java @@ -24,12 +24,19 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import org.floens.chan.R; +import org.floens.chan.core.ChanPreferences; +import org.floens.chan.ui.fragment.FolderPickFragment; +import org.floens.chan.utils.ThemeHelper; + +import java.io.File; public class AdvancedSettingsActivity extends PreferenceActivity { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + ThemeHelper.setTheme(this); + getFragmentManager().beginTransaction().replace(android.R.id.content, new AdvancedSettingsFragment()).commit(); } @@ -79,6 +86,32 @@ public class AdvancedSettingsActivity extends PreferenceActivity { return true; } }); + + reloadSavePath(); + final Preference saveLocation = findPreference("preference_image_save_location"); + saveLocation.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + File dir = ChanPreferences.getImageSaveDirectory(); + dir.mkdirs(); + + FolderPickFragment frag = FolderPickFragment.newInstance(new FolderPickFragment.FolderPickListener() { + @Override + public void folderPicked(File path) { + ChanPreferences.setImageSaveDirectory(path); + reloadSavePath(); + } + }, dir); + getActivity().getFragmentManager().beginTransaction().add(frag, null).commit(); + + return true; + } + }); + } + + private void reloadSavePath() { + Preference saveLocation = findPreference("preference_image_save_location"); + saveLocation.setSummary(ChanPreferences.getImageSaveDirectory().getAbsolutePath()); } private void updateSummary(ListPreference list, String value) { diff --git a/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java index fd701753..0a57d848 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java @@ -19,14 +19,15 @@ package org.floens.chan.ui.activity; import android.app.ActionBar; import android.app.Activity; -import android.net.Uri; import android.os.Bundle; import android.support.v4.view.ViewPager; +import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; import android.view.Window; import org.floens.chan.R; +import org.floens.chan.core.ChanPreferences; import org.floens.chan.core.model.Post; import org.floens.chan.ui.adapter.ImageViewAdapter; import org.floens.chan.ui.adapter.PostAdapter; @@ -166,20 +167,21 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang finish(); return true; } else if (item.getItemId() == R.id.action_download_album) { - List uris = new ArrayList<>(); - Post aPost = null; + List list = new ArrayList<>(); + + String name = "downloaded"; + String filename; for (Post post : adapter.getList()) { - uris.add(Uri.parse(post.imageUrl)); - aPost = post; + filename = (ChanPreferences.getImageSaveOriginalFilename() ? post.tim : post.filename) + "." + post.ext; + list.add(new ImageSaver.DownloadPair(post.imageUrl, filename)); + + name = post.board + "_" + post.resto; } - if (uris.size() > 0) { - String name = "downloaded"; - if (aPost != null) { - name = aPost.board + "_" + aPost.resto; + if (list.size() > 0) { + if (!TextUtils.isEmpty(name)) { + ImageSaver.saveAll(this, name, list); } - - ImageSaver.saveAll(this, name, uris); } return true; diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java new file mode 100644 index 00000000..887f48e6 --- /dev/null +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java @@ -0,0 +1,196 @@ +/* + * 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.fragment; + +import android.app.DialogFragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.FrameLayout; +import android.widget.ListView; +import android.widget.TextView; + +import org.floens.chan.R; +import org.floens.chan.utils.ThemeHelper; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class FolderPickFragment extends DialogFragment { + private TextView statusPath; + private ListView listView; + private ArrayAdapter adapter; + boolean hasParent = false; + + private FolderPickListener listener; + private File currentPath; + private List directories; + private FrameLayout okButton; + private TextView okButtonIcon; + + public static FolderPickFragment newInstance(FolderPickListener listener, File startingPath) { + FolderPickFragment fragment = new FolderPickFragment(); + fragment.listener = listener; + fragment.currentPath = startingPath; + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (listener == null || currentPath == null) { + dismiss(); + } + + setStyle(STYLE_NO_TITLE, 0); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup unused, Bundle savedInstanceState) { + if (listener == null || currentPath == null) { + return null; + } + + View container = inflater.inflate(R.layout.folder_pick, null); + + statusPath = (TextView) container.findViewById(R.id.folder_status); + listView = (ListView) container.findViewById(R.id.folder_list); + okButton = (FrameLayout) container.findViewById(R.id.pick_ok); + okButtonIcon = (TextView) container.findViewById(R.id.pick_ok_icon); + + container.findViewById(R.id.pick_back).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + + okButton.findViewById(R.id.pick_ok).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.folderPicked(currentPath); + dismiss(); + } + }); + + if (!ThemeHelper.getInstance().getTheme().isLightTheme) { + ((TextView) container.findViewById(R.id.pick_back_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_cancel_dark, 0, 0, 0); + ((TextView) container.findViewById(R.id.pick_ok_icon)).setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_action_done_dark, 0, 0, 0); + } + + adapter = new ArrayAdapter(inflater.getContext(), 0) { + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_1, null); + } + TextView text = (TextView) convertView; + + String name = getItem(position); + + text.setText(name); + + text.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (hasParent) { + if (position == 0) { + File parent = currentPath.getParentFile(); + moveTo(parent); + } else if (position > 0 && position <= directories.size()) { + File dir = directories.get(position - 1); + moveTo(dir); + } + } else { + if (position >= 0 && position < directories.size()) { + File dir = directories.get(position); + moveTo(dir); + } + } + } + }); + + return text; + } + }; + + listView.setAdapter(adapter); + + moveTo(currentPath); + + return container; + } + + private boolean validPath(File path) { + return path != null && path.isDirectory() && path.canRead() && path.canWrite(); + } + + private void moveTo(File path) { + if (path != null && path.isDirectory()) { + File[] listFiles = path.listFiles(); + if (listFiles != null) { + currentPath = path; + statusPath.setText(currentPath.getAbsolutePath()); + List dirs = new ArrayList<>(); + for (File file : path.listFiles()) { + if (file.isDirectory()) { + dirs.add(file); + } + } + + setDirs(dirs); + } + } + + validState(); + } + + private void validState() { + if (validPath(currentPath)) { + okButton.setEnabled(true); + okButtonIcon.setEnabled(true); + } else { + okButton.setEnabled(false); + okButtonIcon.setEnabled(false); + } + } + + private void setDirs(List dirs) { + directories = dirs; + adapter.clear(); + + if (currentPath.getParent() != null) { + adapter.add(".."); + hasParent = true; + } else { + hasParent = false; + } + for (File file : dirs) { + adapter.add(file.getName()); + } + adapter.notifyDataSetChanged(); + } + + public static interface FolderPickListener { + public void folderPicked(File path); + } +} diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java index 0e4a0017..ec29c22e 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java @@ -222,9 +222,9 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal } else if (item.getItemId() == R.id.action_open_browser) { Utils.openLink(context, post.imageUrl); } else if (item.getItemId() == R.id.action_image_save) { - ImageSaver.save(context, post.imageUrl, post.filename, post.ext, false); + ImageSaver.saveImage(context, post.imageUrl, ChanPreferences.getImageSaveOriginalFilename() ? post.tim : post.filename, post.ext, false); } else if (item.getItemId() == R.id.action_share) { - ImageSaver.save(context, post.imageUrl, post.filename, post.ext, true); + ImageSaver.saveImage(context, post.imageUrl, ChanPreferences.getImageSaveOriginalFilename() ? post.tim : post.filename, post.ext, true); } } diff --git a/Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java b/Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java index b4bd3451..b7129892 100644 --- a/Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java +++ b/Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java @@ -32,7 +32,6 @@ import android.widget.Toast; import org.floens.chan.R; import org.floens.chan.core.ChanPreferences; import org.floens.chan.ui.activity.AboutActivity; -import org.floens.chan.ui.activity.BaseActivity; import org.floens.chan.ui.activity.SettingsActivity; import org.floens.chan.utils.ThemeHelper; diff --git a/Clover/app/src/main/java/org/floens/chan/utils/ImageSaver.java b/Clover/app/src/main/java/org/floens/chan/utils/ImageSaver.java index 03c55d72..7ee99da7 100644 --- a/Clover/app/src/main/java/org/floens/chan/utils/ImageSaver.java +++ b/Clover/app/src/main/java/org/floens/chan/utils/ImageSaver.java @@ -38,45 +38,57 @@ import org.floens.chan.core.net.ByteArrayRequest; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; public class ImageSaver { private static final String TAG = "ImageSaver"; - public static void saveAll(Context context, String folderName, final List list) { + public static void saveAll(final Context context, String folderName, final List list) { final DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - String folderPath = ChanPreferences.getImageSaveDirectory() + File.separator + folderName; - File folder = Environment.getExternalStoragePublicDirectory(folderPath); - int nextFileNameNumber = 0; - while (folder.exists() || folder.isFile()) { - folderPath = ChanPreferences.getImageSaveDirectory() + File.separator + folderName + "_" - + Integer.toString(nextFileNameNumber++); - folder = Environment.getExternalStoragePublicDirectory(folderPath); - } + folderName = filterName(folderName); + + final File subFolder = findUnused(new File(ChanPreferences.getImageSaveDirectory() + File.separator + folderName), true); + + String text = context.getString(R.string.download_confirm) + .replace("COUNT", Integer.toString(list.size())) + .replace("FOLDER", subFolder.getAbsolutePath()); - final String finalFolderPath = folderPath; - String text = context.getString(R.string.download_confirm).replace("COUNT", Integer.toString(list.size())) - .replace("FOLDER", folderPath); - final File finalFolder = folder; new AlertDialog.Builder(context).setMessage(text).setNegativeButton(R.string.cancel, null) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { + if (!subFolder.isDirectory() && !subFolder.mkdirs()) { + Toast.makeText(context, R.string.image_save_directory_error, Toast.LENGTH_LONG).show(); + return; + } + + final List files = new ArrayList<>(list.size()); + for (DownloadPair uri : list) { + String name = filterName(uri.imageName); + // Finding unused filenames won't actually work, because the file doesn't get + // saved right away. The download manager will also prevent if there's a name collision. + File destination = findUnused(new File(subFolder, name), false); + + Pair p = new Pair(); + p.uri = Uri.parse(uri.imageUrl); + p.file = destination; + files.add(p); + } + new Thread(new Runnable() { @Override public void run() { - finalFolder.mkdirs(); - - for (Uri uri : list) { + for (Pair pair : files) { DownloadManager.Request request; try { - request = new DownloadManager.Request(uri); + request = new DownloadManager.Request(pair.uri); } catch (IllegalArgumentException e) { continue; } - request.setDestinationInExternalPublicDir(finalFolderPath, uri.getLastPathSegment()); + request.setDestinationUri(Uri.fromFile(pair.file)); request.setVisibleInDownloadsUi(false); request.allowScanningByMediaScanner(); @@ -88,26 +100,23 @@ public class ImageSaver { }).show(); } - public static void save(final Context context, String imageUrl, final String name, final String extension, - final boolean share) { + public static void saveImage(final Context context, String imageUrl, String name, final String extension, final boolean share) { + File saveDir = ChanPreferences.getImageSaveDirectory(); + + if (!saveDir.isDirectory() && !saveDir.mkdirs()) { + Toast.makeText(context, R.string.image_save_directory_error, Toast.LENGTH_LONG).show(); + return; + } + + String fileName = filterName(name + "." + extension); + final File file = findUnused(new File(saveDir, fileName), false); + + Logger.test(file.getAbsolutePath()); + ChanApplication.getVolleyRequestQueue().add(new ByteArrayRequest(imageUrl, new Response.Listener() { @Override public void onResponse(byte[] data) { - storeImage(context, data, name, extension, new SaveCallback() { - @Override - public void onUri(String name, Uri uri) { - if (!share) { - String message = context.getResources().getString(R.string.image_save_succeeded) + " " - + name; - Toast.makeText(context, message, Toast.LENGTH_LONG).show(); - } else { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("image/*"); - intent.putExtra(Intent.EXTRA_STREAM, uri); - context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))); - } - } - }); + storeImage(context, data, file, share); } }, new Response.ErrorListener() { @Override @@ -117,71 +126,81 @@ public class ImageSaver { })); } - private static void storeImage(final Context context, byte[] data, String name, String extension, - final SaveCallback callback) { - String errorReason = null; - - try { - if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - errorReason = context.getString(R.string.image_save_not_mounted); - throw new IOException(errorReason); - } + private static String filterName(String name) { + return name.replaceAll("[^\\w.]", ""); + } - File path = new File(Environment.getExternalStorageDirectory() + File.separator - + ChanPreferences.getImageSaveDirectory()); + private static File findUnused(File start, boolean isDir) { + String base; + String extension; - if (!path.exists()) { - if (!path.mkdirs()) { - errorReason = context.getString(R.string.image_save_directory_error); - throw new IOException(errorReason); - } + if (isDir) { + base = start.getAbsolutePath(); + extension = null; + } else { + String[] splitted = start.getAbsolutePath().split("\\.(?=[^\\.]+$)"); + if (splitted.length == 2) { + base = splitted[0]; + extension = "." + splitted[1]; + } else { + base = splitted[0]; + extension = "."; } + } - final File savedFile = saveFile(path, data, name, extension); - if (savedFile == null) { - errorReason = context.getString(R.string.image_save_failed); - throw new IOException(errorReason); + File test; + if (isDir) { + test = new File(base); + } else { + test = new File(base + extension); + } + int index = 0; + int tries = 0; + while (test.exists() && tries++ < 100) { + if (isDir) { + test = new File(base + "_" + index); + } else { + test = new File(base + "_" + index + extension); } + index++; + } - MediaScannerConnection.scanFile(context, new String[]{savedFile.toString()}, null, - new MediaScannerConnection.OnScanCompletedListener() { - @Override - public void onScanCompleted(String path, final Uri uri) { - Utils.runOnUiThread(new Runnable() { - @Override - public void run() { - Logger.i(TAG, "Media scan succeeded: " + uri); - callback.onUri(savedFile.toString(), uri); - } - }); - } - } - ); + return test; + } - } catch (IOException e) { - e.printStackTrace(); + private static void storeImage(final Context context, final byte[] data, final File destination, final boolean share) { + if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + Toast.makeText(context, R.string.image_save_not_mounted, Toast.LENGTH_LONG).show(); + return; + } - if (errorReason == null) - errorReason = context.getString(R.string.image_save_failed); + new Thread(new Runnable() { + @Override + public void run() { + final boolean res = saveByteArray(destination, data); - Toast.makeText(context, errorReason, Toast.LENGTH_LONG).show(); - } + Utils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (!res) { + Toast.makeText(context, R.string.image_save_failed, Toast.LENGTH_LONG).show(); + } else { + scanFile(context, destination.getAbsolutePath(), share); + } + } + }); + } + }).start(); } - private static File saveFile(File path, byte[] source, String name, String extension) { - File destination = new File(path, name + "." + extension); - int nextFileNameNumber = 0; - String newName; - while (destination.exists()) { - newName = name + "_" + Integer.toString(nextFileNameNumber++) + "." + extension; - destination = new File(path, newName); - } - + private static boolean saveByteArray(File destination, byte[] source) { + boolean res = false; FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(destination); outputStream.write(source); outputStream.close(); + res = true; } catch (IOException e) { e.printStackTrace(); } finally { @@ -192,13 +211,49 @@ public class ImageSaver { } } } + return res; + } + + private static void scanFile(final Context context, final String path, final boolean shareAfterwards) { + Logger.test("Scan: " + path); + + MediaScannerConnection.scanFile(context, new String[]{path}, null, + new MediaScannerConnection.OnScanCompletedListener() { + @Override + public void onScanCompleted(String unused, final Uri uri) { + Utils.runOnUiThread(new Runnable() { + @Override + public void run() { + Logger.i(TAG, "File saved & media scan succeeded: " + uri); - Logger.i(TAG, "Saved image to: " + destination.getPath()); + if (shareAfterwards) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("image/*"); + intent.putExtra(Intent.EXTRA_STREAM, uri); + context.startActivity(Intent.createChooser(intent, context.getString(R.string.action_share))); + } else { + String message = context.getResources().getString(R.string.image_save_succeeded) + " " + path; + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } + } + }); + } + } + ); + } + + public static class DownloadPair { + public String imageUrl; + public String imageName; - return destination; + public DownloadPair(String uri, String name) { + this.imageUrl = uri; + this.imageName = name; + } } - private static interface SaveCallback { - public void onUri(String name, Uri uri); + private static class Pair { + public Uri uri; + public File file; } } diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_action_cancel.png b/Clover/app/src/main/res/drawable-hdpi/ic_action_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..f889617e4471d6a0c65dd81a060bea482276b322 GIT binary patch literal 438 zcmV;n0ZIOeP)uR$+`ZL}CL9iu4pv&~TPqL?R?{{9XWYPqIRgeD^;8#3vi0QmIrbmFkC- z=lNP%LWrBV*rEMwV3MsoHqsVpnjWjFdZ5h9V<&ATapw~E1PAb=v+=Jp6hzH@=Ksnh z?p@-Z`}Cg%jQ{`u07*qoM6N<$g0AVdX8-^I literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-hdpi/ic_action_cancel_dark.png b/Clover/app/src/main/res/drawable-hdpi/ic_action_cancel_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..e206f296c2535b11a2243ae93a31b2957f42ab7d GIT binary patch literal 353 zcmV-n0iOPeP)PbXFRCwBAU>F6XU=$26V8kbBY;0@^ zq{4u>@9f#Lk4Z5Ys8<$96#?-opx)!S)G-k-2V^-H$RMbJq*~4eHG_d%2lN5)H&Psd zX8AX$88`tmKFR0LpMR2)lDYX-@nh*(FEV>wX{AjT2oT23$* zkPyFQSx&M8a5#d2T+4}$0s_&&O0MO^WJ8(*W-3I%Z(_12RUCjL5gsSDoS?`7xUw3l zg%w#2Ae7H3aRf7^mQ(5oW@=bYi6aPQLy}84s2Y@7l#+5_6*+|rnj=YcL aAiw|+C&DJQP0oz~0000<|{Ln>}1B}pV4FnQGw$lhbcF#pN_|NmTvlOA(yaXHp7=YfQzWd6l13QbWBbF5EntH}^M8nLKhNl}v$ z`vZ-XOPZxe9-MDujuPO_2=wV|3@K%?U6t&_#+%cevc=Z|mdKI;Vst0JaBXn*aa+ literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel.png b/Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..58e2e3b4d86d43293595bfbcdf7aa14d462d7dfd GIT binary patch literal 513 zcmV+c0{;DpP)2M=_pR@i* zPEjZcR^hV+AS76ZFCxHFf_3;}0xTpT!WR`#QUWS`aRDVHAj6vo$dP~!Zzf=k1ds5h z0`8IE6+U}6SYkz=AE7zA$Wy1p8|=~!7V&gK%#^meXz0%8wWZ964n9e$r4n+ z9g84Tf;flor_-F)OM-ZYj|(39WCq}AtQYDUDT$39RD*!28&48mq(Bf4M z*efW9cy$A|3d4V1o%z@v+C@zkpMKi=gq( z;I)vA(3}bH@|rvKM#%J5*z{q@^l{j9LBw=P%yd!IbXnZ=#fa&vu^iuQjhHdV;Tpbc z`@Fl|n2Wjr5BGg7jj7@Nn$Qio;4%G+3?YONLI@#*kQ4I~U;up6U|mr}u3rEE002ov JPDHLkV1oU%v4;Qv literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png b/Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..a9bbcde5a0267fbd3c4c6509ac1c62acf46f150f GIT binary patch literal 567 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGok|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+7;k#IIEGZ*dNXHt?qLHFSI_A;6@yoJy>&HeX8*_&=~AlY z`%pHfoh@Pshs;H>pYOJwV`TZC(D7dGud9|S&<QojJWq{W$G32b z+5V~1gJ(Xw^ejJ`(e%&xUmqVSJPiNw{_E=X^MpPv_%OM-#r}~0Ueza!552v22-k&k zw)=gRdH<(O`$GtK8R!4~v!1Zbc|A!_KQZ;k^r*>K?Gj2817|C@nmr5G57?Vqso%P4 zo96qq30ql}-e*n?kCATY?BH`Ktv3mA2oX5N&U~1)htK72frZO~6$vuuTo18);X9%D zr+{&9QxRvp(}8P^maY2A39BF6IW%49Lrz2A!_%AHd(*V&b)d^ZzkL#%zY!3T1~l9vDm!He@N#Jfu^j zcZ{=T|IvEE2|E;%<@c>-$vgG_=Q5kSK`&;A>NT$GJSg;Hj+UDB-^Sh9Nb<2H?#IoD$9}cKB rE>@6>R4b4>;QSjo7J%#o8+#@m23aH3DE>zRz930YS3j3^P6#a{g zR;!|d;+#c=g+U@&8#qlP?>iTDL8fv<-j3NWs1Dzp&i6b`Uf zX#a4S^$OQ1)f!I5VD@Js`+6EyHWhQndmY%!Xw{~#{@|iQ?BVGm24)xj?M?l$iOJ2& zW8Fh#s{qD=Ojqze(iZm7@2I z%I@218TfOAUI_H-2v0oV{2Mtkfb0dj_nCMYChW}1JZTwX29or2^>bP0l+XkKmnG>o literal 0 HcmV?d00001 diff --git a/Clover/app/src/main/res/layout/folder_pick.xml b/Clover/app/src/main/res/layout/folder_pick.xml new file mode 100644 index 00000000..4a8f2f1d --- /dev/null +++ b/Clover/app/src/main/res/layout/folder_pick.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Clover/app/src/main/res/layout/post_replies.xml b/Clover/app/src/main/res/layout/post_replies.xml index 6f7bca10..45e7c3fd 100644 --- a/Clover/app/src/main/res/layout/post_replies.xml +++ b/Clover/app/src/main/res/layout/post_replies.xml @@ -50,7 +50,7 @@ along with this program. If not, see . android:drawableLeft="@drawable/ic_action_back" android:drawablePadding="8dp" android:gravity="center_vertical" - android:text="@string/post_replies_back"/> + android:text="@string/back"/> . android:drawableLeft="@drawable/ic_action_done" android:drawablePadding="8dp" android:gravity="center_vertical" - android:text="@string/post_replies_close"/> + android:text="@string/close"/> diff --git a/Clover/app/src/main/res/values/strings.xml b/Clover/app/src/main/res/values/strings.xml index 1a005065..13e236d5 100644 --- a/Clover/app/src/main/res/values/strings.xml +++ b/Clover/app/src/main/res/values/strings.xml @@ -81,8 +81,6 @@ along with this program. If not, see . replies image images - Back - Close Info Quick reply @@ -123,6 +121,8 @@ along with this program. If not, see . Error deleting post Only delete the image + Choose + Edit boards Add or remove boards Thread watcher @@ -153,6 +153,8 @@ along with this program. If not, see . pages + Image save folder + Save original filename Ask before opening links Start playing videos immediately Auto refresh threads diff --git a/Clover/app/src/main/res/xml/preference.xml b/Clover/app/src/main/res/xml/preference.xml index 32c97291..6696e4fa 100644 --- a/Clover/app/src/main/res/xml/preference.xml +++ b/Clover/app/src/main/res/xml/preference.xml @@ -1,5 +1,4 @@ - - + + + + + android:title="@string/preference_force_phone_layout" /> + android:title="@string/preference_anonymize" /> + android:title="@string/preference_anonymize_ids" />