From 52ccfdb86043398437a483f5b20102ec2033678a Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 18 Dec 2022 10:03:29 +0100 Subject: [PATCH] Export/Import all data --- .../android/activities/LoginActivity.java | 20 +- .../app/fedilab/android/helper/Helper.java | 50 ++- .../android/helper/SettingsStorage.java | 134 -------- .../app/fedilab/android/helper/ZipHelper.java | 294 ++++++++++++++++++ .../ui/fragment/login/FragmentLoginMain.java | 74 ++++- .../settings/FragmentSettingsCategories.java | 35 ++- app/src/main/res/menu/main_login.xml | 22 +- app/src/main/res/values/strings.xml | 1 + 8 files changed, 456 insertions(+), 174 deletions(-) delete mode 100644 app/src/main/java/app/fedilab/android/helper/SettingsStorage.java create mode 100644 app/src/main/java/app/fedilab/android/helper/ZipHelper.java diff --git a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java index 4b91601b..0262a836 100644 --- a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java @@ -54,9 +54,9 @@ public class LoginActivity extends BaseActivity { public static Account.API apiLogin; public static String currentInstanceLogin, client_idLogin, client_secretLogin, softwareLogin; - private final int PICK_IMPORT = 5557; public static boolean requestedAdmin; + @SuppressLint("ApplySharedPref") public void proceedLogin(Activity activity, Account account) { new Thread(() -> { @@ -154,6 +154,8 @@ public class LoginActivity extends BaseActivity { //The activity handles a redirect URI, it will extract token code and will proceed to authentication //That happens when the user wants to use an external browser manageItent(getIntent()); + + } @@ -176,7 +178,6 @@ public class LoginActivity extends BaseActivity { // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); - if (id == R.id.action_proxy) { Intent intent = new Intent(LoginActivity.this, ProxyActivity.class); startActivity(intent); @@ -188,20 +189,5 @@ public class LoginActivity extends BaseActivity { return super.onOptionsItemSelected(item); } - @Override - protected void onActivityResult(int requestCode, int resultCode, - Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { - if (data == null || data.getData() == null) { - Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); - return; - } - // String filename = Helper.getFilePathFromURI(LoginActivity.this, data.getData()); - // Sqlite.importDB(LoginActivity.this, filename); - } else { - Toasty.error(LoginActivity.this, getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); - } - } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 3b985591..fe70d4fe 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -312,7 +312,7 @@ public class Helper { public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; public static final String TEMP_MEDIA_DIRECTORY = "TEMP_MEDIA_DIRECTORY"; - + public static final String TEMP_EXPORT_DATA = "TEMP_EXPORT_DATA"; public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84; public static final int EXTERNAL_STORAGE_REQUEST_CODE_MEDIA_SAVE = 85; @@ -1276,6 +1276,50 @@ public class Helper { }).start(); } + + public static void createFileFromUri(Context context, Uri uri, OnFileCopied callBack) { + new Thread(() -> { + InputStream selectedFileInputStream; + File file = null; + try { + String uriFullPath = uri.getPath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; + } + final String fileName = Helper.dateFileToString(context, new Date()) + ".zip"; + selectedFileInputStream = context.getContentResolver().openInputStream(uri); + if (selectedFileInputStream != null) { + final File certCacheDir = new File(context.getCacheDir(), TEMP_EXPORT_DATA); + boolean isCertCacheDirExists = certCacheDir.exists(); + if (!isCertCacheDirExists) { + isCertCacheDirExists = certCacheDir.mkdirs(); + } + if (isCertCacheDirExists) { + String filePath = certCacheDir.getAbsolutePath() + "/" + fileName; + file = new File(filePath); + OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); + byte[] buffer = new byte[1024]; + int length; + while ((length = selectedFileInputStream.read(buffer)) > 0) { + selectedFileOutPutStream.write(buffer, 0, length); + } + selectedFileOutPutStream.flush(); + selectedFileOutPutStream.close(); + } + selectedFileInputStream.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + File finalFile = file; + Runnable myRunnable = () -> callBack.onFileCopied(finalFile); + mainHandler.post(myRunnable); + }).start(); + } + public static void createAttachmentFromPAth(String path, OnAttachmentCopied callBack) { new Thread(() -> { Attachment attachment = new Attachment(); @@ -1954,6 +1998,10 @@ public class Helper { void onAttachmentCopied(Attachment attachment); } + public interface OnFileCopied { + void onFileCopied(File file); + } + public static void addMutedAccount(app.fedilab.android.client.entities.api.Account target) { if (MainActivity.filteredAccounts == null) { MainActivity.filteredAccounts = new ArrayList<>(); diff --git a/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java b/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java deleted file mode 100644 index 86a74a23..00000000 --- a/app/src/main/java/app/fedilab/android/helper/SettingsStorage.java +++ /dev/null @@ -1,134 +0,0 @@ -package app.fedilab.android.helper; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import static app.fedilab.android.BaseMainActivity.currentAccount; -import static app.fedilab.android.helper.LogoHelper.getMainLogo; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.BitmapFactory; -import android.net.Uri; -import android.os.Environment; - -import androidx.preference.PreferenceManager; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Date; -import java.util.Map; - -import app.fedilab.android.R; - - -//From https://stackoverflow.com/a/10864463 - -public class SettingsStorage { - - - public static boolean saveSharedPreferencesToFile(Context context) { - boolean res = false; - ObjectOutputStream output = null; - String fileName = "Fedilab_settings_export_" + Helper.dateFileToString(context, new Date()) + ".txt"; - String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - String fullPath = filePath + "/" + fileName; - File dst = new File(fullPath); - try { - output = new ObjectOutputStream(new FileOutputStream(dst)); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - output.writeObject(sharedpreferences.getAll()); - res = true; - String message = context.getString(R.string.data_export_settings_success); - Intent intentOpen = new Intent(); - intentOpen.setAction(android.content.Intent.ACTION_VIEW); - Uri uri = Uri.parse("file://" + fullPath); - intentOpen.setDataAndType(uri, "text/txt"); - String title = context.getString(R.string.data_export_settings); - Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), - getMainLogo(context)), Helper.NotifType.BACKUP, title, message); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - if (output != null) { - output.flush(); - output.close(); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return res; - } - - @SuppressLint("ApplySharedPref") - @SuppressWarnings({"unchecked", "UnnecessaryUnboxing"}) - public static boolean loadSharedPreferencesFromFile(Context context, Uri srcUri) { - boolean res = false; - ObjectInputStream input = null; - try { - input = new ObjectInputStream(context.getContentResolver().openInputStream(srcUri)); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - SharedPreferences.Editor prefEdit = sharedpreferences.edit(); - prefEdit.clear(); - Map entries = (Map) input.readObject(); - for (Map.Entry entry : entries.entrySet()) { - Object v = entry.getValue(); - String key = entry.getKey(); - //We skip some values - if (key.compareTo(Helper.PREF_USER_ID) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_INSTANCE) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_USER_INSTANCE) == 0) { - continue; - } - if (key.compareTo(Helper.PREF_USER_TOKEN) == 0) { - continue; - } - if (v instanceof Boolean) - prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); - else if (v instanceof Float) - prefEdit.putFloat(key, ((Float) v).floatValue()); - else if (v instanceof Integer) - prefEdit.putInt(key, ((Integer) v).intValue()); - else if (v instanceof Long) - prefEdit.putLong(key, ((Long) v).longValue()); - else if (v instanceof String) - prefEdit.putString(key, ((String) v)); - } - prefEdit.commit(); - res = true; - } catch (IOException | ClassNotFoundException e) { - e.printStackTrace(); - } finally { - try { - if (input != null) { - input.close(); - } - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return res; - } -} diff --git a/app/src/main/java/app/fedilab/android/helper/ZipHelper.java b/app/src/main/java/app/fedilab/android/helper/ZipHelper.java new file mode 100644 index 00000000..e9873b94 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/ZipHelper.java @@ -0,0 +1,294 @@ +package app.fedilab.android.helper; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + + +import static app.fedilab.android.BaseMainActivity.currentAccount; +import static app.fedilab.android.helper.LogoHelper.getMainLogo; +import static app.fedilab.android.sqlite.Sqlite.DB_NAME; +import static app.fedilab.android.sqlite.Sqlite.db; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; + +import androidx.preference.PreferenceManager; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.channels.FileChannel; +import java.util.Date; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import app.fedilab.android.R; +import es.dmoral.toasty.Toasty; + + +public class ZipHelper { + + final static int BUFFER_SIZE = 2048; + + public static void exportData(Context context) throws IOException { + String suffix = Helper.dateFileToString(context, new Date()); + String fileName = "Fedilab_data_export_" + suffix + ".zip"; + String filePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); + String zipFile = filePath + "/" + fileName; + BufferedInputStream origin; + try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)))) { + byte[] data = new byte[BUFFER_SIZE]; + String settingsPath = storeSettings(context, suffix); + if (settingsPath != null) { + FileInputStream fi = new FileInputStream(settingsPath); + origin = new BufferedInputStream(fi, BUFFER_SIZE); + try { + ZipEntry entry = new ZipEntry(settingsPath.substring(settingsPath.lastIndexOf("/") + 1)); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { + out.write(data, 0, count); + } + } finally { + origin.close(); + } + //noinspection ResultOfMethodCallIgnored + new File(settingsPath).delete(); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + return; + } + String dbPath = exportDB(context, suffix); + if (dbPath != null) { + FileInputStream fi = new FileInputStream(dbPath); + origin = new BufferedInputStream(fi, BUFFER_SIZE); + try { + ZipEntry entry = new ZipEntry(dbPath.substring(dbPath.lastIndexOf("/") + 1)); + out.putNextEntry(entry); + int count; + while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) { + out.write(data, 0, count); + } + } finally { + origin.close(); + } + //noinspection ResultOfMethodCallIgnored + new File(dbPath).delete(); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + return; + } + String message = context.getString(R.string.data_export_settings_success); + Intent intentOpen = new Intent(); + intentOpen.setAction(android.content.Intent.ACTION_VIEW); + Uri uri = Uri.parse("file://" + zipFile); + intentOpen.setDataAndType(uri, "application/zip"); + String title = context.getString(R.string.data_export_settings); + Helper.notify_user(context, currentAccount, intentOpen, BitmapFactory.decodeResource(context.getResources(), + getMainLogo(context)), Helper.NotifType.BACKUP, title, message); + } + } + + + @SuppressLint("UnspecifiedImmutableFlag") + public static void importData(Context context, File file) { + new Thread(() -> { + try { + int size; + byte[] buffer = new byte[BUFFER_SIZE]; + + String uriFullPath = file.getAbsolutePath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; + } + fullPath = fullPath.replace(".zip", ""); + File f = new File(fullPath); + if (!f.isDirectory()) { + //noinspection ResultOfMethodCallIgnored + f.mkdirs(); + } + boolean successful = true; + try (ZipInputStream zin = new ZipInputStream(new FileInputStream(fullPath + ".zip"))) { + ZipEntry ze; + while ((ze = zin.getNextEntry()) != null) { + if (!successful) { + break; + } + String path = fullPath + ze.getName(); + File unzipFile = new File(path); + FileOutputStream out = new FileOutputStream(unzipFile, false); + BufferedOutputStream fout = new BufferedOutputStream(out, BUFFER_SIZE); + try { + while ((size = zin.read(buffer, 0, BUFFER_SIZE)) != -1) { + fout.write(buffer, 0, size); + } + + zin.closeEntry(); + } finally { + fout.flush(); + fout.close(); + } + if (ze.getName().contains("settings")) { + successful = restoreSettings(context, Uri.fromFile(new File(path))); + } else if (ze.getName().contains("database")) { + successful = importDB(context, path); + } else { + break; + } + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + boolean finalSuccessful = successful; + Runnable myRunnable = () -> { + if (finalSuccessful) { + Helper.restart(context); + } else { + Toasty.error(context, context.getString(R.string.toast_error), Toast.LENGTH_LONG).show(); + } + }; + mainHandler.post(myRunnable); + + } catch (Exception e) { + e.printStackTrace(); + } + }).start(); + + } + + private static String storeSettings(Context context, String suffix) { + boolean res = false; + ObjectOutputStream output = null; + String fileName = "Fedilab_settings_export_" + suffix + ".fedilab"; + String filePath = context.getCacheDir().getAbsolutePath(); + String fullPath = filePath + "/" + fileName; + File dst = new File(fullPath); + try { + output = new ObjectOutputStream(new FileOutputStream(dst)); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + output.writeObject(sharedpreferences.getAll()); + res = true; + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (output != null) { + output.flush(); + output.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return res ? fullPath : null; + } + + @SuppressLint("ApplySharedPref") + @SuppressWarnings("UnnecessaryUnboxing") + private static boolean restoreSettings(Context context, Uri srcUri) { + boolean res = false; + ObjectInputStream input = null; + try { + input = new ObjectInputStream(context.getContentResolver().openInputStream(srcUri)); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); + SharedPreferences.Editor prefEdit = sharedpreferences.edit(); + prefEdit.clear(); + //noinspection unchecked + Map entries = (Map) input.readObject(); + for (Map.Entry entry : entries.entrySet()) { + Object v = entry.getValue(); + String key = entry.getKey(); + if (v instanceof Boolean) + prefEdit.putBoolean(key, ((Boolean) v).booleanValue()); + else if (v instanceof Float) + prefEdit.putFloat(key, ((Float) v).floatValue()); + else if (v instanceof Integer) + prefEdit.putInt(key, ((Integer) v).intValue()); + else if (v instanceof Long) + prefEdit.putLong(key, ((Long) v).longValue()); + else if (v instanceof String) + prefEdit.putString(key, ((String) v)); + } + + prefEdit.commit(); + res = true; + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } finally { + try { + if (input != null) { + input.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + return res; + } + + + private static String exportDB(Context context, String suffix) { + try { + String fileName = "Fedilab_database_export_" + suffix + ".fedilab"; + String filePath = context.getCacheDir().getAbsolutePath(); + String fullPath = filePath + "/" + fileName; + File dbSource = context.getDatabasePath(DB_NAME); + File dbDest = new File(fullPath); + FileChannel src = new FileInputStream(dbSource).getChannel(); + FileChannel dst = new FileOutputStream(dbDest).getChannel(); + dst.transferFrom(src, 0, src.size()); + src.close(); + dst.close(); + return fullPath; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static boolean importDB(Context context, String backupDBPath) { + try { + if (db != null) { + db.close(); + } + File dbDest = context.getDatabasePath(DB_NAME); + File dbSource = new File(backupDBPath); + FileChannel src = new FileInputStream(dbSource).getChannel(); + FileChannel dst = new FileOutputStream(dbDest).getChannel(); + dst.transferFrom(src, 0, src.size()); + src.close(); + dst.close(); + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java index d8eadd7f..f4b41ef9 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.login; * see . */ +import static android.app.Activity.RESULT_OK; import static app.fedilab.android.activities.LoginActivity.apiLogin; import static app.fedilab.android.activities.LoginActivity.client_idLogin; import static app.fedilab.android.activities.LoginActivity.client_secretLogin; @@ -22,8 +23,9 @@ import static app.fedilab.android.activities.LoginActivity.currentInstanceLogin; import static app.fedilab.android.activities.LoginActivity.requestedAdmin; import static app.fedilab.android.activities.LoginActivity.softwareLogin; +import android.Manifest; import android.content.Intent; -import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.text.Editable; @@ -36,11 +38,13 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.widget.PopupMenu; +import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.preference.PreferenceManager; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; @@ -55,6 +59,7 @@ import app.fedilab.android.client.entities.app.InstanceSocial; import app.fedilab.android.databinding.FragmentLoginMainBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.helper.ZipHelper; import app.fedilab.android.viewmodel.mastodon.AppsVM; import app.fedilab.android.viewmodel.mastodon.InstanceSocialVM; import app.fedilab.android.viewmodel.mastodon.NodeInfoVM; @@ -65,7 +70,9 @@ public class FragmentLoginMain extends Fragment { private FragmentLoginMainBinding binding; private boolean searchInstanceRunning = false; private String oldSearch; - + private static final int REQUEST_CODE = 5412; + private final int PICK_IMPORT = 5557; + private ActivityResultLauncher permissionLauncher; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -76,6 +83,24 @@ public class FragmentLoginMain extends Fragment { InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); binding.menuIcon.setOnClickListener(this::showMenu); binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); + + permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); + openFileIntent.setType("application/zip"); + String[] mimeTypes = new String[]{"application/zip"}; + openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + //noinspection deprecation + startActivityForResult( + Intent.createChooser( + openFileIntent, + getString(R.string.load_settings)), PICK_IMPORT); + } else { + ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); + } + }); + binding.loginInstance.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -184,7 +209,6 @@ public class FragmentLoginMain extends Fragment { MenuInflater menuInflater = popupMenu.getMenuInflater(); menuInflater.inflate(R.menu.main_login, popupMenu.getMenu()); MenuItem adminTabItem = popupMenu.getMenu().findItem(R.id.action_request_admin); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); adminTabItem.setChecked(requestedAdmin); popupMenu.setOnMenuItemClickListener(item -> { int itemId = item.getItemId(); @@ -208,6 +232,8 @@ public class FragmentLoginMain extends Fragment { return false; } }); + } else if (itemId == R.id.action_import_data) { + permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE); } return false; }); @@ -258,4 +284,44 @@ public class FragmentLoginMain extends Fragment { }); } + + + @SuppressWarnings("deprecation") + @Override + public void onActivityResult(int requestCode, int resultCode, + Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMPORT && resultCode == RESULT_OK) { + if (data == null || data.getData() == null) { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + return; + } + Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); + } else { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + } + } + + + @SuppressWarnings("deprecation") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_CODE) { + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Intent openFileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + openFileIntent.addCategory(Intent.CATEGORY_OPENABLE); + openFileIntent.setType("application/zip"); + String[] mimeTypes = new String[]{"application/zip"}; + openFileIntent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + startActivityForResult( + Intent.createChooser( + openFileIntent, + getString(R.string.load_settings)), PICK_IMPORT); + } else { + Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java index b8be1e3c..36d5b60e 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/settings/FragmentSettingsCategories.java @@ -19,6 +19,8 @@ import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; +import android.webkit.URLUtil; +import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -30,8 +32,11 @@ import androidx.navigation.Navigation; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; +import java.io.IOException; + import app.fedilab.android.R; -import app.fedilab.android.helper.SettingsStorage; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ZipHelper; import es.dmoral.toasty.Toasty; public class FragmentSettingsCategories extends PreferenceFragmentCompat { @@ -117,7 +122,11 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { } ActivityResultLauncher permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { if (isGranted) { - SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + try { + ZipHelper.exportData(requireActivity()); + } catch (IOException e) { + e.printStackTrace(); + } } else { ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } @@ -152,12 +161,18 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { @Override public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (resultCode == Activity.RESULT_OK && requestCode == PICKUP_FILE) { - boolean result = data != null && SettingsStorage.loadSharedPreferencesFromFile(requireActivity(), data.getData()); - if (result) { - Toasty.success(requireActivity(), getString(R.string.data_import_settings_success), Toasty.LENGTH_LONG).show(); - } else { - Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); + if (data == null || data.getData() == null) { + Toasty.error(requireActivity(), getString(R.string.toot_select_file_error), Toast.LENGTH_LONG).show(); + return; + } + String uriFullPath = data.getData().getPath(); + String[] uriFullPathStr = uriFullPath.split(":"); + String fullPath = uriFullPath; + if (uriFullPathStr.length > 1) { + fullPath = uriFullPathStr[1]; } + final String fileName = URLUtil.guessFileName(fullPath, null, null); + Helper.createFileFromUri(requireActivity(), data.getData(), file -> ZipHelper.importData(requireActivity(), file)); } } @@ -167,7 +182,11 @@ public class FragmentSettingsCategories extends PreferenceFragmentCompat { if (requestCode == REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - SettingsStorage.saveSharedPreferencesToFile(requireActivity()); + try { + ZipHelper.exportData(requireActivity()); + } catch (IOException e) { + e.printStackTrace(); + } } else { Toasty.error(requireActivity(), getString(R.string.permission_missing), Toasty.LENGTH_SHORT).show(); } diff --git a/app/src/main/res/menu/main_login.xml b/app/src/main/res/menu/main_login.xml index 654c95e5..cbfb46fe 100644 --- a/app/src/main/res/menu/main_login.xml +++ b/app/src/main/res/menu/main_login.xml @@ -21,15 +21,17 @@ android:checkable="true" android:title="@string/admin_scope" app:actionViewClass="android.widget.CheckBox" /> + + + + + --> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e7d27cd..a6f5edb4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2107,4 +2107,5 @@ Add all users in muted home All accounts will be muted for the Home timeline. Mute them all + Import data \ No newline at end of file