Skip to content

Commit

Permalink
AND-9167 [Onboarding V2] Make seed phrase flow stateless and safe to …
Browse files Browse the repository at this point in the history
…modifications (#3791)
  • Loading branch information
MasterBin authored Nov 22, 2024
1 parent a176106 commit 979dad8
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
Expand All @@ -26,6 +27,9 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

// =============================================
// | DON'T FIXME !!! MODIFY WITH CAUTION !!! |
// =============================================
@Suppress("LongParameterList")
@ComponentScoped
internal class MultiWalletSeedPhraseModel @Inject constructor(
Expand All @@ -45,21 +49,40 @@ internal class MultiWalletSeedPhraseModel @Inject constructor(
private val _uiState = MutableStateFlow(getStartState())

private val generateSeedPhraseUiStateBuilder = GenerateSeedPhraseUiStateBuilder(
state = state,
updateUiState = { block -> updateUiStateSpecific(block) },
onContinue = ::openWordsCheck,

// =============================================
// | DON'T FIXME !!! MODIFY WITH CAUTION !!! |
// =============================================
changeWords24Option = { state.update { it.copy(words24Option = it.words24Option) } },
// =============================================
)

private val seedPhraseCheckUiStateBuilder = SeedPhraseCheckUiStateBuilder(
state = state,
currentState = { state.value },
currentUiState = { _uiState.value },
updateUiState = { block -> updateUiStateSpecific(block) },
importWallet = ::importWallet,
readyToImport = { ready -> state.update { it.copy(readyToImport = ready) } },

// =============================================
// | DON'T FIXME !!! MODIFY WITH CAUTION !!! |
// =============================================
importWallet = importWallet@{
importWallet(
mnemonic = if (state.value.words24Option) {
state.value.generatedWords24 ?: return@importWallet
} else {
state.value.generatedWords12 ?: return@importWallet
},
passphrase = null,
)
},
// =============================================
)

private val importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
modelScope = modelScope,
state = state,
mnemonicRepository = mnemonicRepository,
updateUiState = { block -> updateUiStateSpecific(block) },
importWallet = ::importWallet,
Expand Down Expand Up @@ -133,21 +156,21 @@ internal class MultiWalletSeedPhraseModel @Inject constructor(
}
}

private fun importWallet(shouldReset: Boolean = false) {
// TODO make stateless AND-9167
// =============================================
// | DON'T FIXME !!! MODIFY WITH CAUTION !!! |
// =============================================
private fun importWallet(mnemonic: Mnemonic, passphrase: String?) {
val currentState = state.value
val mnemonic = currentState.importedMnemonic
?: (if (currentState.words24Option) currentState.generatedWords24 else currentState.generatedWords12)
?: return
if (currentState.readyToImport.not()) return

val scanResponse = params.parentParams.scanResponse

modelScope.launch {
val result = tangemSdkManager.importWallet(
scanResponse = scanResponse,
mnemonic = mnemonic.mnemonicComponents.joinToString(" "),
passphrase = currentState.passphrase,
shouldReset = shouldReset,
passphrase = passphrase,
shouldReset = false,
)

when (result) {
Expand Down Expand Up @@ -176,6 +199,7 @@ internal class MultiWalletSeedPhraseModel @Inject constructor(
}
}
}
// =============================================

private fun handleActivationError() {
updateDialog(
Expand All @@ -187,7 +211,16 @@ internal class MultiWalletSeedPhraseModel @Inject constructor(
)
}

private fun resetCard() = importWallet(true)
private fun resetCard() {
val scanResponse = params.parentParams.scanResponse

modelScope.launch {
tangemSdkManager.resetToFactorySettings(
cardId = scanResponse.card.cardId,
allowsRequestAccessCodeFromRepository = true,
)
}
}

fun navigateToSupportScreen() {
modelScope.launch {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@ data class SeedPhraseState(
val generatedWords12: Mnemonic? = null,
val generatedWords24: Mnemonic? = null,
val words24Option: Boolean = false,
val importedMnemonic: Mnemonic? = null,
val passphrase: String? = null,
val readyToImport: Boolean = false,
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder

import com.tangem.crypto.bip39.Mnemonic
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

internal class GenerateSeedPhraseUiStateBuilder(
private val state: MutableStateFlow<SeedPhraseState>,
private val changeWords24Option: (Boolean) -> Unit,
private val updateUiState: (
(MultiWalletSeedPhraseUM.GenerateSeedPhrase) -> MultiWalletSeedPhraseUM.GenerateSeedPhrase,
) -> Unit,
Expand All @@ -25,7 +22,7 @@ internal class GenerateSeedPhraseUiStateBuilder(
}.toImmutableList()

return MultiWalletSeedPhraseUM.GenerateSeedPhrase(
words24OptionSelected = state.value.words24Option,
words24OptionSelected = false,
words = words12,
onWords24OptionSwitch = { switchType(words12, words24) },
onContinueClick = onContinue,
Expand All @@ -37,7 +34,7 @@ internal class GenerateSeedPhraseUiStateBuilder(
generatedWords24: ImmutableList<MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem>,
) {
updateUiState { uiSt ->
state.update { it.copy(words24Option = uiSt.words24OptionSelected.not()) }
changeWords24Option(uiSt.words24OptionSelected.not())

uiSt.copy(
words24OptionSelected = uiSt.words24OptionSelected.not(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,27 @@ import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.common.core.TangemSdkError
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.crypto.bip39.Mnemonic
import com.tangem.crypto.bip39.MnemonicErrorResult
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MnemonicRepository
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch

internal class ImportSeedPhraseUiStateBuilder(
private val modelScope: CoroutineScope,
private val state: MutableStateFlow<SeedPhraseState>,
private val mnemonicRepository: MnemonicRepository,
private val updateUiState: ((MultiWalletSeedPhraseUM.Import) -> MultiWalletSeedPhraseUM.Import) -> Unit,
private val importWallet: () -> Unit,
private val importWallet: (mnemonic: Mnemonic, passphrase: String?) -> Unit,
) {
private val wordsCheckJobHolder = JobHolder()
private var importedMnemonic: Mnemonic? = null
private var passphrase: String? = null

fun getState(): MultiWalletSeedPhraseUM {
return MultiWalletSeedPhraseUM.Import(
Expand All @@ -38,15 +37,21 @@ internal class ImportSeedPhraseUiStateBuilder(
}
},
passPhraseChange = {
state.update { st -> st.copy(passphrase = it.text) }
passphrase = it.text
updateUiState { state -> state.copy(passPhrase = it) }
},
onPassphraseInfoClick = ::showInfoBS,
createWalletClick = importWallet,
createWalletClick = ::onCreateWallet,
onSuggestionClick = { word -> addSuggestedWord(word) },
)
}

private fun onCreateWallet() {
val mnemonic = importedMnemonic ?: return
val passphrase = passphrase?.takeIf { it.isNotEmpty() }
importWallet(mnemonic, passphrase)
}

private fun addSuggestedWord(word: String) {
updateUiState { st ->
val text = st.words.text
Expand Down Expand Up @@ -102,7 +107,7 @@ internal class ImportSeedPhraseUiStateBuilder(
)
}

state.value = state.value.copy(importedMnemonic = null)
importedMnemonic = null

val text = wordsField.text
val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
Expand All @@ -120,7 +125,7 @@ internal class ImportSeedPhraseUiStateBuilder(

try {
val mnemonic = mnemonicRepository.generateMnemonic(text)
state.value = state.value.copy(importedMnemonic = mnemonic)
importedMnemonic = mnemonic
updateUiState {
it.copy(
invalidWords = emptyList<String>().toImmutableList(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.SeedPhraseState
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

internal class SeedPhraseCheckUiStateBuilder(
private val state: MutableStateFlow<SeedPhraseState>,
private val currentState: () -> SeedPhraseState,
private val currentUiState: () -> MultiWalletSeedPhraseUM,
private val updateUiState: (
(MultiWalletSeedPhraseUM.GeneratedWordsCheck) -> MultiWalletSeedPhraseUM.GeneratedWordsCheck,
) -> Unit,
private val importWallet: () -> Unit,
private val readyToImport: (Boolean) -> Unit,
) {

@Suppress("MagicNumber")
Expand Down Expand Up @@ -43,7 +42,7 @@ internal class SeedPhraseCheckUiStateBuilder(
)

val allFieldsCorrect = newState.allFieldsCorrect()
state.update { it.copy(readyToImport = allFieldsCorrect) }
readyToImport(allFieldsCorrect)

newState.copy(
createWalletButtonEnabled = allFieldsCorrect,
Expand Down Expand Up @@ -89,7 +88,7 @@ internal class SeedPhraseCheckUiStateBuilder(
}

private fun checkWordField(word: String, shownIndex: Int): Boolean {
val currentState = state.value
val currentState = currentState()

currentState.generatedWords12 ?: return false
currentState.generatedWords24 ?: return false
Expand Down

0 comments on commit 979dad8

Please sign in to comment.