From eea78302ab9c39e03e915a9124b80c61996c2c05 Mon Sep 17 00:00:00 2001 From: FineFindus Date: Wed, 8 May 2024 22:26:26 +0200 Subject: [PATCH] feat(settings): implement settings import --- .../settings/SettingsAboutAppFragment.java | 112 +++++++++++++++++- .../ic_fluent_arrow_import_24_filled.xml | 9 ++ mastodon/src/main/res/values/strings_mo.xml | 5 + 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 mastodon/src/main/res/drawable/ic_fluent_arrow_import_24_filled.xml diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java index 843d97ca1..e58041921 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/settings/SettingsAboutAppFragment.java @@ -25,6 +25,7 @@ import com.google.gson.ToNumberPolicy; import org.joinmastodon.android.BuildConfig; import org.joinmastodon.android.GlobalUserPreferences; +import org.joinmastodon.android.MastodonApp; import org.joinmastodon.android.R; import org.joinmastodon.android.api.MastodonAPIController; import org.joinmastodon.android.api.session.AccountSession; @@ -61,6 +62,7 @@ import me.grishka.appkit.utils.V; public class SettingsAboutAppFragment extends BaseSettingsFragment implements HasAccountID{ private static final String TAG="SettingsAboutAppFragment"; + private static final int IMPORT_RESULT=314; private ListItem mediaCacheItem, copyCrashLogItem; private CheckableListItem enablePreReleasesItem; private AccountSession session; @@ -68,7 +70,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment impleme private File crashLogFile=new File(MastodonApp.context.getFilesDir(), "crash.log"); // MOSHIDON - private ListItem clearRecentEmojisItem, exportItem; + private ListItem clearRecentEmojisItem, exportItem, importItem; @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -84,6 +86,7 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment impleme new ListItem<>(R.string.settings_tos, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), "https://"+session.domain+"/terms")), new ListItem<>(R.string.settings_privacy_policy, 0, R.drawable.ic_fluent_open_24_regular, i->UiUtils.launchWebBrowser(getActivity(), getString(R.string.privacy_policy_url)), 0, true), exportItem=new ListItem<>(R.string.export_settings_title, R.string.export_settings_summary, R.drawable.ic_fluent_arrow_export_24_filled, this::onExportClick), + importItem=new ListItem<>(R.string.import_settings_title, R.string.import_settings_summary, R.drawable.ic_fluent_arrow_import_24_filled, this::onImportClick, 0, true), clearRecentEmojisItem=new ListItem<>(R.string.mo_clear_recent_emoji, 0, this::onClearRecentEmojisClick), mediaCacheItem=new ListItem<>(R.string.settings_clear_cache, 0, this::onClearMediaCacheClick), new ListItem<>(getString(R.string.sk_settings_clear_timeline_cache), session.domain, this::onClearTimelineCacheClick), @@ -194,6 +197,113 @@ public class SettingsAboutAppFragment extends BaseSettingsFragment impleme } } + private void onImportClick(ListItem item){ + new M3AlertDialogBuilder(getContext()) + .setTitle(R.string.import_settings_confirm) + .setIcon(R.drawable.ic_fluent_warning_24_regular) + .setMessage(R.string.import_settings_confirm_body) + .setPositiveButton(R.string.ok, (dialogInterface, i) -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/json"); + startActivityForResult(intent, IMPORT_RESULT); + }) + .setNegativeButton(R.string.cancel, null) + .show(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data){ + if(requestCode==IMPORT_RESULT && resultCode==Activity.RESULT_OK){ + Uri uri=data.getData(); + if(uri==null){ + return; + } + try{ + InputStream inputStream=getContext().getContentResolver().openInputStream(uri); + if(inputStream==null) + return; + BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder stringBuilder=new StringBuilder(); + String line; + while((line=reader.readLine())!=null){ + stringBuilder.append(line); + } + inputStream.close(); + String jsonString=stringBuilder.toString(); + + Gson gson=new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE).create(); + JsonObject jsonObject=JsonParser.parseString(jsonString).getAsJsonObject(); + + //check if json has required attributes + if(!(jsonObject.has("versionName") && jsonObject.has("versionCode") && jsonObject.has("GlobalUserPreferences"))){ + Toast.makeText(getContext(), getContext().getString(R.string.import_settings_failed), Toast.LENGTH_SHORT).show(); + return; + } + String versionName=jsonObject.get("versionName").getAsString(); + int versionCode=jsonObject.get("versionCode").getAsInt(); + Log.i(TAG, "onActivityResult: Reading exported settings ("+versionName+" "+versionCode+")"); + + // retrieve GlobalUserPreferences + Map jsonGlobalPrefs=gson.fromJson(jsonObject.getAsJsonObject("GlobalUserPreferences"), Map.class); + SharedPreferences.Editor globalPrefsEditor=GlobalUserPreferences.getPrefs().edit(); + for(String key : jsonGlobalPrefs.keySet()){ + Object value=jsonGlobalPrefs.get(key); + if(value==null) + continue; + Log.e(TAG, "onActivityResult: " + key + ":" + value); + savePrefValue(globalPrefsEditor, key, value); + } + + // retrieve LocalPreferences for all logged in accounts + //TODO: maybe show a dialog for which accounts to import? + for(AccountSession accountSession : AccountSessionManager.getInstance().getLoggedInAccounts()){ + if(!jsonObject.has(accountSession.self.id)) + continue; + Map prefs=gson.fromJson(jsonObject.getAsJsonObject(accountSession.self.id), Map.class); + + SharedPreferences.Editor prefEditor=accountSession.getRawLocalPreferences().edit(); + for(String key : prefs.keySet()){ + Object value=prefs.get(key); + if(value==null) + continue; + savePrefValue(prefEditor, key, value); + } + } + + // restart app to apply new preferences + // https://stackoverflow.com/a/46848226 + PackageManager packageManager=getContext().getPackageManager(); + Intent intent=packageManager.getLaunchIntentForPackage(getContext().getPackageName()); + ComponentName componentName=intent.getComponent(); + Intent mainIntent=Intent.makeRestartActivityTask(componentName); + // Required for API 34 and later + // Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents + mainIntent.setPackage(getContext().getPackageName()); + getContext().startActivity(mainIntent); + Runtime.getRuntime().exit(0); + }catch(IOException e){ + Log.w(TAG, e); + Toast.makeText(getContext(), getContext().getString(R.string.import_settings_failed), Toast.LENGTH_SHORT).show(); + } + } + } + + private void savePrefValue(SharedPreferences.Editor editor, String key, Object value) { + if(value.getClass().equals(Boolean.class)) + editor.putBoolean(key, (Boolean) value); + // gson parses all numbers either long (for int) or double (the rest) + else if(value.getClass().equals(Long.class)) + editor.putInt(key, ((Long) value).intValue()); + else if(value.getClass().equals(Double.class)) + editor.putFloat(key, ((Double) value).floatValue()); + else + editor.putString(key, String.valueOf(value)); + //explicitly immediately since the app will restarted soon after + // and it may not have the time to write the values in the background + editor.commit(); + } + private void updateMediaCacheItem(){ long size=ImageCache.getInstance(getActivity()).getDiskCache().size(); mediaCacheItem.subtitle=UiUtils.formatFileSize(getActivity(), size, false); diff --git a/mastodon/src/main/res/drawable/ic_fluent_arrow_import_24_filled.xml b/mastodon/src/main/res/drawable/ic_fluent_arrow_import_24_filled.xml new file mode 100644 index 000000000..267c0b811 --- /dev/null +++ b/mastodon/src/main/res/drawable/ic_fluent_arrow_import_24_filled.xml @@ -0,0 +1,9 @@ + + + diff --git a/mastodon/src/main/res/values/strings_mo.xml b/mastodon/src/main/res/values/strings_mo.xml index 2d2046da9..78dce7e6f 100644 --- a/mastodon/src/main/res/values/strings_mo.xml +++ b/mastodon/src/main/res/values/strings_mo.xml @@ -120,8 +120,13 @@ Blocked accounts Hide notifications from this user? + Confirm to import settings? + All current settings and timelines will be overwritten! This action cannot be undone. + Failed to import settings Export Settings Failed to export settings Export settings Export all logged-in accounts\' settings and timelines + Import settings + Import previously exported settings and timelines \ No newline at end of file