Refactored imagesaver

Add option to select the folder to save to.
captchafix
Florens Douwes 11 years ago
parent 55e2dee09d
commit e29fecb4b9
  1. 23
      Clover/app/src/main/java/org/floens/chan/core/ChanPreferences.java
  2. 33
      Clover/app/src/main/java/org/floens/chan/ui/activity/AdvancedSettingsActivity.java
  3. 24
      Clover/app/src/main/java/org/floens/chan/ui/activity/ImageViewActivity.java
  4. 196
      Clover/app/src/main/java/org/floens/chan/ui/fragment/FolderPickFragment.java
  5. 4
      Clover/app/src/main/java/org/floens/chan/ui/fragment/ImageViewFragment.java
  6. 1
      Clover/app/src/main/java/org/floens/chan/ui/fragment/SettingsFragment.java
  7. 231
      Clover/app/src/main/java/org/floens/chan/utils/ImageSaver.java
  8. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_action_cancel.png
  9. BIN
      Clover/app/src/main/res/drawable-hdpi/ic_action_cancel_dark.png
  10. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_action_cancel.png
  11. BIN
      Clover/app/src/main/res/drawable-mdpi/ic_action_cancel_dark.png
  12. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel.png
  13. BIN
      Clover/app/src/main/res/drawable-xhdpi/ic_action_cancel_dark.png
  14. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel.png
  15. BIN
      Clover/app/src/main/res/drawable-xxhdpi/ic_action_cancel_dark.png
  16. 95
      Clover/app/src/main/res/layout/folder_pick.xml
  17. 4
      Clover/app/src/main/res/layout/post_replies.xml
  18. 6
      Clover/app/src/main/res/values/strings.xml
  19. 33
      Clover/app/src/main/res/xml/preference.xml
  20. 20
      Clover/app/src/main/res/xml/preference_advanced.xml

@ -18,9 +18,12 @@
package org.floens.chan.core; package org.floens.chan.core;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Environment;
import org.floens.chan.ChanApplication; import org.floens.chan.ChanApplication;
import java.io.File;
public class ChanPreferences { public class ChanPreferences {
private static SharedPreferences p() { private static SharedPreferences p() {
return ChanApplication.getPreferences(); return ChanApplication.getPreferences();
@ -46,8 +49,24 @@ public class ChanPreferences {
p().edit().putBoolean("preference_developer", developer).commit(); p().edit().putBoolean("preference_developer", developer).commit();
} }
public static String getImageSaveDirectory() { public static File getImageSaveDirectory() {
return "Clover"; 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() { public static boolean getWatchEnabled() {

@ -24,12 +24,19 @@ import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import org.floens.chan.R; 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 { public class AdvancedSettingsActivity extends PreferenceActivity {
@Override @Override
protected void onCreate(final Bundle savedInstanceState) { protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this);
getFragmentManager().beginTransaction().replace(android.R.id.content, new AdvancedSettingsFragment()).commit(); getFragmentManager().beginTransaction().replace(android.R.id.content, new AdvancedSettingsFragment()).commit();
} }
@ -79,6 +86,32 @@ public class AdvancedSettingsActivity extends PreferenceActivity {
return true; 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) { private void updateSummary(ListPreference list, String value) {

@ -19,14 +19,15 @@ package org.floens.chan.ui.activity;
import android.app.ActionBar; import android.app.ActionBar;
import android.app.Activity; import android.app.Activity;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.Window; import android.view.Window;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.ChanPreferences;
import org.floens.chan.core.model.Post; import org.floens.chan.core.model.Post;
import org.floens.chan.ui.adapter.ImageViewAdapter; import org.floens.chan.ui.adapter.ImageViewAdapter;
import org.floens.chan.ui.adapter.PostAdapter; import org.floens.chan.ui.adapter.PostAdapter;
@ -166,20 +167,21 @@ public class ImageViewActivity extends Activity implements ViewPager.OnPageChang
finish(); finish();
return true; return true;
} else if (item.getItemId() == R.id.action_download_album) { } else if (item.getItemId() == R.id.action_download_album) {
List<Uri> uris = new ArrayList<>(); List<ImageSaver.DownloadPair> list = new ArrayList<>();
Post aPost = null;
String name = "downloaded";
String filename;
for (Post post : adapter.getList()) { for (Post post : adapter.getList()) {
uris.add(Uri.parse(post.imageUrl)); filename = (ChanPreferences.getImageSaveOriginalFilename() ? post.tim : post.filename) + "." + post.ext;
aPost = post; list.add(new ImageSaver.DownloadPair(post.imageUrl, filename));
name = post.board + "_" + post.resto;
} }
if (uris.size() > 0) { if (list.size() > 0) {
String name = "downloaded"; if (!TextUtils.isEmpty(name)) {
if (aPost != null) { ImageSaver.saveAll(this, name, list);
name = aPost.board + "_" + aPost.resto;
} }
ImageSaver.saveAll(this, name, uris);
} }
return true; return true;

@ -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 <http://www.gnu.org/licenses/>.
*/
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<String> adapter;
boolean hasParent = false;
private FolderPickListener listener;
private File currentPath;
private List<File> 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<String>(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<File> 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<File> 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);
}
}

@ -222,9 +222,9 @@ public class ImageViewFragment extends Fragment implements ThumbnailImageViewCal
} else if (item.getItemId() == R.id.action_open_browser) { } else if (item.getItemId() == R.id.action_open_browser) {
Utils.openLink(context, post.imageUrl); Utils.openLink(context, post.imageUrl);
} else if (item.getItemId() == R.id.action_image_save) { } 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) { } 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);
} }
} }

