diff --git a/app/build.gradle b/app/build.gradle
index b2820092..5e050a47 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -170,6 +170,9 @@ dependencies {
// Crash screen
implementation 'com.melegy.redscreenofdeath:red-screen-of-death:0.1.2'
+ implementation 'net.lingala.zip4j:zip4j:2.7.0'
+ implementation 'org.apache.commons:commons-io:1.3.2'
+
/**** Builds and flavors ****/
// debugImplementation because LeakCanary should only run in debug builds.
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/asynctasks/BackupSettings.java b/app/src/main/java/ml/docilealligator/infinityforreddit/asynctasks/BackupSettings.java
new file mode 100644
index 00000000..a6fc1508
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/asynctasks/BackupSettings.java
@@ -0,0 +1,191 @@
+package ml.docilealligator.infinityforreddit.asynctasks;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Handler;
+
+import androidx.documentfile.provider.DocumentFile;
+
+import net.lingala.zip4j.ZipFile;
+import net.lingala.zip4j.model.ZipParameters;
+import net.lingala.zip4j.model.enums.EncryptionMethod;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.Executor;
+
+import ml.docilealligator.infinityforreddit.BuildConfig;
+import ml.docilealligator.infinityforreddit.R;
+import ml.docilealligator.infinityforreddit.utils.CustomThemeSharedPreferencesUtils;
+import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
+
+public class BackupSettings {
+ public static void backupSettings(Context context, Executor executor, Handler handler,
+ ContentResolver contentResolver, Uri destinationDirUri,
+ SharedPreferences defaultSharedPreferences,
+ SharedPreferences lightThemeSharedPreferences,
+ SharedPreferences darkThemeSharedPreferences,
+ SharedPreferences amoledThemeSharedPreferences,
+ SharedPreferences sortTypeSharedPreferences,
+ SharedPreferences postLayoutSharedPreferences,
+ SharedPreferences postFeedScrolledPositionSharedPreferences,
+ SharedPreferences mainActivityTabsSharedPreferences,
+ SharedPreferences nsfwAndSpoilerSharedPreferencs,
+ SharedPreferences bottomAppBarSharedPreferences,
+ SharedPreferences postHistorySharedPreferences,
+ BackupSettingsListener backupSettingsListener) {
+ executor.execute(() -> {
+ boolean res = saveSharedPreferencesToFile(context, defaultSharedPreferences,
+ SharedPreferencesUtils.DEFAULT_PREFERENCES_FILE);
+ boolean res1 = saveSharedPreferencesToFile(context, lightThemeSharedPreferences,
+ CustomThemeSharedPreferencesUtils.LIGHT_THEME_SHARED_PREFERENCES_FILE);
+ boolean res2 = saveSharedPreferencesToFile(context, darkThemeSharedPreferences,
+ CustomThemeSharedPreferencesUtils.DARK_THEME_SHARED_PREFERENCES_FILE);
+ boolean res3 = saveSharedPreferencesToFile(context, amoledThemeSharedPreferences,
+ CustomThemeSharedPreferencesUtils.AMOLED_THEME_SHARED_PREFERENCES_FILE);
+ boolean res4 = saveSharedPreferencesToFile(context, sortTypeSharedPreferences,
+ SharedPreferencesUtils.SORT_TYPE_SHARED_PREFERENCES_FILE);
+ boolean res5 = saveSharedPreferencesToFile(context, postLayoutSharedPreferences,
+ SharedPreferencesUtils.POST_LAYOUT_SHARED_PREFERENCES_FILE);
+ boolean res6 = saveSharedPreferencesToFile(context, postFeedScrolledPositionSharedPreferences,
+ SharedPreferencesUtils.FRONT_PAGE_SCROLLED_POSITION_SHARED_PREFERENCES_FILE);
+ boolean res7 = saveSharedPreferencesToFile(context, mainActivityTabsSharedPreferences,
+ SharedPreferencesUtils.MAIN_PAGE_TABS_SHARED_PREFERENCES_FILE);
+ boolean res8 = saveSharedPreferencesToFile(context, nsfwAndSpoilerSharedPreferencs,
+ SharedPreferencesUtils.NSFW_AND_SPOILER_SHARED_PREFERENCES_FILE);
+ boolean res9 = saveSharedPreferencesToFile(context, bottomAppBarSharedPreferences,
+ SharedPreferencesUtils.BOTTOM_APP_BAR_SHARED_PREFERENCES_FILE);
+ boolean res10 = saveSharedPreferencesToFile(context, postHistorySharedPreferences,
+ SharedPreferencesUtils.POST_HISTORY_SHARED_PREFERENCES_FILE);
+
+ boolean zipRes = zipAndMoveToDestinationDir(context, contentResolver, destinationDirUri);
+
+ try {
+ FileUtils.deleteDirectory(new File(context.getExternalCacheDir() + "/Backup/"));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ handler.post(() -> {
+ boolean finalResult = res && res1 && res2 && res3 && res4 && res5 && res6 && res7 && res8 && res9 && res10 && zipRes;
+ if (finalResult) {
+ backupSettingsListener.success();
+ } else {
+ if (!zipRes) {
+ backupSettingsListener.failed(context.getText(R.string.create_zip_in_destination_directory_failed).toString());
+ } else {
+ backupSettingsListener.failed(context.getText(R.string.backup_some_settings_failed).toString());
+ }
+ }
+ });
+ });
+ }
+
+ private static boolean saveSharedPreferencesToFile(Context context, SharedPreferences sharedPreferences,
+ String fileName) {
+
+ boolean result = false;
+ ObjectOutputStream output = null;
+ String backupDir = context.getExternalCacheDir() + "/Backup/" + BuildConfig.VERSION_NAME;
+ if (!new File(backupDir).exists()) {
+ new File(backupDir).mkdirs();
+ } else {
+ File backupDirFile = new File(backupDir);
+ backupDirFile.delete();
+ backupDirFile.mkdirs();
+ }
+
+ try {
+ output = new ObjectOutputStream(new FileOutputStream(new File(backupDir + "/" + fileName + ".txt")));
+ output.writeObject(sharedPreferences.getAll());
+
+ result = true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (output != null) {
+ output.flush();
+ }
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ return result;
+ }
+
+ private static boolean zipAndMoveToDestinationDir(Context context, ContentResolver contentResolver, Uri destinationDirUri) {
+ OutputStream outputStream = null;
+ boolean result = false;
+ try {
+ String time = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(System.currentTimeMillis()));
+ String fileName = "Infinity_For_Reddit_Settings_Backup_v" + BuildConfig.VERSION_NAME + "-" + time + ".zip";
+ String filePath = context.getExternalCacheDir() + "/Backup/" + fileName;
+ ZipFile zip = new ZipFile(filePath, "123321".toCharArray());
+ ZipParameters zipParameters = new ZipParameters();
+ zipParameters.setEncryptFiles(true);
+ zipParameters.setEncryptionMethod(EncryptionMethod.AES);
+ zip.addFolder(new File(context.getExternalCacheDir() + "/Backup/" + BuildConfig.VERSION_NAME + "/"), zipParameters);
+
+ DocumentFile dir = DocumentFile.fromTreeUri(context, destinationDirUri);
+ if (dir == null) {
+ return false;
+ }
+ DocumentFile checkForDuplicate = dir.findFile(fileName);
+ if (checkForDuplicate != null) {
+ checkForDuplicate.delete();
+ }
+ DocumentFile destinationFile = dir.createFile("application/zip", fileName);
+ if (destinationFile == null) {
+ return false;
+ }
+
+ outputStream = contentResolver.openOutputStream(destinationFile.getUri());
+ if (outputStream == null) {
+ return false;
+ }
+
+ byte[] fileReader = new byte[1024];
+
+ FileInputStream inputStream = new FileInputStream(filePath);
+ while (true) {
+ int read = inputStream.read(fileReader);
+
+ if (read == -1) {
+ break;
+ }
+
+ outputStream.write(fileReader, 0, read);
+ }
+ result = true;
+ } catch (IOException e) {
+ e.printStackTrace();
+
+ } finally {
+ if (outputStream != null) {
+ try {
+ outputStream.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public interface BackupSettingsListener {
+ void success();
+ void failed(String errorMessage);
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/settings/AdvancedPreferenceFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/settings/AdvancedPreferenceFragment.java
index cbb6669f..0708edd7 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/settings/AdvancedPreferenceFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/settings/AdvancedPreferenceFragment.java
@@ -2,12 +2,15 @@ package ml.docilealligator.infinityforreddit.settings;
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.preference.Preference;
@@ -25,6 +28,7 @@ import javax.inject.Named;
import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.R;
import ml.docilealligator.infinityforreddit.RedditDataRoomDatabase;
+import ml.docilealligator.infinityforreddit.asynctasks.BackupSettings;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllPostLayouts;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllReadPosts;
import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllSortTypes;
@@ -34,11 +38,15 @@ import ml.docilealligator.infinityforreddit.asynctasks.DeleteAllUsers;
import ml.docilealligator.infinityforreddit.events.RecreateActivityEvent;
import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils;
+import static android.app.Activity.RESULT_OK;
+import static android.content.Intent.ACTION_OPEN_DOCUMENT_TREE;
+
/**
* A simple {@link Fragment} subclass.
*/
public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
+ private static final int SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE = 1;
@Inject
RedditDataRoomDatabase mRedditDataRoomDatabase;
@Inject
@@ -69,6 +77,12 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
@Named("nsfw_and_spoiler")
SharedPreferences nsfwAndBlurringSharedPreferences;
@Inject
+ @Named("bottom_app_bar")
+ SharedPreferences bottomAppBarSharedPreferences;
+ @Inject
+ @Named("post_history")
+ SharedPreferences postHistorySharedPreferences;
+ @Inject
Executor executor;
private Activity activity;
@@ -87,6 +101,8 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
Preference deleteReadPostsPreference = findPreference(SharedPreferencesUtils.DELETE_READ_POSTS_IN_DATABASE);
Preference deleteAllLegacySettingsPreference = findPreference(SharedPreferencesUtils.DELETE_ALL_LEGACY_SETTINGS);
Preference resetAllSettingsPreference = findPreference(SharedPreferencesUtils.RESET_ALL_SETTINGS);
+ Preference backupSettingsPreference = findPreference(SharedPreferencesUtils.BACKUP_SETTINGS);
+ Preference restoreSettingsPreference = findPreference(SharedPreferencesUtils.RESTORE_SETTINGS);
if (deleteSubredditsPreference != null) {
deleteSubredditsPreference.setOnPreferenceClickListener(preference -> {
@@ -252,6 +268,67 @@ public class AdvancedPreferenceFragment extends PreferenceFragmentCompat {
return true;
});
}
+
+ if (backupSettingsPreference != null) {
+ backupSettingsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivityForResult(intent, SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE);
+
+ /*BackupSettings.backupSettings(activity, executor, new Handler(), activity.getContentResolver(), null,
+ mSharedPreferences, lightThemeSharedPreferences, darkThemeSharedPreferences,
+ amoledThemeSharedPreferences, mSortTypeSharedPreferences, mPostLayoutSharedPreferences,
+ postFeedScrolledPositionSharedPreferences, mainActivityTabsSharedPreferences,
+ nsfwAndBlurringSharedPreferences, bottomAppBarSharedPreferences, postHistorySharedPreferences,
+ new BackupSettings.BackupSettingsListener() {
+ @Override
+ public void success() {
+
+ }
+
+ @Override
+ public void failed() {
+
+ }
+ });*/
+ return true;
+ }
+ });
+ }
+
+ if (restoreSettingsPreference != null) {
+ restoreSettingsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ return true;
+ }
+ });
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (requestCode == SELECT_BACKUP_SETTINGS_DIRECTORY_REQUEST_CODE && resultCode == RESULT_OK) {
+ Uri uri = data.getData();
+ BackupSettings.backupSettings(activity, executor, new Handler(), activity.getContentResolver(), uri,
+ mSharedPreferences, lightThemeSharedPreferences, darkThemeSharedPreferences,
+ amoledThemeSharedPreferences, mSortTypeSharedPreferences, mPostLayoutSharedPreferences,
+ postFeedScrolledPositionSharedPreferences, mainActivityTabsSharedPreferences,
+ nsfwAndBlurringSharedPreferences, bottomAppBarSharedPreferences, postHistorySharedPreferences,
+ new BackupSettings.BackupSettingsListener() {
+ @Override
+ public void success() {
+ Toast.makeText(activity, R.string.backup_settings_success, Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public void failed(String errorMessage) {
+ Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
}
@Override
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SharedPreferencesUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SharedPreferencesUtils.java
index 5670b82f..d1942a34 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SharedPreferencesUtils.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/SharedPreferencesUtils.java
@@ -169,7 +169,10 @@ public class SharedPreferencesUtils {
public static final String HIDE_SUBREDDIT_AND_USER_PREFIX = "hide_subreddit_and_user_prefix";
public static final String HIDE_THE_NUMBER_OF_VOTES = "hide_the_number_of_votes";
public static final String HIDE_THE_NUMBER_OF_COMMENTS = "hide_the_number_of_comments";
+ public static final String BACKUP_SETTINGS = "backup_settings";
+ public static final String RESTORE_SETTINGS = "restore_settings";
+ public static final String DEFAULT_PREFERENCES_FILE = "ml.docilealligator.infinityforreddit_preferences";
public static final String MAIN_PAGE_TABS_SHARED_PREFERENCES_FILE = "ml.docilealligator.infinityforreddit.main_page_tabs";
public static final String MAIN_PAGE_TAB_COUNT = "_main_page_tab_count";
public static final String MAIN_PAGE_SHOW_TAB_NAMES = "_main_page_show_tab_names";
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 93adbf0b..795a6a0a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -541,6 +541,8 @@
Hide the Number of Votes
Hide the Number of Comments
Show Avatar on the Left in the Navigation Drawer
+ Backup Settings
+ Restore Settings
Cannot get the link
@@ -1037,4 +1039,8 @@
Start by joining a subreddit!
+ Successfully exported settings to the destination directory
+ Could not create backup zip in the destination directory
+ Could not backup some settings but others were successfully exported to the destination directory
+
diff --git a/app/src/main/res/xml/advanced_preferences.xml b/app/src/main/res/xml/advanced_preferences.xml
index 6992530d..68b74260 100644
--- a/app/src/main/res/xml/advanced_preferences.xml
+++ b/app/src/main/res/xml/advanced_preferences.xml
@@ -38,6 +38,14 @@
app:key="reset_all_settings"
app:title="@string/settings_reset_all_settings_title" />
+
+
+
+