From d1b90f29e63c631c9de266ed526bdb5e212420d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 19 Apr 2021 22:42:59 +0800 Subject: [PATCH] Export & Import settings. close #467 --- TMessagesProj/build.gradle | 4 +- .../java/org/telegram/ui/ChatActivity.java | 22 +- .../settings/NekoSettingsActivity.java | 227 +++++++++++++++++- .../tw/nekomimi/nekogram/utils/GsonUtil.kt | 6 + .../src/main/res/values/strings_nekox.xml | 3 + 5 files changed, 257 insertions(+), 5 deletions(-) diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 00917bfa3..d0c177f17 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -3,8 +3,8 @@ import cn.hutool.core.util.RuntimeUtil apply plugin: "com.android.application" apply plugin: "kotlin-android" -def verName = "7.7.2-rc01" -def verCode = 290 + 3 * 1 +def verName = "7.7.2-rc02" +def verCode = 290 + 3 * 2 if (System.getenv("DEBUG_BUILD") == "true") { verName += "-" + RuntimeUtil.execForStr("git log --pretty=format:'%h' -n 1)") diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 86410cbc5..e2a7a908b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -254,6 +254,7 @@ import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoXConfig; import tw.nekomimi.nekogram.parts.MessageTransKt; import tw.nekomimi.nekogram.parts.PollTransUpdatesKt; +import tw.nekomimi.nekogram.settings.NekoSettingsActivity; import tw.nekomimi.nekogram.transtale.Translator; import tw.nekomimi.nekogram.utils.AlertUtil; import tw.nekomimi.nekogram.utils.PGPUtil; @@ -11739,6 +11740,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not return 21; } else if ((messageObject.getDocumentName().toLowerCase().endsWith(".nekox-stickers.json"))) { return 22; + } else if ((messageObject.getDocumentName().toLowerCase().endsWith(".nekox-settings.json"))) { + return 23; } else if (!messageObject.isNewGif() && mime.endsWith("/mp4") || mime.endsWith("/png") || mime.endsWith("/jpg") || mime.endsWith("/jpeg")) { return 6; } @@ -19215,14 +19218,17 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not items.add(LocaleController.getString("ShareFile", R.string.ShareFile)); options.add(6); icons.add(R.drawable.baseline_share_24); - } else if (type == 21 || type == 22) { + } else if (type == 21 || type == 22 || type == 23) { options.add(5); if (type == 21) { items.add(LocaleController.getString("ImportProxyList", R.string.ImportProxyList)); icons.add(R.drawable.baseline_security_24); - } else { + } else if (type == 22) { items.add(LocaleController.getString("ImportStickersList", R.string.ImportStickersList)); icons.add(R.drawable.deproko_baseline_stickers_filled_24); + } else { + items.add(LocaleController.getString("ImportSettings", R.string.ImportSettings)); + icons.add(R.drawable.baseline_security_24); } items.add(LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads)); options.add(10); @@ -20316,6 +20322,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(new StickersActivity(finalLocFile)); }); + } else if (locFile.getName().toLowerCase().endsWith(".nekox-settings.json")) { + + File finalLocFile = locFile; + + NekoSettingsActivity.importSettings(getParentActivity(), finalLocFile); + } } break; @@ -22948,6 +22960,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not presentFragment(new StickersActivity(finalLocFile)); }); + + } else if (message.getDocumentName().toLowerCase().endsWith(".nekox-settings.json")) { + + File finalLocFile = locFile; + NekoSettingsActivity.importSettings(getParentActivity(), finalLocFile); + } else { boolean handled = false; if (message.canPreviewDocument()) { diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java index b5987c9b7..633378905 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java @@ -1,7 +1,13 @@ package tw.nekomimi.nekogram.settings; +import android.Manifest; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Build; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -10,12 +16,24 @@ import android.widget.FrameLayout; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.jakewharton.processphoenix.ProcessPhoenix; + +import org.json.JSONException; +import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; +import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.browser.Browser; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ActionBar.ThemeDescription; @@ -30,10 +48,21 @@ import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.DocumentSelectActivity; +import org.telegram.ui.LaunchActivity; +import java.io.File; import java.util.ArrayList; +import java.util.Date; +import java.util.Map; +import java.util.function.Function; +import kotlin.text.StringsKt; import tw.nekomimi.nekogram.ExternalGcm; +import tw.nekomimi.nekogram.utils.AlertUtil; +import tw.nekomimi.nekogram.utils.FileUtil; +import tw.nekomimi.nekogram.utils.GsonUtil; +import tw.nekomimi.nekogram.utils.ShareUtil; @SuppressLint("RtlHardcoded") public class NekoSettingsActivity extends BaseFragment { @@ -66,12 +95,21 @@ public class NekoSettingsActivity extends BaseFragment { return true; } + + private static final int backup_settings = 1; + private static final int import_settings = 2; + @SuppressLint("NewApi") @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(LocaleController.getString("NekoSettings", R.string.NekoSettings)); + ActionBarMenu menu = actionBar.createMenu(); + ActionBarMenuItem otherMenu = menu.addItem(0, R.drawable.ic_ab_other); + otherMenu.addSubItem(backup_settings, LocaleController.getString("BackupSettings", R.string.BackupSettings)); + otherMenu.addSubItem(import_settings, LocaleController.getString("ImportSettings", R.string.ImportSettings)); + if (AndroidUtilities.isTablet()) { actionBar.setOccupyStatusBar(false); } @@ -80,6 +118,35 @@ public class NekoSettingsActivity extends BaseFragment { public void onItemClick(int id) { if (id == -1) { finishFragment(); + } else if (id == backup_settings) { + backupSettings(); + } else if (id == import_settings) { + try { + if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + getParentActivity().requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 4); + return; + } + } catch (Throwable ignore) { + } + DocumentSelectActivity fragment = new DocumentSelectActivity(false); + fragment.setMaxSelectedFiles(1); + fragment.setAllowPhoto(false); + fragment.setDelegate(new DocumentSelectActivity.DocumentSelectActivityDelegate() { + @Override + public void didSelectFiles(DocumentSelectActivity activity, ArrayList files, String caption, boolean notify, int scheduleDate) { + activity.finishFragment(); + importSettings(getParentActivity(), new File(files.get(0))); + } + + @Override + public void didSelectPhotos(ArrayList photos, boolean notify, int scheduleDate) { + } + + @Override + public void startDocumentSelectActivity() { + } + }); + presentFragment(fragment); } } }); @@ -120,6 +187,164 @@ public class NekoSettingsActivity extends BaseFragment { return fragmentView; } + private void backupSettings() { + + try { + File cacheFile = new File(ApplicationLoader.applicationContext.getCacheDir(), new Date().toLocaleString() + ".nekox-settings.json"); + FileUtil.writeUtf8String(backupSettingsJson(), cacheFile); + ShareUtil.shareFile(getParentActivity(), cacheFile); + } catch (JSONException e) { + AlertUtil.showSimpleAlert(getParentActivity(), e); + } + + } + + private String backupSettingsJson() throws JSONException { + + JSONObject configJson = new JSONObject(); + + ArrayList userconfig = new ArrayList<>(); + userconfig.add("saveIncomingPhotos"); + userconfig.add("passcodeHash"); + userconfig.add("passcodeType"); + userconfig.add("passcodeHash"); + userconfig.add("autoLockIn"); + userconfig.add("useFingerprint"); + spToJSON("userconfing", configJson, userconfig::contains); + + ArrayList mainconfig = new ArrayList<>(); + mainconfig.add("saveToGallery"); + mainconfig.add("autoplayGifs"); + mainconfig.add("autoplayVideo"); + mainconfig.add("mapPreviewType"); + mainconfig.add("raiseToSpeak"); + mainconfig.add("customTabs"); + mainconfig.add("directShare"); + mainconfig.add("shuffleMusic"); + mainconfig.add("playOrderReversed"); + mainconfig.add("inappCamera"); + mainconfig.add("repeatMode"); + mainconfig.add("fontSize"); + mainconfig.add("bubbleRadius"); + mainconfig.add("ivFontSize"); + mainconfig.add("allowBigEmoji"); + mainconfig.add("streamMedia"); + mainconfig.add("saveStreamMedia"); + mainconfig.add("smoothKeyboard"); + mainconfig.add("pauseMusicOnRecord"); + mainconfig.add("streamAllVideo"); + mainconfig.add("streamMkv"); + mainconfig.add("suggestStickers"); + mainconfig.add("sortContactsByName"); + mainconfig.add("sortFilesByName"); + mainconfig.add("noSoundHintShowed"); + mainconfig.add("directShareHash"); + mainconfig.add("useThreeLinesLayout"); + mainconfig.add("archiveHidden"); + mainconfig.add("distanceSystemType"); + mainconfig.add("loopStickers"); + mainconfig.add("keepMedia"); + mainconfig.add("noStatusBar"); + mainconfig.add("lastKeepMediaCheckTime"); + mainconfig.add("searchMessagesAsListHintShows"); + mainconfig.add("searchMessagesAsListUsed"); + mainconfig.add("stickersReorderingHintUsed"); + mainconfig.add("textSelectionHintShows"); + mainconfig.add("scheduledOrNoSoundHintShows"); + mainconfig.add("lockRecordAudioVideoHint"); + mainconfig.add("disableVoiceAudioEffects"); + mainconfig.add("chatSwipeAction"); + mainconfig.add("theme"); + spToJSON("mainconfig", configJson, mainconfig::contains); + spToJSON("themeconfig", configJson, null); + spToJSON("nekoconfig", configJson, null); + + return configJson.toString(4); + } + + private static void spToJSON(String sp, JSONObject object, Function filter) throws JSONException { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences(sp, Activity.MODE_PRIVATE); + JSONObject jsonConfig = new JSONObject(); + for (Map.Entry entry : preferences.getAll().entrySet()) { + String key = entry.getKey(); + if (filter != null && !filter.apply(key)) continue; + if (entry.getValue() instanceof Long) { + key = key + "_long"; + } else if (entry.getValue() instanceof Float) { + key = key + "_float"; + } + jsonConfig.put(key, entry.getValue()); + } + object.put(sp, jsonConfig); + } + + public static void importSettings(Context context, File settingsFile) { + + AlertUtil.showConfirm(context, + LocaleController.getString("ImportSettingsAlert", R.string.ImportSettingsAlert), + R.drawable.baseline_security_24, + LocaleController.getString("Import", R.string.Import), + true, + () -> importSettingsConfirmed(context, settingsFile)); + + } + + public static void importSettingsConfirmed(Context context, File settingsFile) { + + try { + JsonObject configJson = GsonUtil.toJsonObject(FileUtil.readUtf8String(settingsFile)); + importSettings(configJson); + + AlertDialog restart = new AlertDialog(context, 0); + restart.setTitle(LocaleController.getString("NekoX", R.string.NekoX)); + restart.setMessage(LocaleController.getString("RestartAppToTakeEffect", R.string.RestartAppToTakeEffect)); + restart.setPositiveButton(LocaleController.getString("OK", R.string.OK), (__, ___) -> { + ProcessPhoenix.triggerRebirth(context, new Intent(context, LaunchActivity.class)); + }); + restart.show(); + } catch (Exception e) { + AlertUtil.showSimpleAlert(context, e); + } + + } + + @SuppressLint("ApplySharedPref") + public static void importSettings(JsonObject configJson) throws JSONException { + + for (Map.Entry element : configJson.entrySet()) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences(element.getKey(), Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + for (Map.Entry config : ((JsonObject) element.getValue()).entrySet()) { + String key = config.getKey(); + JsonPrimitive value = (JsonPrimitive) config.getValue(); + if (value.isBoolean()) { + editor.putBoolean(key, value.getAsBoolean()); + } else if (value.isNumber()) { + boolean isLong = false; + boolean isFloat = false; + if (key.endsWith("_long")) { + key = StringsKt.substringBeforeLast(key, "_long", key); + isLong = true; + } else if (key.endsWith("_float")) { + key = StringsKt.substringBeforeLast(key, "_float", key); + isFloat = true; + } + if (isLong) { + editor.putLong(key, value.getAsLong()); + } else if (isFloat) { + editor.putFloat(key, value.getAsFloat()); + } else { + editor.putInt(key, value.getAsInt()); + } + } else { + editor.putString(key, value.getAsString()); + } + } + editor.commit(); + } + + } + @Override public void onResume() { super.onResume(); @@ -139,7 +364,7 @@ public class NekoSettingsActivity extends BaseFragment { aboutRow = rowCount++; channelRow = rowCount++; - fdroidRow = rowCount ++; + fdroidRow = rowCount++; if (ExternalGcm.checkPlayServices()) { googlePlayRow = rowCount++; } else { diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/GsonUtil.kt b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/GsonUtil.kt index f3f2e437e..6ba3e45f9 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/GsonUtil.kt +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/utils/GsonUtil.kt @@ -1,6 +1,7 @@ package tw.nekomimi.nekogram.utils import com.google.gson.Gson +import com.google.gson.JsonObject import com.google.gson.internal.Streams import com.google.gson.stream.JsonWriter import java.io.StringWriter @@ -21,4 +22,9 @@ object GsonUtil { return stringWriter.toString() } + @JvmStatic + fun toJsonObject(json: String): JsonObject { + return gson.fromJson(json, JsonObject::class.java) + } + } \ No newline at end of file diff --git a/TMessagesProj/src/main/res/values/strings_nekox.xml b/TMessagesProj/src/main/res/values/strings_nekox.xml index 80cbbe9e5..9abb1d330 100644 --- a/TMessagesProj/src/main/res/values/strings_nekox.xml +++ b/TMessagesProj/src/main/res/values/strings_nekox.xml @@ -233,4 +233,7 @@ Payload Immediately Due to technical limitations, Ws Relay cannot be used before logging in. + Backup settings + Import settings + Are you sure you want to overwrite the settings? Importing settings from unknown sources may cause the pin to be overwritten and cause the application to be locked. \ No newline at end of file