From eda7eb4cf8f29bbcd34262d7fe830f3f260a5dd8 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Thu, 1 Dec 2022 22:09:37 +0800 Subject: [PATCH] feat: set passcode for each account Co-authored-by: tehcneko --- .../ui/Adapters/DrawerLayoutAdapter.java | 2 + .../telegram/ui/Components/AlertsCreator.java | 2 + .../telegram/ui/Components/PasscodeView.java | 3 +- .../java/org/telegram/ui/DialogsActivity.java | 2 + .../java/org/telegram/ui/LaunchActivity.java | 25 ++ .../java/org/telegram/ui/LoginActivity.java | 16 +- .../org/telegram/ui/PasscodeActivity.java | 41 ++- .../nekogram/helpers/PasscodeHelper.java | 155 ++++++++ .../nekogram/helpers/SettingsHelper.java | 61 ++++ .../nekogram/settings/AccountCell.java | 98 ++++++ .../settings/BaseNekoSettingsActivity.java | 4 + .../NekoPasscodeSettingsActivity.java | 330 ++++++++++++++++++ .../settings/NekoSettingsActivity.java | 13 +- .../main/res/values-zh-rCN/strings_neko.xml | 18 + .../src/main/res/values/strings_neko.xml | 18 + 15 files changed, 760 insertions(+), 28 deletions(-) create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/PasscodeHelper.java create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/AccountCell.java create mode 100644 TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoPasscodeSettingsActivity.java diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java index 12c54b8bd..493e1f4d0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java @@ -49,6 +49,7 @@ import cn.hutool.core.util.StrUtil; import kotlin.jvm.functions.Function0; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoXConfig; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import xyz.nextalone.nagram.NaConfig; public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate { @@ -267,6 +268,7 @@ public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter imple private void resetItems() { accountNumbers.clear(); for (int a : SharedConfig.activeAccounts) { + if (PasscodeHelper.isAccountHidden(a)) continue; if (UserConfig.getInstance(a).isClientActivated()) { accountNumbers.add(a); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index eb2ba5c1d..2f47ebcf4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -122,6 +122,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import kotlin.Unit; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.ui.BottomBuilder; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.utils.AlertUtil; @@ -4949,6 +4950,7 @@ public class AlertsCreator { final LinearLayout linearLayout = new LinearLayout(parentActivity); linearLayout.setOrientation(LinearLayout.VERTICAL); for (int a : SharedConfig.activeAccounts) { + if (PasscodeHelper.isAccountHidden(a)) continue; TLRPC.User u = UserConfig.getInstance(a).getCurrentUser(); if (u != null) { AccountSelectCell cell = new AccountSelectCell(parentActivity, false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java index 34cd7fb25..0d564a3c9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java @@ -85,6 +85,7 @@ import java.util.Locale; import java.util.concurrent.Executor; import tw.nekomimi.nekogram.NekoConfig; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.utils.VibrateUtil; import xyz.nextalone.nagram.NaConfig; @@ -954,7 +955,7 @@ public class PasscodeView extends FrameLayout implements NotificationCenter.Noti onPasscodeError(); return; } - if (!SharedConfig.checkPasscode(password)) { + if (!PasscodeHelper.checkPasscode((Activity) getContext(), password) && !SharedConfig.checkPasscode(password)) { SharedConfig.increaseBadPasscodeTries(); if (SharedConfig.passcodeRetryInMs > 0) { checkRetryTextView(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index b7109c0e1..402fb0896 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -193,6 +193,7 @@ import java.util.ArrayList; import java.util.Random; import kotlin.Unit; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.ui.BottomBuilder; import tw.nekomimi.nekogram.InternalUpdater; import tw.nekomimi.nekogram.NekoConfig; @@ -2787,6 +2788,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. int accounts = 0; for (int a : SharedConfig.activeAccounts) { + if (PasscodeHelper.isAccountHidden(a)) continue; TLRPC.User u = AccountInstance.getInstance(a).getUserConfig().getCurrentUser(); if (u != null) { AccountSelectCell cell = new AccountSelectCell(context, false); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index 010007423..60924ac28 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -185,6 +185,7 @@ import cn.hutool.core.util.StrUtil; import kotlin.Unit; import kotlin.text.StringsKt; import tw.nekomimi.nekogram.InternalUpdater; +import tw.nekomimi.nekogram.helpers.SettingsHelper; import tw.nekomimi.nekogram.ui.BottomBuilder; import tw.nekomimi.nekogram.NekoConfig; import tw.nekomimi.nekogram.NekoXConfig; @@ -2051,6 +2052,17 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar sticker = path.replace("addstickers/", ""); } else if (path.startsWith("addemoji/")) { emoji = path.replace("addemoji/", ""); + } else if (path.startsWith("nekosettings/")) { + SettingsHelper.processDeepLink(data, fragment -> { + AndroidUtilities.runOnUIThread(() -> presentFragment(fragment, false, false)); + if (AndroidUtilities.isTablet()) { + actionBarLayout.showLastFragment(); + rightActionBarLayout.showLastFragment(); + drawerLayoutContainer.setAllowOpenDrawer(false, false); + } else { + drawerLayoutContainer.setAllowOpenDrawer(true, false); + } + }, () -> showBulletin(factory -> factory.createErrorBulletin(LocaleController.getString("UnknownNekoSettingsOption", R.string.UnknownNekoSettingsOption)))); } else if (path.startsWith("msg/") || path.startsWith("share/")) { message = data.getQueryParameter("url"); if (message == null) { @@ -2421,6 +2433,19 @@ public class LaunchActivity extends BasePermissionsActivity implements ActionBar } } else if (url.startsWith("tg:upgrade") || url.startsWith("tg://upgrade") || url.startsWith("tg:update") || url.startsWith("tg://update")) { checkAppUpdate(true); + } else if (url.startsWith("tg:neko") || url.startsWith("tg://neko")) { + url = url.replace("tg:neko", "tg://t.me/nekosettings").replace("tg://neko", "tg://t.me/nekosettings"); + data = Uri.parse(url); + SettingsHelper.processDeepLink(data, fragment -> { + AndroidUtilities.runOnUIThread(() -> presentFragment(fragment, false, false)); + if (AndroidUtilities.isTablet()) { + actionBarLayout.showLastFragment(); + rightActionBarLayout.showLastFragment(); + drawerLayoutContainer.setAllowOpenDrawer(false, false); + } else { + drawerLayoutContainer.setAllowOpenDrawer(true, false); + } + }, () -> showBulletin(factory -> factory.createErrorBulletin(LocaleController.getString("UnknownNekoSettingsOption", R.string.UnknownNekoSettingsOption)))); } else if ((url.startsWith("tg:search") || url.startsWith("tg://search"))) { url = url.replace("tg:search", "tg://telegram.org").replace("tg://search", "tg://telegram.org"); data = Uri.parse(url); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index dc22263d7..022cc69d3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -173,6 +173,7 @@ import cn.hutool.core.util.StrUtil; import kotlin.Unit; import tw.nekomimi.nekogram.DataCenter; import tw.nekomimi.nekogram.NekoXConfig; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.ui.BottomBuilder; import tw.nekomimi.nekogram.ui.EditTextAutoFill; import tw.nekomimi.nekogram.utils.AlertUtil; @@ -1634,6 +1635,7 @@ R.string.CustomBackend)) } private void onAuthSuccess(TLRPC.TL_auth_authorization res, boolean afterSignup) { + PasscodeHelper.removePasscodeForAccount(currentAccount); MessagesController.getInstance(currentAccount).cleanup(); ConnectionsManager.getInstance(currentAccount).setUserId(res.user.id); UserConfig.getInstance(currentAccount).clearConfig(); @@ -7462,19 +7464,7 @@ R.string.CustomBackend)) int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { if (error == null) { TLRPC.TL_auth_authorization res = (TLRPC.TL_auth_authorization) response; - ConnectionsManager.getInstance(currentAccount).setUserId(res.user.id); - UserConfig.getInstance(currentAccount).clearConfig(); - MessagesController.getInstance(currentAccount).cleanup(); - UserConfig.getInstance(currentAccount).syncContacts = syncContacts; - UserConfig.getInstance(currentAccount).setCurrentUser(res.user); - UserConfig.getInstance(currentAccount).saveConfig(true); - MessagesStorage.getInstance(currentAccount).cleanup(true); - ArrayList users = new ArrayList<>(); - users.add(res.user); - MessagesStorage.getInstance(currentAccount).putUsersAndChats(users, null, true, true); - MessagesController.getInstance(currentAccount).putUser(res.user, false); - ConnectionsManager.getInstance(currentAccount).updateDcSettings(); - needFinishActivity(false, res.setup_password_required, res.otherwise_relogin_days); + onAuthSuccess(res); } else { if (error.code == 401) { ConnectionsManager.native_cleanUp(currentAccount, true); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java index 491667102..3af587a9c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java @@ -97,6 +97,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.utils.VibrateUtil; public class PasscodeActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { @@ -167,6 +168,14 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter private Runnable onShowKeyboardCallback; + private int account = -1; + + public PasscodeActivity(@PasscodeActivityType int type, int account) { + super(); + this.type = type; + this.account = account; + } + public PasscodeActivity(@PasscodeActivityType int type) { super(); this.type = type; @@ -1031,18 +1040,24 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter return; } - boolean isFirst = SharedConfig.passcodeHash.length() == 0; - try { - SharedConfig.passcodeSalt = new byte[16]; - Utilities.random.nextBytes(SharedConfig.passcodeSalt); - byte[] passcodeBytes = firstPassword.getBytes("UTF-8"); - byte[] bytes = new byte[32 + passcodeBytes.length]; - System.arraycopy(SharedConfig.passcodeSalt, 0, bytes, 0, 16); - System.arraycopy(passcodeBytes, 0, bytes, 16, passcodeBytes.length); - System.arraycopy(SharedConfig.passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16); - SharedConfig.passcodeHash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); - } catch (Exception e) { - FileLog.e(e); + boolean isFirst; + if (account != -1) { + isFirst = false; + PasscodeHelper.setPasscodeForAccount(firstPassword, account); + } else { + isFirst = SharedConfig.passcodeHash.length() == 0; + try { + SharedConfig.passcodeSalt = new byte[16]; + Utilities.random.nextBytes(SharedConfig.passcodeSalt); + byte[] passcodeBytes = firstPassword.getBytes("UTF-8"); + byte[] bytes = new byte[32 + passcodeBytes.length]; + System.arraycopy(SharedConfig.passcodeSalt, 0, bytes, 0, 16); + System.arraycopy(passcodeBytes, 0, bytes, 16, passcodeBytes.length); + System.arraycopy(SharedConfig.passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16); + SharedConfig.passcodeHash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); + } catch (Exception e) { + FileLog.e(e); + } } SharedConfig.allowScreenCapture = true; SharedConfig.passcodeType = currentPasswordType; @@ -1080,7 +1095,7 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter onPasscodeError(); return; } - if (!SharedConfig.checkPasscode(password)) { + if (!PasscodeHelper.checkPasscode(getParentActivity(), password) && !SharedConfig.checkPasscode(password)) { SharedConfig.increaseBadPasscodeTries(); passwordEditText.setText(""); for (CodeNumberField f : codeFieldContainer.codeField) { diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/PasscodeHelper.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/PasscodeHelper.java new file mode 100644 index 000000000..482e80306 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/PasscodeHelper.java @@ -0,0 +1,155 @@ +package tw.nekomimi.nekogram.helpers; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.text.TextUtils; +import android.util.Base64; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.SharedConfig; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.ui.LaunchActivity; + +import java.nio.charset.StandardCharsets; + +public class PasscodeHelper { + private static final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("nekopasscode", Context.MODE_PRIVATE); + + public static boolean checkPasscode(Activity activity, String passcode) { + if (hasPasscodeForAccount(Integer.MAX_VALUE)) { + String passcodeHash = preferences.getString("passcodeHash" + Integer.MAX_VALUE, ""); + String passcodeSaltString = preferences.getString("passcodeSalt" + Integer.MAX_VALUE, ""); + if (checkPasscodeHash(passcode, passcodeHash, passcodeSaltString)) { + for (int a : SharedConfig.activeAccounts) { + if (UserConfig.getInstance(a).isClientActivated() && isAccountAllowPanic(a)) { + MessagesController.getInstance(a).performLogout(1); + } + } + return false; + } + } + for (int a : SharedConfig.activeAccounts) { + if (UserConfig.getInstance(a).isClientActivated() && hasPasscodeForAccount(a)) { + String passcodeHash = preferences.getString("passcodeHash" + a, ""); + String passcodeSaltString = preferences.getString("passcodeSalt" + a, ""); + if (checkPasscodeHash(passcode, passcodeHash, passcodeSaltString)) { + if (activity instanceof LaunchActivity) { + LaunchActivity launchActivity = (LaunchActivity) activity; + launchActivity.switchToAccount(a, true); + return true; + } + } + } + } + return false; + } + + private static boolean checkPasscodeHash(String passcode, String passcodeHash, String passcodeSaltString) { + try { + byte[] passcodeSalt; + if (passcodeSaltString.length() > 0) { + passcodeSalt = Base64.decode(passcodeSaltString, Base64.DEFAULT); + } else { + passcodeSalt = new byte[0]; + } + byte[] passcodeBytes = passcode.getBytes(StandardCharsets.UTF_8); + byte[] bytes = new byte[32 + passcodeBytes.length]; + System.arraycopy(passcodeSalt, 0, bytes, 0, 16); + System.arraycopy(passcodeBytes, 0, bytes, 16, passcodeBytes.length); + System.arraycopy(passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16); + String hash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); + return passcodeHash.equals(hash); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + + public static void removePasscodeForAccount(int account) { + preferences.edit() + .remove("passcodeHash" + account) + .remove("passcodeSalt" + account) + .remove("hide" + account) + .apply(); + } + + public static boolean isAccountAllowPanic(int account) { + return preferences.getBoolean("allowPanic" + account, true); + } + + public static boolean isAccountHidden(int account) { + return hasPasscodeForAccount(account) && preferences.getBoolean("hide" + account, false); + } + + public static void setAccountAllowPanic(int account, boolean panic) { + preferences.edit() + .putBoolean("allowPanic" + account, panic) + .apply(); + } + + public static void setHideAccount(int account, boolean hide) { + preferences.edit() + .putBoolean("hide" + account, hide) + .apply(); + } + + public static void setPasscodeForAccount(String firstPassword, int account) { + try { + byte[] passcodeSalt = new byte[16]; + Utilities.random.nextBytes(passcodeSalt); + byte[] passcodeBytes = firstPassword.getBytes(StandardCharsets.UTF_8); + byte[] bytes = new byte[32 + passcodeBytes.length]; + System.arraycopy(passcodeSalt, 0, bytes, 0, 16); + System.arraycopy(passcodeBytes, 0, bytes, 16, passcodeBytes.length); + System.arraycopy(passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16); + preferences.edit() + .putString("passcodeHash" + account, Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length))) + .putString("passcodeSalt" + account, Base64.encodeToString(passcodeSalt, Base64.DEFAULT)) + .apply(); + } catch (Exception e) { + FileLog.e(e); + } + } + + public static boolean hasPasscodeForAccount(int account) { + return preferences.contains("passcodeHash" + account) && preferences.contains("passcodeSalt" + account); + } + + public static boolean hasPanicCode() { + return hasPasscodeForAccount(Integer.MAX_VALUE); + } + + public static String getSettingsKey() { + var settingsHash = preferences.getString("settingsHash", ""); + if (!TextUtils.isEmpty(settingsHash)) { + return settingsHash; + } + byte[] bytes = new byte[8]; + Utilities.random.nextBytes(bytes); + var hash = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + preferences.edit().putString("settingsHash", hash).apply(); + return hash; + } + + public static boolean isSettingsHidden() { + return preferences.getBoolean("hideSettings", false); + } + + public static void setHideSettings(boolean hide) { + preferences.edit() + .putBoolean("hideSettings", hide) + .apply(); + } + + public static boolean isEnabled() { + return preferences.getAll().size() != 0; + } + + public static void clearAll() { + preferences.edit().clear().apply(); + } +} diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java new file mode 100644 index 000000000..b454045ab --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/helpers/SettingsHelper.java @@ -0,0 +1,61 @@ +package tw.nekomimi.nekogram.helpers; + +import android.net.Uri; + +import org.telegram.ui.ActionBar.BaseFragment; + +import tw.nekomimi.nekogram.settings.NekoChatSettingsActivity; +import tw.nekomimi.nekogram.settings.NekoAccountSettingsActivity; +import tw.nekomimi.nekogram.settings.NekoExperimentalSettingsActivity; +import tw.nekomimi.nekogram.settings.NekoGeneralSettingsActivity; +import tw.nekomimi.nekogram.settings.NekoPasscodeSettingsActivity; +import tw.nekomimi.nekogram.settings.NekoSettingsActivity; + +public class SettingsHelper { + + public static void processDeepLink(Uri uri, Callback callback, Runnable unknown) { + if (uri == null) { + unknown.run(); + return; + } + var segments = uri.getPathSegments(); + if (segments.isEmpty() || segments.size() > 2 || !"nekosettings".equals(segments.get(0))) { + unknown.run(); + return; + } + BaseFragment fragment; + if (segments.size() == 1) { + fragment = new NekoSettingsActivity(); + } else if (PasscodeHelper.getSettingsKey().equals(segments.get(1))) { + fragment = new NekoPasscodeSettingsActivity(); + } else { + switch (segments.get(1)) { + case "a": + case "account": + fragment = new NekoAccountSettingsActivity(); + break; + case "chat": + case "chats": + case "c": + fragment = new NekoChatSettingsActivity(); + break; + case "experimental": + case "e": + fragment = new NekoExperimentalSettingsActivity(); + break; + case "general": + case "g": + fragment = new NekoGeneralSettingsActivity(); + break; + default: + unknown.run(); + return; + } + } + callback.presentFragment(fragment); + } + + public interface Callback { + void presentFragment(BaseFragment fragment); + } +} diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/AccountCell.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/AccountCell.java new file mode 100644 index 000000000..a4e2e3835 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/AccountCell.java @@ -0,0 +1,98 @@ +package tw.nekomimi.nekogram.settings; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.LayoutHelper; + +public class AccountCell extends FrameLayout { + + private final TextView textView; + private final BackupImageView imageView; + private final ImageView checkImageView; + private final AvatarDrawable avatarDrawable; + private boolean needDivider; + + private int accountNumber; + + public AccountCell(Context context) { + super(context); + + avatarDrawable = new AvatarDrawable(); + avatarDrawable.setTextSize(AndroidUtilities.dp(12)); + + imageView = new BackupImageView(context); + imageView.setRoundRadius(AndroidUtilities.dp(18)); + addView(imageView, LayoutHelper.createFrame(36, 36, Gravity.LEFT | Gravity.TOP, 16, 10, 0, 0)); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 68, 0, 56, 0)); + + checkImageView = new ImageView(context); + checkImageView.setImageResource(R.drawable.account_check); + checkImageView.setScaleType(ImageView.ScaleType.CENTER); + checkImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_switchTrackChecked), PorterDuff.Mode.MULTIPLY)); + addView(checkImageView, LayoutHelper.createFrame(40, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 6, 0)); + } + + @Override + protected void onDraw(Canvas canvas) { + if (needDivider) { + canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(68), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + imageView.setAlpha(enabled ? 1.0f : 0.5f); + textView.setAlpha(enabled ? 1.0f : 0.5f); + checkImageView.setAlpha(enabled ? 1.0f : 0.5f); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + } + + public void setAccount(int account, boolean check, boolean divider) { + accountNumber = account; + TLRPC.User user = UserConfig.getInstance(accountNumber).getCurrentUser(); + avatarDrawable.setInfo(user); + textView.setText(ContactsController.formatName(user.first_name, user.last_name)); + imageView.getImageReceiver().setCurrentAccount(account); + imageView.setForUserOrChat(user, avatarDrawable); + checkImageView.setVisibility(check ? VISIBLE : INVISIBLE); + needDivider = divider; + setWillNotDraw(!divider); + } + + public int getAccountNumber() { + return accountNumber; + } +} diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoSettingsActivity.java index ecc0622ce..ce0cae43b 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/BaseNekoSettingsActivity.java @@ -330,6 +330,10 @@ public abstract class BaseNekoSettingsActivity extends BaseFragment { view = new TextRadioCell(mContext); view.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); break; + case 11: + view = new AccountCell(mContext); + view.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); + break; } //noinspection ConstantConditions view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); diff --git a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoPasscodeSettingsActivity.java b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoPasscodeSettingsActivity.java new file mode 100644 index 000000000..b392fbfe2 --- /dev/null +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoPasscodeSettingsActivity.java @@ -0,0 +1,330 @@ +package tw.nekomimi.nekogram.settings; + +import android.app.Dialog; +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AccountInstance; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.SharedConfig; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.TextCheckCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.URLSpanNoUnderline; +import org.telegram.ui.PasscodeActivity; + +import java.util.ArrayList; +import java.util.Locale; + +import tw.nekomimi.nekogram.helpers.PasscodeHelper; + +public class NekoPasscodeSettingsActivity extends BaseNekoSettingsActivity { + + private boolean passcodeSet; + + private int showInSettingsRow; + private int showInSettings2Row; + + private int accountsStartRow; + private int accountsEndRow; + + private int panicCodeRow; + private int setPanicCodeRow; + private int removePanicCodeRow; + private int panicCode2Row; + + private int clearPasscodesRow; + private int clearPasscodes2Row; + + private final ArrayList accounts = new ArrayList<>(); + + @Override + public boolean onFragmentCreate() { + for (int a : SharedConfig.activeAccounts) { + var u = AccountInstance.getInstance(a).getUserConfig().getCurrentUser(); + if (u != null) { + accounts.add(a); + } + } + return super.onFragmentCreate(); + } + + @Override + protected void onItemClick(View view, int position, float x, float y) { + if (!passcodeSet) { + BulletinFactory.of(this).createErrorBulletin(LocaleController.getString("PasscodeNeeded", R.string.PasscodeNeeded)).show(); + return; + } + if (position > accountsStartRow && position < accountsEndRow) { + var account = accounts.get(position - accountsStartRow - 1); + var builder = new AlertDialog.Builder(getParentActivity()); + + var linearLayout = new LinearLayout(getParentActivity()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + if (PasscodeHelper.hasPasscodeForAccount(account)) { + TextCheckCell hideAccount = new TextCheckCell(getParentActivity(), 23, true); + hideAccount.setTextAndCheck(LocaleController.getString("PasscodeHideAccount", R.string.PasscodeHideAccount), PasscodeHelper.isAccountHidden(account), false); + hideAccount.setOnClickListener(view13 -> { + boolean hide = !hideAccount.isChecked(); + PasscodeHelper.setHideAccount(account, hide); + hideAccount.setChecked(hide); + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + }); + hideAccount.setBackground(Theme.getSelectorDrawable(false)); + linearLayout.addView(hideAccount, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + TextCheckCell allowPanic = new TextCheckCell(getParentActivity(), 23, true); + allowPanic.setTextAndCheck(LocaleController.getString("PasscodeAllowPanic", R.string.PasscodeAllowPanic), PasscodeHelper.isAccountAllowPanic(account), false); + allowPanic.setOnClickListener(view13 -> { + boolean hide = !allowPanic.isChecked(); + PasscodeHelper.setAccountAllowPanic(account, hide); + allowPanic.setChecked(hide); + }); + allowPanic.setBackground(Theme.getSelectorDrawable(false)); + linearLayout.addView(allowPanic, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + AlertDialog.AlertDialogCell editPasscode = new AlertDialog.AlertDialogCell(getParentActivity(), null); + editPasscode.setTextAndIcon(PasscodeHelper.hasPasscodeForAccount(account) ? LocaleController.getString("PasscodeEdit", R.string.PasscodeEdit) : LocaleController.getString("PasscodeSet", R.string.PasscodeSet), 0); + editPasscode.setOnClickListener(view1 -> { + builder.getDismissRunnable().run(); + presentFragment(new PasscodeActivity(PasscodeActivity.TYPE_SETUP_CODE, account)); + }); + editPasscode.setBackground(Theme.getSelectorDrawable(false)); + linearLayout.addView(editPasscode, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + if (PasscodeHelper.hasPasscodeForAccount(account)) { + AlertDialog.AlertDialogCell removePasscode = new AlertDialog.AlertDialogCell(getParentActivity(), null); + removePasscode.setTextAndIcon(LocaleController.getString("PasscodeRemove", R.string.PasscodeRemove), 0); + removePasscode.setOnClickListener(view12 -> { + AlertDialog alertDialog = new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString(R.string.PasscodeRemove)) + .setMessage(LocaleController.getString(R.string.PasscodeRemoveConfirmMessage)) + .setNegativeButton(LocaleController.getString(R.string.Cancel), null) + .setPositiveButton(LocaleController.getString(R.string.DisablePasscodeTurnOff), (dialog, which) -> { + var hidden = PasscodeHelper.isAccountHidden(account); + PasscodeHelper.removePasscodeForAccount(account); + listAdapter.notifyItemChanged(position); + if (hidden) { + getNotificationCenter().postNotificationName(NotificationCenter.mainUserInfoChanged); + } + }).create(); + showDialog(alertDialog); + ((TextView) alertDialog.getButton(Dialog.BUTTON_POSITIVE)).setTextColor(Theme.getColor(Theme.key_dialogTextRed)); + }); + removePasscode.setBackground(Theme.getSelectorDrawable(false)); + linearLayout.addView(removePasscode, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + builder.setView(linearLayout); + showDialog(builder.create()); + } else if (position == clearPasscodesRow) { + PasscodeHelper.clearAll(); + finishFragment(); + } else if (position == setPanicCodeRow) { + presentFragment(new PasscodeActivity(PasscodeActivity.TYPE_SETUP_CODE, Integer.MAX_VALUE)); + } else if (position == removePanicCodeRow) { + AlertDialog alertDialog = new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString(R.string.PasscodePanicCodeRemove)) + .setMessage(LocaleController.getString(R.string.PasscodePanicCodeRemoveConfirmMessage)) + .setNegativeButton(LocaleController.getString(R.string.Cancel), null) + .setPositiveButton(LocaleController.getString(R.string.DisablePasscodeTurnOff), (dialog, which) -> { + PasscodeHelper.removePasscodeForAccount(Integer.MAX_VALUE); + listAdapter.notifyItemChanged(setPanicCodeRow); + listAdapter.notifyItemRemoved(removePanicCodeRow); + updateRows(); + }).create(); + showDialog(alertDialog); + ((TextView) alertDialog.getButton(Dialog.BUTTON_POSITIVE)).setTextColor(Theme.getColor(Theme.key_dialogTextRed)); + } else if (position == showInSettingsRow) { + PasscodeHelper.setHideSettings(!PasscodeHelper.isSettingsHidden()); + if (view instanceof TextCheckCell) { + ((TextCheckCell) view).setChecked(!PasscodeHelper.isSettingsHidden()); + } + } + } + + @Override + protected BaseListAdapter createAdapter(Context context) { + return new ListAdapter(context); + } + + @Override + protected String getActionBarTitle() { + return LocaleController.getString("PasscodeNeko", R.string.PasscodeNeko); + } + + @Override + protected String getKey() { + return PasscodeHelper.getSettingsKey(); + } + + @Override + public void onResume() { + passcodeSet = SharedConfig.passcodeHash.length() > 0; + if (!passcodeSet) { + BulletinFactory.of(this).createErrorBulletin(LocaleController.getString("PasscodeNeeded", R.string.PasscodeNeeded)).show(); + } + updateRows(); + super.onResume(); + } + + @Override + protected void updateRows() { + super.updateRows(); + + showInSettingsRow = rowCount++; + showInSettings2Row = rowCount++; + + accountsStartRow = rowCount++; + rowCount += accounts.size(); + accountsEndRow = rowCount++; + + panicCodeRow = rowCount++; + setPanicCodeRow = rowCount++; + if (!PasscodeHelper.hasPanicCode()) { + removePanicCodeRow = -1; + } else { + removePanicCodeRow = rowCount++; + } + panicCode2Row = rowCount++; + + if (false) { + clearPasscodesRow = rowCount++; + clearPasscodes2Row = rowCount++; + } else { + clearPasscodesRow = -1; + clearPasscodes2Row = -1; + } + } + + private class ListAdapter extends BaseListAdapter { + + public ListAdapter(Context context) { + super(context); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 1: { + if (position == clearPasscodes2Row) { + holder.itemView.setBackground(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + holder.itemView.setBackground(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + case 2: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setCanDisable(true); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + if (position == setPanicCodeRow) { + textCell.setText(PasscodeHelper.hasPanicCode() ? LocaleController.getString("PasscodePanicCodeEdit", R.string.PasscodePanicCodeEdit) : LocaleController.getString("PasscodePanicCodeSet", R.string.PasscodePanicCodeSet), removePanicCodeRow != -1); + } else if (position == clearPasscodesRow) { + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText)); + textCell.setText("Clear passcodes", false); + } else if (position == removePanicCodeRow) { + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText)); + textCell.setText(LocaleController.getString("PasscodePanicCodeRemove", R.string.PasscodePanicCodeRemove), false); + } + break; + } + case 3: { + TextCheckCell textCell = (TextCheckCell) holder.itemView; + textCell.setEnabled(passcodeSet, null); + if (position == showInSettingsRow) { + textCell.setTextAndCheck(LocaleController.getString("PasscodeShowInSettings", R.string.PasscodeShowInSettings), !PasscodeHelper.isSettingsHidden(), false); + } + break; + } + case 4: { + HeaderCell cell = (HeaderCell) holder.itemView; + cell.setEnabled(passcodeSet, null); + if (position == accountsStartRow) { + cell.setText(LocaleController.getString("Account", R.string.Account)); + } else if (position == panicCodeRow) { + cell.setText(LocaleController.getString("PasscodePanicCode", R.string.PasscodePanicCode)); + } + break; + } + case 7: { + TextInfoPrivacyCell cell = (TextInfoPrivacyCell) holder.itemView; + cell.setEnabled(passcodeSet, null); + cell.setBackground(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + if (position == accountsEndRow) { + cell.setText(LocaleController.getString("PasscodeAbout", R.string.PasscodeAbout)); + } else if (position == panicCode2Row) { + cell.setText(LocaleController.getString("PasscodePanicCodeAbout", R.string.PasscodePanicCodeAbout)); + if (clearPasscodesRow == -1) { + cell.setBackground(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + } else if (position == showInSettings2Row) { + var link = String.format(Locale.ENGLISH, "https://t.me/nekosettings/%s", PasscodeHelper.getSettingsKey()); + var stringBuilder = new SpannableStringBuilder(AndroidUtilities.replaceTags(LocaleController.getString("PasscodeShowInSettingsAbout", R.string.PasscodeShowInSettingsAbout))); + stringBuilder.append("\n").append(link); + stringBuilder.setSpan(new URLSpanNoUnderline(null) { + @Override + public void onClick(@NonNull View view) { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", link); + clipboard.setPrimaryClip(clip); + BulletinFactory.of(NekoPasscodeSettingsActivity.this).createCopyLinkBulletin().show(); + } + }, stringBuilder.length() - link.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + cell.setText(stringBuilder); + } + break; + } + case 11: { + AccountCell cell = (AccountCell) holder.itemView; + cell.setEnabled(passcodeSet); + int account = accounts.get(position - accountsStartRow - 1); + cell.setAccount(account, PasscodeHelper.hasPasscodeForAccount(account), position + 1 != accountsEndRow); + break; + } + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return passcodeSet && super.isEnabled(holder); + } + + @Override + public int getItemViewType(int position) { + if (position == clearPasscodes2Row) { + return 1; + } else if (position == clearPasscodesRow || position == setPanicCodeRow || position == removePanicCodeRow) { + return 2; + } else if (position == showInSettingsRow) { + return 3; + } else if (position == accountsStartRow || position == panicCodeRow) { + return 4; + } else if (position == showInSettings2Row || position == accountsEndRow || position == panicCode2Row) { + return 7; + } else if (position > accountsStartRow && position < accountsEndRow) { + return 11; + } + return 2; + } + } + +} 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 f838393ec..53abe92b7 100644 --- a/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java +++ b/TMessagesProj/src/main/java/tw/nekomimi/nekogram/settings/NekoSettingsActivity.java @@ -60,6 +60,7 @@ import java.util.function.Function; import kotlin.text.StringsKt; import tw.nekomimi.nekogram.DatacenterActivity; +import tw.nekomimi.nekogram.helpers.PasscodeHelper; import tw.nekomimi.nekogram.utils.AlertUtil; import tw.nekomimi.nekogram.utils.EnvUtil; import tw.nekomimi.nekogram.utils.FileUtil; @@ -77,6 +78,7 @@ public class NekoSettingsActivity extends BaseFragment { private int generalRow; private int accountRow; private int chatRow; + private int passcodeRow; private int experimentRow; private int categories2Row; @@ -172,6 +174,8 @@ public class NekoSettingsActivity extends BaseFragment { presentFragment(new NekoGeneralSettingsActivity()); } else if (position == accountRow) { presentFragment(new NekoAccountSettingsActivity()); + } else if (position == passcodeRow) { + presentFragment(new NekoPasscodeSettingsActivity()); } else if (position == experimentRow) { presentFragment(new NekoExperimentalSettingsActivity()); } else if (position == channelRow) { @@ -380,6 +384,11 @@ public class NekoSettingsActivity extends BaseFragment { generalRow = rowCount++; accountRow = rowCount++; chatRow = rowCount++; + if (!PasscodeHelper.isSettingsHidden()) { + passcodeRow = rowCount++; + } else { + passcodeRow = -1; + } experimentRow = rowCount++; categories2Row = rowCount++; @@ -472,6 +481,8 @@ public class NekoSettingsActivity extends BaseFragment { textCell.setTextAndIcon(LocaleController.getString("Chat", R.string.Chat), R.drawable.menu_chats, true); } else if (position == generalRow) { textCell.setTextAndIcon(LocaleController.getString("General", R.string.General), R.drawable.msg_theme, true); + } else if (position == passcodeRow) { + textCell.setTextAndIcon(LocaleController.getString("PasscodeNeko", R.string.PasscodeNeko), R.drawable.msg_permissions, true); } else if (position == experimentRow) { textCell.setTextAndIcon(LocaleController.getString("Experiment", R.string.Experiment), R.drawable.msg_fave, true); } else if (position == accountRow) { @@ -555,7 +566,7 @@ public class NekoSettingsActivity extends BaseFragment { public int getItemViewType(int position) { if (position == categories2Row || position == about2Row) { return 1; - } else if (position == chatRow || position == accountRow || position == generalRow || position == experimentRow) { + } else if (position == chatRow || position == accountRow || position == generalRow || position == passcodeRow || position == experimentRow) { return 2; } else if (position == categoriesRow || position == aboutRow) { return 4; diff --git a/TMessagesProj/src/main/res/values-zh-rCN/strings_neko.xml b/TMessagesProj/src/main/res/values-zh-rCN/strings_neko.xml index d17f4833f..d3044d54a 100644 --- a/TMessagesProj/src/main/res/values-zh-rCN/strings_neko.xml +++ b/TMessagesProj/src/main/res/values-zh-rCN/strings_neko.xml @@ -149,4 +149,22 @@ 检查 Telegram 的**数据中心**的状态。 数据中心状态 加速上传和下载 + 没有这样的喵设置选项。 + 您必须先启用锁定码 + 锁定码 + 在喵设置中显示 + 隐藏后,您**只能**通过链接打开此页面: + 设置锁定码 + 更改锁定码 + 移除锁定码 + 隐藏账户 + 使用紧急代码退出登录 + 为每个账户设置不同的锁定码,并通过输入相应的锁定码在它们之间切换。 + 紧急代码 + 设置紧急代码 + 更改紧急代码 + 关闭紧急代码 + 您确定要关闭紧急代码吗? + 您确定要移除这个账户的锁定码吗? + 如果出现问题,使用紧急代码退出登录账户。 diff --git a/TMessagesProj/src/main/res/values/strings_neko.xml b/TMessagesProj/src/main/res/values/strings_neko.xml index 226db4108..aa94de78f 100644 --- a/TMessagesProj/src/main/res/values/strings_neko.xml +++ b/TMessagesProj/src/main/res/values/strings_neko.xml @@ -153,4 +153,22 @@ Enhance Upload and Download speed Fix Google Maps drifting in China Use OSMDroid Map + There is no such Neko Settings option. + You have to enable passcode lock first + Passcode + Show in Neko Settings + After hiding, you can **ONLY** open this page by link: + Set passcode + Change passcode + Remove passcode + Hide account + Logout with panic code + Set different passcodes for each account, and switch between them by entering the corresponding passcode. + Panic code + Set panic code + Change panic code + Turn off panic code + Are you sure you want to turn off panic code? + Are you sure you want to remove passcode for this account? + Use the panic code to log out accounts if something goes wrong.