feat: set passcode for each account

Co-authored-by: tehcneko <chsqwyx@gmail.com>
This commit is contained in:
xtaodada 2022-12-01 22:09:37 +08:00
parent 5d2ccbae60
commit eda7eb4cf8
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
15 changed files with 760 additions and 28 deletions

View File

@ -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);
}

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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<TLRPC.User> 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);

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));

View File

@ -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<Integer> 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;
}
}
}

View File

@ -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;

View File

@ -149,4 +149,22 @@
<string name="DatacenterStatusAbout">检查 Telegram 的**数据中心**的状态。</string>
<string name="DatacenterStatus">数据中心状态</string>
<string name="enhancedFileLoader">加速上传和下载</string>
<string name="UnknownNekoSettingsOption">没有这样的喵设置选项。</string>
<string name="PasscodeNeeded">您必须先启用锁定码</string>
<string name="PasscodeNeko">锁定码</string>
<string name="PasscodeShowInSettings">在喵设置中显示</string>
<string name="PasscodeShowInSettingsAbout">隐藏后,您**只能**通过链接打开此页面:</string>
<string name="PasscodeSet">设置锁定码</string>
<string name="PasscodeEdit">更改锁定码</string>
<string name="PasscodeRemove">移除锁定码</string>
<string name="PasscodeHideAccount">隐藏账户</string>
<string name="PasscodeAllowPanic">使用紧急代码退出登录</string>
<string name="PasscodeAbout">为每个账户设置不同的锁定码,并通过输入相应的锁定码在它们之间切换。</string>
<string name="PasscodePanicCode">紧急代码</string>
<string name="PasscodePanicCodeSet">设置紧急代码</string>
<string name="PasscodePanicCodeEdit">更改紧急代码</string>
<string name="PasscodePanicCodeRemove">关闭紧急代码</string>
<string name="PasscodePanicCodeRemoveConfirmMessage">您确定要关闭紧急代码吗?</string>
<string name="PasscodeRemoveConfirmMessage">您确定要移除这个账户的锁定码吗?</string>
<string name="PasscodePanicCodeAbout">如果出现问题,使用紧急代码退出登录账户。</string>
</resources>

View File

@ -153,4 +153,22 @@
<string name="enhancedFileLoader">Enhance Upload and Download speed</string>
<string name="mapDriftingFixForGoogleMaps">Fix Google Maps drifting in China</string>
<string name="useOSMDroidMap">Use OSMDroid Map</string>
<string name="UnknownNekoSettingsOption">There is no such Neko Settings option.</string>
<string name="PasscodeNeeded">You have to enable passcode lock first</string>
<string name="PasscodeNeko">Passcode</string>
<string name="PasscodeShowInSettings">Show in Neko Settings</string>
<string name="PasscodeShowInSettingsAbout">After hiding, you can **ONLY** open this page by link:</string>
<string name="PasscodeSet">Set passcode</string>
<string name="PasscodeEdit">Change passcode</string>
<string name="PasscodeRemove">Remove passcode</string>
<string name="PasscodeHideAccount">Hide account</string>
<string name="PasscodeAllowPanic">Logout with panic code</string>
<string name="PasscodeAbout">Set different passcodes for each account, and switch between them by entering the corresponding passcode.</string>
<string name="PasscodePanicCode">Panic code</string>
<string name="PasscodePanicCodeSet">Set panic code</string>
<string name="PasscodePanicCodeEdit">Change panic code</string>
<string name="PasscodePanicCodeRemove">Turn off panic code</string>
<string name="PasscodePanicCodeRemoveConfirmMessage">Are you sure you want to turn off panic code?</string>
<string name="PasscodeRemoveConfirmMessage">Are you sure you want to remove passcode for this account?</string>
<string name="PasscodePanicCodeAbout">Use the panic code to log out accounts if something goes wrong.</string>
</resources>