@ -32,7 +32,6 @@ import android.widget.Toast;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.ChanPreferences; import org.floens.chan.core.ChanPreferences;
import org.floens.chan.ui.activity.AboutActivity; 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.ui.activity.SettingsActivity;
import org.floens.chan.utils.ThemeHelper; import org.floens.chan.utils.ThemeHelper;

@ -38,45 +38,57 @@ import org.floens.chan.core.net.ByteArrayRequest;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ImageSaver { public class ImageSaver {
private static final String TAG = "ImageSaver"; private static final String TAG = "ImageSaver";
public static void saveAll(Context context, String folderName, final List<Uri> list) { public static void saveAll(final Context context, String folderName, final List<DownloadPair> list) {
final DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); final DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
String folderPath = ChanPreferences.getImageSaveDirectory() + File.separator + folderName; folderName = filterName(folderName);
File folder = Environment.getExternalStoragePublicDirectory(folderPath);
int nextFileNameNumber = 0; final File subFolder = findUnused(new File(ChanPreferences.getImageSaveDirectory() + File.separator + folderName), true);
while (folder.exists() || folder.isFile()) {
folderPath = ChanPreferences.getImageSaveDirectory() + File.separator + folderName + "_" String text = context.getString(R.string.download_confirm)
+ Integer.toString(nextFileNameNumber++); .replace("COUNT", Integer.toString(list.size()))
folder = Environment.getExternalStoragePublicDirectory(folderPath); .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) new AlertDialog.Builder(context).setMessage(text).setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { 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<Pair> 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() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
finalFolder.mkdirs(); for (Pair pair : files) {
for (Uri uri : list) {
DownloadManager.Request request; DownloadManager.Request request;
try { try {
request = new DownloadManager.Request(uri); request = new DownloadManager.Request(pair.uri);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
continue; continue;
} }
request.setDestinationInExternalPublicDir(finalFolderPath, uri.getLastPathSegment()); request.setDestinationUri(Uri.fromFile(pair.file));
request.setVisibleInDownloadsUi(false); request.setVisibleInDownloadsUi(false);
request.allowScanningByMediaScanner(); request.allowScanningByMediaScanner();
@ -88,26 +100,23 @@ public class ImageSaver {
}).show(); }).show();
} }
public static void save(final Context context, String imageUrl, final String name, final String extension, public static void saveImage(final Context context, String imageUrl, String name, final String extension, final boolean share) {
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<byte[]>() { ChanApplication.getVolleyRequestQueue().add(new ByteArrayRequest(imageUrl, new Response.Listener<byte[]>() {
@Override @Override
public void onResponse(byte[] data) { public void onResponse(byte[] data) {
storeImage(context, data, name, extension, new SaveCallback() { storeImage(context, data, file, share);
@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)));
}
}
});
} }
}, new Response.ErrorListener() { }, new Response.ErrorListener() {
@Override @Override
@ -117,71 +126,81 @@ public class ImageSaver {
})); }));
} }
private static void storeImage(final Context context, byte[] data, String name, String extension, private static String filterName(String name) {
final SaveCallback callback) { return name.replaceAll("[^\\w.]", "");
String errorReason = null; }
try {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
errorReason = context.getString(R.string.image_save_not_mounted);
throw new IOException(errorReason);
}
File path = new File(Environment.getExternalStorageDirectory() + File.separator private static File findUnused(File start, boolean isDir) {
+ ChanPreferences.getImageSaveDirectory()); String base;
String extension;
if (!path.exists()) { if (isDir) {
if (!path.mkdirs()) { base = start.getAbsolutePath();
errorReason = context.getString(R.string.image_save_directory_error); extension = null;
throw new IOException(errorReason); } 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); File test;
if (savedFile == null) { if (isDir) {
errorReason = context.getString(R.string.image_save_failed); test = new File(base);
throw new IOException(errorReason); } 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, return test;
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);
}
});
}
}
);
} catch (IOException e) { private static void storeImage(final Context context, final byte[] data, final File destination, final boolean share) {
e.printStackTrace(); if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
Toast.makeText(context, R.string.image_save_not_mounted, Toast.LENGTH_LONG).show();
return;
}
if (errorReason == null) new Thread(new Runnable() {
errorReason = context.getString(R.string.image_save_failed); @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) { private static boolean saveByteArray(File destination, byte[] source) {
File destination = new File(path, name + "." + extension); boolean res = false;
int nextFileNameNumber = 0;
String newName;
while (destination.exists()) {
newName = name + "_" + Integer.toString(nextFileNameNumber++) + "." + extension;
destination = new File(path, newName);
}
FileOutputStream outputStream = null; FileOutputStream outputStream = null;
try { try {
outputStream = new FileOutputStream(destination); outputStream = new FileOutputStream(destination);
outputStream.write(source); outputStream.write(source);
outputStream.close(); outputStream.close();
res = true;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } 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 { private static class Pair {
public void onUri(String name, Uri uri); public Uri uri;
public File file;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 B

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?><!--
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/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="200dp"
android:minWidth="320dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:divider="?android:attr/dividerVertical"
android:dividerPadding="12dp"
android:orientation="horizontal"
android:showDividers="middle">
<FrameLayout
android:id="@+id/pick_back"
style="?android:actionButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/pick_back_icon"
style="?android:actionBarTabTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_cancel"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/cancel" />
</FrameLayout>
<FrameLayout
android:id="@+id/pick_ok"
style="?android:actionButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<TextView
android:id="@+id/pick_ok_icon"
style="?android:actionBarTabTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp"
android:gravity="center_vertical"
android:paddingRight="20dp"
android:text="@string/folder_pick_ok" />
</FrameLayout>
</LinearLayout>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/folder_status"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
<View
android:layout_width="fill_parent"
android:layout_height="3dp"
android:background="@android:color/darker_gray"/>
<ListView
android:id="@+id/folder_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

@ -50,7 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:drawableLeft="@drawable/ic_action_back" android:drawableLeft="@drawable/ic_action_back"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/post_replies_back"/> android:text="@string/back"/>
</FrameLayout> </FrameLayout>
<FrameLayout <FrameLayout
@ -70,7 +70,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
android:drawableLeft="@drawable/ic_action_done" android:drawableLeft="@drawable/ic_action_done"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:text="@string/post_replies_close"/> android:text="@string/close"/>
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>

@ -81,8 +81,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="multiple_replies">replies</string> <string name="multiple_replies">replies</string>
<string name="one_image">image</string> <string name="one_image">image</string>
<string name="multiple_images">images</string> <string name="multiple_images">images</string>
<string name="post_replies_back">Back</string>
<string name="post_replies_close">Close</string>
<string name="post_info">Info</string> <string name="post_info">Info</string>
<string-array name="post_options"> <string-array name="post_options">
<item>Quick reply</item> <item>Quick reply</item>
@ -123,6 +121,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<string name="delete_fail">Error deleting post</string> <string name="delete_fail">Error deleting post</string>
<string name="delete_image_only">Only delete the image</string> <string name="delete_image_only">Only delete the image</string>
<string name="folder_pick_ok">Choose</string>
<string name="preference_board_edit">Edit boards</string> <string name="preference_board_edit">Edit boards</string>
<string name="preference_board_edit_summary">Add or remove boards</string> <string name="preference_board_edit_summary">Add or remove boards</string>
<string name="preference_watch_settings">Thread watcher</string> <string name="preference_watch_settings">Thread watcher</string>
@ -153,6 +153,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<item>pages</item> <item>pages</item>
</string-array> </string-array>
<string name="preference_image_save_location">Image save folder</string>
<string name="preference_image_save_original">Save original filename</string>
<string name="preference_open_link_confirmation">Ask before opening links</string> <string name="preference_open_link_confirmation">Ask before opening links</string>
<string name="preference_autoplay">Start playing videos immediately</string> <string name="preference_autoplay">Start playing videos immediately</string>
<string name="preference_auto_refresh_thread">Auto refresh threads</string> <string name="preference_auto_refresh_thread">Auto refresh threads</string>

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
Clover - 4chan browser https://github.com/Floens/Clover/ Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens Copyright (C) 2014 Floens
@ -24,7 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:targetClass="org.floens.chan.ui.activity.BoardEditor" android:targetClass="org.floens.chan.ui.activity.BoardEditor"
android:targetPackage="org.floens.chan"/> android:targetPackage="org.floens.chan" />
</Preference> </Preference>
<Preference <Preference
android:key="watch_settings" android:key="watch_settings"
@ -32,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:targetClass="org.floens.chan.ui.activity.WatchSettingsActivity" android:targetClass="org.floens.chan.ui.activity.WatchSettingsActivity"
android:targetPackage="org.floens.chan"/> android:targetPackage="org.floens.chan" />
</Preference> </Preference>
<Preference <Preference
android:key="pass_settings" android:key="pass_settings"
@ -40,47 +39,47 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:targetClass="org.floens.chan.ui.activity.PassSettingsActivity" android:targetClass="org.floens.chan.ui.activity.PassSettingsActivity"
android:targetPackage="org.floens.chan"/> android:targetPackage="org.floens.chan" />
</Preference> </Preference>
<PreferenceCategory android:title="@string/preference_browsing"> <PreferenceCategory android:title="@string/preference_browsing">
<ListPreference <ListPreference
android:key="preference_theme"
android:defaultValue="light" android:defaultValue="light"
android:dialogTitle="@string/preference_theme" android:dialogTitle="@string/preference_theme"
android:entries="@array/preference_themes" android:entries="@array/preference_themes"
android:entryValues="@array/preference_themes_values" android:entryValues="@array/preference_themes_values"
android:title="@string/preference_theme"/> android:key="preference_theme"
android:title="@string/preference_theme" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="preference_open_link_confirmation" android:key="preference_open_link_confirmation"
android:title="@string/preference_open_link_confirmation"/> android:title="@string/preference_open_link_confirmation" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="preference_auto_refresh_thread" android:key="preference_auto_refresh_thread"
android:title="@string/preference_auto_refresh_thread"/> android:title="@string/preference_auto_refresh_thread" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="preference_image_auto_load" android:key="preference_image_auto_load"
android:title="@string/preference_image_auto_load"/> android:title="@string/preference_image_auto_load" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="preference_autoplay"
android:dependency="preference_image_auto_load" android:dependency="preference_image_auto_load"
android:title="@string/preference_autoplay"/> android:key="preference_autoplay"
android:title="@string/preference_autoplay" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/preference_posting"> <PreferenceCategory android:title="@string/preference_posting">
<EditTextPreference <EditTextPreference
android:key="preference_default_name" android:key="preference_default_name"
android:title="@string/preference_default_name"/> android:title="@string/preference_default_name" />
<EditTextPreference <EditTextPreference
android:key="preference_default_email" android:key="preference_default_email"
android:title="@string/preference_default_email"/> android:title="@string/preference_default_email" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
@ -89,17 +88,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<Preference <Preference
android:key="about_licences" android:key="about_licences"
android:summary="@string/preference_licences_summary" android:summary="@string/preference_licences_summary"
android:title="@string/preference_licenses"/> android:title="@string/preference_licenses" />
<Preference <Preference
android:key="about_version" android:key="about_version"
android:title="Chan"/> android:title="Chan" />
<Preference <Preference
android:key="about_developer" android:key="about_developer"
android:title="@string/preference_developer"> android:title="@string/preference_developer">
<intent <intent
android:action="android.intent.action.VIEW" android:action="android.intent.action.VIEW"
android:targetClass="org.floens.chan.ui.activity.DeveloperActivity" android:targetClass="org.floens.chan.ui.activity.DeveloperActivity"
android:targetPackage="org.floens.chan"/> android:targetPackage="org.floens.chan" />
</Preference> </Preference>
</PreferenceCategory> </PreferenceCategory>

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
Clover - 4chan browser https://github.com/Floens/Clover/ Clover - 4chan browser https://github.com/Floens/Clover/
Copyright (C) 2014 Floens Copyright (C) 2014 Floens
@ -17,27 +16,36 @@ 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/>.
--> -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:key="preference_image_save_location"
android:title="@string/preference_image_save_location" />
<CheckBoxPreference
android:defaultValue="false"
android:key="preference_image_save_original"
android:title="@string/preference_image_save_original" />
<ListPreference <ListPreference
android:key="preference_board_mode"
android:defaultValue="catalog" android:defaultValue="catalog"
android:dialogTitle="@string/preference_board_mode" android:dialogTitle="@string/preference_board_mode"
android:entries="@array/preference_board_modes" android:entries="@array/preference_board_modes"
android:entryValues="@array/preference_board_modes_values" android:entryValues="@array/preference_board_modes_values"
android:key="preference_board_mode"
android:title="@string/preference_board_mode" /> android:title="@string/preference_board_mode" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="preference_force_phone_layout" android:key="preference_force_phone_layout"
android:title="@string/preference_force_phone_layout"/> android:title="@string/preference_force_phone_layout" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="preference_anonymize" android:key="preference_anonymize"
android:title="@string/preference_anonymize"/> android:title="@string/preference_anonymize" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="preference_anonymize_ids" android:key="preference_anonymize_ids"
android:title="@string/preference_anonymize_ids"/> android:title="@string/preference_anonymize_ids" />
</PreferenceScreen> </PreferenceScreen>

Loading…
Cancel
Save