diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt index 34894f03..830109f5 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountStateViewModel.kt @@ -74,7 +74,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() { tryLoginExistingAccount(route, npub) } - fun isValidKey(key: String, password: String): Pair { + fun isValidKey(key: String, password: String, accountIndex: Long = 0): Pair { try { val signer = if (key.startsWith("ncryptsec")) { @@ -83,7 +83,7 @@ class AccountStateViewModel(npub: String?) : ViewModel() { } else if (key.startsWith("nsec")) { NostrSignerInternal(KeyPair(privKey = key.bechToBytes())) } else if (key.contains(" ") && Nip06().isValidMnemonic(key)) { - val keyPair = KeyPair(privKey = Nip06().privateKeyFromMnemonic(key)) + val keyPair = KeyPair(privKey = Nip06().privateKeyFromMnemonic(key, accountIndex)) NostrSignerInternal(keyPair) } else { NostrSignerInternal(KeyPair(Hex.decode(key))) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt index d3b03d02..7938cfac 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/LoginScreen.kt @@ -833,6 +833,7 @@ fun LoginPage( var isMnemonicMode by rememberSaveable { mutableStateOf(false) } var mnemonicWordCount by rememberSaveable { mutableIntStateOf(12) } var mnemonicWords by remember { mutableStateOf(List(12) { "" }) } + var mnemonicAccountIndex by rememberSaveable { mutableIntStateOf(0) } val clipboardManager = LocalClipboard.current Scaffold( @@ -957,6 +958,28 @@ fun LoginPage( } }, ) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(18.dp), + value = if (mnemonicAccountIndex == 0) "" else mnemonicAccountIndex.toString(), + onValueChange = { value -> + mnemonicAccountIndex = value.filter { it.isDigit() }.toIntOrNull() ?: 0 + if (errorMessage.isNotEmpty()) errorMessage = "" + }, + label = { Text(stringResource(R.string.mnemonic_account_index)) }, + placeholder = { Text("0") }, + supportingText = { + Text(stringResource(R.string.mnemonic_account_index_description, mnemonicAccountIndex)) + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Number, + imeAction = ImeAction.Done, + ), + singleLine = true, + ) } else { Text( stringResource(R.string.setup_amber_with_your_nostr_private_key_you_can_enter_different_versions_nsec_ncryptsec_or_hex_you_can_also_scan_it_from_a_qr_code), @@ -1161,7 +1184,7 @@ fun LoginPage( Amber.instance.applicationIOScope.launch { isLoading = true try { - val isValid = accountViewModel.isValidKey(mnemonicStr, "") + val isValid = accountViewModel.isValidKey(mnemonicStr, "", mnemonicAccountIndex.toLong()) isLoading = false if (isValid.first != null) { keyPair = isValid.first!! diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MnemonicLoginInput.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MnemonicLoginInput.kt index c39b7ade..14d9a661 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MnemonicLoginInput.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/MnemonicLoginInput.kt @@ -149,10 +149,12 @@ private fun WordField( nextFocusRequester: FocusRequester?, imeAction: ImeAction, ) { - // Tracks the last word that was chosen from the dropdown so we can suppress - // the menu after selection (selecting changes `word`, which would otherwise - // re-open the menu immediately). - var selectedWord by remember { mutableStateOf("") } + // `remember(word)` resets this state synchronously whenever `word` changes. + // Initialising to `word` when it is already a complete BIP-39 entry + // suppresses the dropdown immediately — no async gap — covering both the + // paste case (word set externally, onValueChange never called) and the + // case where the user finishes typing a valid word. + var selectedWord by remember(word) { mutableStateOf(if (word in wordList) word else "") } val suggestions = remember(word) { if (word.length >= 2) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 620ed2a5..7a74581c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -385,6 +385,8 @@ 12 words 24 words %1$d of %2$d words entered + Account index + Use a different index if this mnemonic was used to create multiple accounts (m/44\'/1237\'/%1$d\'/0/0) Sign policy How to handle this application permissions? Manually approve each permission