From 4cf00597395f0f045c673893f1ee690756311d1a Mon Sep 17 00:00:00 2001 From: Oscar Mira Date: Mon, 4 Mar 2024 03:20:57 +0100 Subject: [PATCH] demo: add recover from mnemonic dialog --- .../monero/demo/ui/AddWalletViewModel.kt | 12 +++++ .../molly/monero/demo/ui/AddWalletWizard.kt | 50 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletViewModel.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletViewModel.kt index 57b6f24..40c1c7c 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletViewModel.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletViewModel.kt @@ -12,7 +12,9 @@ import im.molly.monero.demo.data.RemoteNodeRepository import im.molly.monero.demo.data.WalletRepository import im.molly.monero.demo.data.model.DefaultMoneroNetwork import im.molly.monero.demo.data.model.RemoteNode +import im.molly.monero.mnemonics.MoneroMnemonic import im.molly.monero.util.parseHex +import im.molly.monero.util.toHex import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed @@ -82,6 +84,16 @@ class AddWalletViewModel( this.secretSpendKeyHex = value } + fun recoverFromMnemonic(words: String): Boolean { + MoneroMnemonic.recoverEntropy(words)?.use { mnemonicCode -> + val secretKey = SecretKey(mnemonicCode.entropy) + secretSpendKeyHex = secretKey.bytes.toHex() + secretKey.destroy() + return true + } + return false + } + fun updateCreationDate(value: String) { this.creationDate = value } diff --git a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt index da3b19f..ea36033 100644 --- a/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt +++ b/demo/android/src/main/kotlin/im/molly/monero/demo/ui/AddWalletWizard.kt @@ -1,5 +1,6 @@ package im.molly.monero.demo.ui +import android.widget.Toast import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions @@ -8,6 +9,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -84,6 +86,8 @@ fun AddWalletSecondStepRoute( onNavigateToHome: () -> Unit, viewModel: AddWalletViewModel = viewModel(), ) { + val context = LocalContext.current + val remoteNodes by viewModel.currentRemoteNodes.collectAsStateWithLifecycle() SecondStepScreen( @@ -109,6 +113,12 @@ fun AddWalletSecondStepRoute( onWalletNameChanged = { name -> viewModel.updateWalletName(name) }, onNetworkChanged = { network -> viewModel.toggleSelectedNetwork(network) }, onSecretSpendKeyHexChanged = { value -> viewModel.updateSecretSpendKeyHex(value) }, + onRecoverFromMnemonic = { words -> + val success = viewModel.recoverFromMnemonic(words) + if (!success) { + Toast.makeText(context, "Invalid seed", Toast.LENGTH_LONG).show() + } + }, onCreationDateChanged = { value -> viewModel.updateCreationDate(value) }, onRestoreHeightChanged = { value -> viewModel.updateRestoreHeight(value) }, remoteNodes = remoteNodes, @@ -134,12 +144,14 @@ private fun SecondStepScreen( onWalletNameChanged: (String) -> Unit = {}, onNetworkChanged: (MoneroNetwork) -> Unit = {}, onSecretSpendKeyHexChanged: (String) -> Unit = {}, + onRecoverFromMnemonic: (String) -> Unit = {}, onCreationDateChanged: (String) -> Unit = {}, onRestoreHeightChanged: (String) -> Unit = {}, remoteNodes: List, selectedRemoteNodeIds: MutableMap = mutableMapOf(), ) { var showOffLineConfirmationDialog by remember { mutableStateOf(false) } + var showMnemonicDialog by remember { mutableStateOf(false) } Scaffold(topBar = { Toolbar( @@ -205,6 +217,13 @@ private fun SecondStepScreen( .fillMaxWidth() .padding(start = 16.dp, end = 16.dp), ) + TextButton( + onClick = { showMnemonicDialog = true }, + modifier = Modifier + .padding(start = 16.dp), + ) { + Text("Recover from 25-word mnemonic") + } Text( text = "Synchronization", style = MaterialTheme.typography.titleMedium, @@ -257,6 +276,37 @@ private fun SecondStepScreen( } } + if (showMnemonicDialog) { + var words by remember { mutableStateOf("") } + AlertDialog( + onDismissRequest = { showMnemonicDialog = false }, + title = { Text("Enter your recovery phrase") }, + text = { + OutlinedTextField( + value = words, + onValueChange = { words = it }, + singleLine = true, + ) + }, + confirmButton = { + TextButton( + onClick = { + onRecoverFromMnemonic(words) + showMnemonicDialog = false + }, + enabled = words.isNotEmpty(), + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton(onClick = { showMnemonicDialog = false }) { + Text("Cancel") + } + } + ) + } + if (showOffLineConfirmationDialog) { AlertDialog(onDismissRequest = { showOffLineConfirmationDialog = false }, title = { Text("No remote nodes selected")