diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 6edc1d9d21..5647ad7cfc 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -4,8 +4,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -19,6 +47,10 @@
+
+
+
+
diff --git a/features/onboarding-v2/impl/build.gradle.kts b/features/onboarding-v2/impl/build.gradle.kts
index 112c1b01f6..5e2dc1d547 100644
--- a/features/onboarding-v2/impl/build.gradle.kts
+++ b/features/onboarding-v2/impl/build.gradle.kts
@@ -25,6 +25,9 @@ dependencies {
implementation(projects.core.decompose)
implementation(projects.core.navigation)
+ /** Common */
+ implementation(projects.common.ui)
+
/** Domain */
implementation(projects.domain.models)
implementation(projects.domain.feedback)
@@ -44,7 +47,7 @@ dependencies {
implementation(deps.lifecycle.runtime.ktx)
/** Compose libraries */
- implementation(deps.compose.material) // to use buttons
+ implementation(deps.compose.material) // to use buttons and text field in MultiWalletSeedPhraseImport.kt
implementation(deps.compose.material3)
implementation(deps.compose.animation)
implementation(deps.compose.foundation)
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt
index 0ab365e362..3f68118904 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/MultiWalletCreateWalletComponent.kt
@@ -15,7 +15,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
import kotlinx.coroutines.launch
-class MultiWalletCreateWalletComponent(
+internal class MultiWalletCreateWalletComponent(
context: AppComponentContext,
params: MultiWalletChildParams,
onNextStep: (OnboardingMultiWalletState.Step) -> Unit,
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt
index 88bb4abd0d..c934b818b3 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/MultiWalletCreateWalletModel.kt
@@ -13,6 +13,7 @@ import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
+import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@@ -24,7 +25,7 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@ComponentScoped
-class MultiWalletCreateWalletModel @Inject constructor(
+internal class MultiWalletCreateWalletModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt
index 6c881297c3..a8ff5a761f 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/MultiWalletCreateWallet.kt
@@ -21,14 +21,14 @@ import com.tangem.features.onboarding.v2.impl.R
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
@Composable
-fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier = Modifier) {
+internal fun MultiWalletCreateWallet(state: MultiWalletCreateWalletUM, modifier: Modifier = Modifier) {
if (state.dialog != null) {
BasicDialog(
title = state.dialog.title.resolveReference(),
- message = state.dialog.description.resolveReference(),
+ message = state.dialog.message.resolveReference(),
confirmButton = DialogButtonUM(
title = state.dialog.confirmButtonText.resolveReference(),
- onClick = state.dialog.onConfirm,
+ onClick = state.dialog.onConfirmClick,
),
dismissButton = DialogButtonUM(
title = state.dialog.dismissButtonText.resolveReference(),
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt
index 59a5a9ace0..6f1af9d0a3 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/ui/state/MultiWalletCreateWalletUM.kt
@@ -1,23 +1,13 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state
import com.tangem.core.ui.extensions.TextReference
+import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
-data class MultiWalletCreateWalletUM(
+internal data class MultiWalletCreateWalletUM(
val title: TextReference,
val bodyText: TextReference,
val showOtherOptionsButton: Boolean,
val onCreateWalletClick: () -> Unit,
val onOtherOptionsClick: () -> Unit,
- val dialog: Dialog?,
-) {
- data class Dialog(
- val title: TextReference,
- val description: TextReference,
- val dismissButtonText: TextReference,
- val confirmButtonText: TextReference,
- val dismissWarningColor: Boolean = false,
- val onConfirm: () -> Unit,
- val onDismissButtonClick: () -> Unit,
- val onDismiss: () -> Unit,
- )
-}
\ No newline at end of file
+ val dialog: OnboardingDialogUM?,
+)
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt
index 505018842e..ce23af89e5 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/MultiWalletSeedPhraseComponent.kt
@@ -1,9 +1,15 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.view.WindowManager
import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
@@ -16,20 +22,24 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@Suppress("UnusedPrivateMember")
-class MultiWalletSeedPhraseComponent(
+internal class MultiWalletSeedPhraseComponent(
context: AppComponentContext,
params: MultiWalletChildParams,
onNextStep: (OnboardingMultiWalletState.Step) -> Unit,
backButtonClickFlow: SharedFlow,
) : AppComponentContext by context, MultiWalletChildComponent {
- private val model: MultiWalletSeedPhraseModel = getOrCreateModel()
+ private val model: MultiWalletSeedPhraseModel = getOrCreateModel(params)
init {
componentScope.launch {
- backButtonClickFlow.collect {
- model.onBack()
- }
+ backButtonClickFlow.collect { model.onBack() }
+ }
+ componentScope.launch {
+ model.onDone.collect { onNextStep(OnboardingMultiWalletState.Step.AddBackupDevice) }
+ }
+ componentScope.launch {
+ model.navigateBack.collect { onNextStep(OnboardingMultiWalletState.Step.CreateWallet) }
}
}
@@ -41,9 +51,30 @@ class MultiWalletSeedPhraseComponent(
model.onBack()
}
+ // disable screenshots
+ val activity = LocalContext.current.findActivity()
+ DisposableEffect(activity) {
+ activity.window.setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE,
+ )
+ onDispose {
+ activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+ }
+
MultiWalletSeedPhrase(
modifier = modifier,
state = state,
)
}
+
+ private fun Context.findActivity(): Activity {
+ var context = this
+ while (context is ContextWrapper) {
+ if (context is Activity) return context
+ context = context.baseContext
+ }
+ error("")
+ }
}
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt
index c867aaf14c..0200433e49 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/MultiWalletSeedPhraseModel.kt
@@ -1,46 +1,78 @@
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
+import com.tangem.common.CompletionResult
+import com.tangem.common.core.TangemSdkError
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.domain.card.repository.CardRepository
+import com.tangem.domain.feedback.GetCardInfoUseCase
+import com.tangem.domain.feedback.SendFeedbackEmailUseCase
+import com.tangem.domain.feedback.models.FeedbackEmailType
+import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.GenerateSeedPhraseUiStateBuilder
+import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.ImportSeedPhraseUiStateBuilder
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder.SeedPhraseCheckUiStateBuilder
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
+import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
+import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.resetCardDialog
+import com.tangem.sdk.api.TangemSdkManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import javax.inject.Inject
+@Suppress("LongParameterList")
@ComponentScoped
-class MultiWalletSeedPhraseModel @Inject constructor(
+internal class MultiWalletSeedPhraseModel @Inject constructor(
+ paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
private val mnemonicRepository: MnemonicRepository,
private val urlOpener: UrlOpener,
+ private val tangemSdkManager: TangemSdkManager,
+ private val cardRepository: CardRepository,
+ private val getCardInfoUseCase: GetCardInfoUseCase,
+ private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
) : Model() {
+ private val params = paramsContainer.require()
+ private val multiWalletState get() = params.multiWalletState
private val state = MutableStateFlow(SeedPhraseState())
private val _uiState = MutableStateFlow(getStartState())
private val generateSeedPhraseUiStateBuilder = GenerateSeedPhraseUiStateBuilder(
state = state,
updateUiState = { block -> updateUiStateSpecific(block) },
- onContinue = { openWordsCheck() },
+ onContinue = ::openWordsCheck,
)
private val seedPhraseCheckUiStateBuilder = SeedPhraseCheckUiStateBuilder(
state = state,
currentUiState = { _uiState.value },
updateUiState = { block -> updateUiStateSpecific(block) },
- importWallet = { },
+ importWallet = ::importWallet,
+ )
+
+ private val importSeedPhraseUiStateBuilder = ImportSeedPhraseUiStateBuilder(
+ modelScope = modelScope,
+ state = state,
+ mnemonicRepository = mnemonicRepository,
+ updateUiState = { block -> updateUiStateSpecific(block) },
+ importWallet = ::importWallet,
)
val uiState = _uiState.asStateFlow()
+ val onDone = MutableSharedFlow()
+ val navigateBack = MutableSharedFlow()
fun onBack() {
when (_uiState.value) {
is MultiWalletSeedPhraseUM.Start -> {
- // TODO navigate back
+ modelScope.launch { navigateBack.emit(Unit) }
}
is MultiWalletSeedPhraseUM.Import,
is MultiWalletSeedPhraseUM.GenerateSeedPhrase,
@@ -56,7 +88,9 @@ class MultiWalletSeedPhraseModel @Inject constructor(
private fun getStartState(): MultiWalletSeedPhraseUM {
return MultiWalletSeedPhraseUM.Start(
- onImportSeedPhraseClicked = {},
+ onImportSeedPhraseClicked = {
+ openImportSeedPhrase()
+ },
onGenerateSeedPhraseClicked = {
generateSeedPhrase()
openGeneratedSeedPhrase()
@@ -73,8 +107,6 @@ class MultiWalletSeedPhraseModel @Inject constructor(
val words12 = state.value.generatedWords12 ?: return
val words24 = state.value.generatedWords24 ?: return
- // TODO disallow screenshots
-
_uiState.value = generateSeedPhraseUiStateBuilder.getState(
generatedWords12 = words12,
generatedWords24 = words24,
@@ -85,6 +117,10 @@ class MultiWalletSeedPhraseModel @Inject constructor(
_uiState.value = seedPhraseCheckUiStateBuilder.getState()
}
+ private fun openImportSeedPhrase() {
+ _uiState.value = importSeedPhraseUiStateBuilder.getState()
+ }
+
private fun generateSeedPhrase() {
val words12 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words12)
val words24 = mnemonicRepository.generateMnemonic(MnemonicRepository.MnemonicType.Words24)
@@ -97,6 +133,79 @@ class MultiWalletSeedPhraseModel @Inject constructor(
}
}
+ private fun importWallet(shouldReset: Boolean = false) {
+ // TODO make stateless [REDACTED_TASK_KEY]
+ val currentState = state.value
+ val mnemonic = currentState.importedMnemonic
+ ?: (if (currentState.words24Option) currentState.generatedWords24 else currentState.generatedWords12)
+ ?: return
+
+ val scanResponse = params.parentParams.scanResponse
+
+ modelScope.launch {
+ val result = tangemSdkManager.importWallet(
+ scanResponse = scanResponse,
+ mnemonic = mnemonic.mnemonicComponents.joinToString(" "),
+ passphrase = currentState.passphrase,
+ shouldReset = shouldReset,
+ )
+
+ when (result) {
+ is CompletionResult.Success -> {
+ // TODO on success send analytics event
+ multiWalletState.update {
+ it.copy(
+ currentScanResponse = it.currentScanResponse.copy(
+ card = result.data.card,
+ derivedKeys = result.data.derivedKeys,
+ primaryCard = result.data.primaryCard,
+ ),
+ )
+ }
+
+ cardRepository.startCardActivation(cardId = result.data.card.cardId)
+
+ onDone.emit(Unit)
+ }
+ is CompletionResult.Failure -> {
+ if (result.error is TangemSdkError.WalletAlreadyCreated) {
+ // show should reset dialog
+ handleActivationError()
+ }
+ }
+ }
+ }
+ }
+
+ private fun handleActivationError() {
+ updateDialog(
+ resetCardDialog(
+ onConfirm = ::navigateToSupportScreen,
+ dismiss = { updateDialog(null) },
+ onDismissButtonClick = ::resetCard,
+ ),
+ )
+ }
+
+ private fun resetCard() = importWallet(true)
+
+ fun navigateToSupportScreen() {
+ modelScope.launch {
+ val cardInfo = getCardInfoUseCase(multiWalletState.value.currentScanResponse).getOrNull() ?: return@launch
+ sendFeedbackEmailUseCase(FeedbackEmailType.DirectUserRequest(cardInfo))
+ }
+ }
+
+ private fun updateDialog(dialog: OnboardingDialogUM?) {
+ _uiState.update { st ->
+ when (st) {
+ is MultiWalletSeedPhraseUM.GeneratedWordsCheck -> st.copy(dialog = dialog)
+ is MultiWalletSeedPhraseUM.Import -> st.copy(dialog = dialog)
+ else -> st
+ }
+ }
+ }
+
private inline fun updateUiStateSpecific(block: (T) -> T) {
_uiState.update {
if (it !is T) return@update it
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt
index 796cd1599a..2b734758c1 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/SeedPhraseState.kt
@@ -6,5 +6,7 @@ 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,
)
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt
index 2d45732a3a..d6f47d8af9 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/GenerateSeedPhraseUiStateBuilder.kt
@@ -8,7 +8,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
-class GenerateSeedPhraseUiStateBuilder(
+internal class GenerateSeedPhraseUiStateBuilder(
private val state: MutableStateFlow,
private val updateUiState: (
(MultiWalletSeedPhraseUM.GenerateSeedPhrase) -> MultiWalletSeedPhraseUM.GenerateSeedPhrase,
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt
new file mode 100644
index 0000000000..e81415f292
--- /dev/null
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/ImportSeedPhraseUiStateBuilder.kt
@@ -0,0 +1,163 @@
+package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.builder
+
+import androidx.compose.ui.text.TextRange
+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.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,
+ private val mnemonicRepository: MnemonicRepository,
+ private val updateUiState: ((MultiWalletSeedPhraseUM.Import) -> MultiWalletSeedPhraseUM.Import) -> Unit,
+ private val importWallet: () -> Unit,
+) {
+ private val wordsCheckJobHolder = JobHolder()
+
+ fun getState(): MultiWalletSeedPhraseUM {
+ return MultiWalletSeedPhraseUM.Import(
+ wordsChange = {
+ launchInterceptWords(wordsField = it)
+ suggestNextWord(it)
+ updateUiState { state ->
+ state.copy(words = it)
+ }
+ },
+ passPhraseChange = {
+ state.update { st -> st.copy(passphrase = it.text) }
+ updateUiState { state -> state.copy(passPhrase = it) }
+ },
+ onPassphraseInfoClick = ::showInfoBS,
+ createWalletClick = importWallet,
+ onSuggestionClick = { word -> addSuggestedWord(word) },
+ )
+ }
+
+ private fun addSuggestedWord(word: String) {
+ updateUiState { st ->
+ val text = st.words.text
+ val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
+ val newWords = wordsFromText.dropLast(1) + word
+ val newWordsText = newWords.joinToString(" ")
+ st.copy(
+ words = TextFieldValue(
+ text = newWordsText,
+ selection = TextRange(newWordsText.length),
+ ),
+ )
+ }
+ }
+
+ private fun suggestNextWord(wordsField: TextFieldValue) {
+ if (wordsField.text.endsWith(" ")) {
+ updateUiState { it.copy(suggestionsList = emptyList().toImmutableList()) }
+ return
+ }
+
+ val text = wordsField.text
+ val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
+ val lastWord = wordsFromText.lastOrNull() ?: return
+ if (lastWord.length < 2) return
+
+ if (mnemonicRepository.words.contains(lastWord) && mnemonicRepository.words.none { it.startsWith(lastWord) }) {
+ updateUiState { it.copy(suggestionsList = emptyList().toImmutableList()) }
+ return
+ }
+
+ updateUiState { st ->
+ st.copy(
+ suggestionsList = mnemonicRepository.words
+ .filter { it.startsWith(lastWord) && it != lastWord }
+ .toImmutableList(),
+ )
+ }
+ }
+
+ private fun launchInterceptWords(wordsField: TextFieldValue) {
+ modelScope.launch {
+ delay(timeMillis = 500)
+ interceptWords(wordsField)
+ }.saveIn(wordsCheckJobHolder)
+ }
+
+ private fun interceptWords(wordsField: TextFieldValue) {
+ updateUiState {
+ it.copy(
+ createWalletEnabled = false,
+ wordsErrorText = null,
+ )
+ }
+
+ state.value = state.value.copy(importedMnemonic = null)
+
+ val text = wordsField.text
+ val wordsFromText = text.split(" ").filter { it.isNotBlank() }.map { it.trim() }
+ val invalidWords = wordsFromText.filterNot { it in mnemonicRepository.words }
+ if (invalidWords.isNotEmpty()) {
+ updateUiState {
+ it.copy(
+ invalidWords = invalidWords.toImmutableList(),
+ wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_wrong_words),
+ createWalletEnabled = false,
+ )
+ }
+ return
+ }
+
+ try {
+ val mnemonic = mnemonicRepository.generateMnemonic(text)
+ state.value = state.value.copy(importedMnemonic = mnemonic)
+ updateUiState {
+ it.copy(
+ invalidWords = emptyList().toImmutableList(),
+ wordsErrorText = null,
+ createWalletEnabled = true,
+ )
+ }
+ } catch (ex: TangemSdkError.MnemonicException) {
+ val error = ex.mnemonicResult
+ if (error is MnemonicErrorResult.InvalidChecksum) {
+ updateUiState {
+ it.copy(
+ wordsErrorText = resourceReference(R.string.onboarding_seed_mnemonic_invalid_checksum),
+ createWalletEnabled = false,
+ )
+ }
+ } else {
+ updateUiState {
+ it.copy(
+ createWalletEnabled = false,
+ wordsErrorText = null,
+ )
+ }
+ }
+ }
+ }
+
+ private fun showInfoBS() {
+ updateUiState { state ->
+ state.copy(
+ infoBottomSheetConfig = TangemBottomSheetConfig.Empty.copy(
+ isShow = true,
+ onDismissRequest = {
+ updateUiState { it.copy(infoBottomSheetConfig = TangemBottomSheetConfig.Empty) }
+ },
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt
index eca9aa9570..d7f9df179e 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/model/builder/SeedPhraseCheckUiStateBuilder.kt
@@ -7,7 +7,7 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
-class SeedPhraseCheckUiStateBuilder(
+internal class SeedPhraseCheckUiStateBuilder(
private val state: MutableStateFlow,
private val currentUiState: () -> MultiWalletSeedPhraseUM,
private val updateUiState: (
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt
index 2d220858a2..5f2ea4aa18 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhrase.kt
@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
@Composable
-fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) {
+internal fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) {
AnimatedContent(
modifier = modifier.navigationBarsPadding(),
targetState = state,
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt
index 532ac00f37..10c3a39e4c 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseImport.kt
@@ -6,13 +6,14 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.style.TextAlign
@@ -30,8 +31,26 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.st
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils.InvalidWordsColorTransformation
import kotlinx.collections.immutable.ImmutableList
+@Suppress("LongMethod")
@Composable
-fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
+internal fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
+ if (state.dialog != null) {
+ BasicDialog(
+ title = state.dialog.title.resolveReference(),
+ message = state.dialog.message.resolveReference(),
+ confirmButton = DialogButtonUM(
+ title = state.dialog.confirmButtonText.resolveReference(),
+ onClick = state.dialog.onConfirmClick,
+ ),
+ dismissButton = DialogButtonUM(
+ title = state.dialog.dismissButtonText.resolveReference(),
+ warning = state.dialog.dismissWarningColor,
+ onClick = state.dialog.onDismissButtonClick,
+ ),
+ onDismissDialog = state.dialog.onDismiss,
+ )
+ }
+
Box(modifier.fillMaxSize()) {
Column(
modifier = Modifier
@@ -109,14 +128,16 @@ private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifie
Column(
modifier = modifier.fillMaxWidth(),
) {
+ // TODO migrate to material3
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size142),
- value = state.passPhrase,
- onValueChange = state.passPhraseChange,
+ value = state.words,
+ onValueChange = state.wordsChange,
textStyle = TangemTheme.typography.body1,
singleLine = false,
+ colors = TangemTextFieldsDefault.defaultTextFieldColors,
visualTransformation = invalidWordsColorTransformation,
)
@@ -151,6 +172,7 @@ private fun SuggestionsBlock(
visible = suggestionsList.isNotEmpty(),
) {
LazyRow(
+ modifier = Modifier.padding(bottom = 8.dp),
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
) {
items(suggestionsList.size) { index ->
@@ -171,13 +193,14 @@ private fun SuggestionsBlock(
@Composable
private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
- Box(modifier = modifier.clickable(onClick = onClick)) {
- Notifier(
- text = text,
- backgroundColor = TangemTheme.colors.icon.primary1,
- textColor = TangemTheme.colors.text.primary2,
- )
- }
+ Notifier(
+ text = text,
+ modifier = modifier
+ .clip(TangemTheme.shapes.roundedCorners8)
+ .clickable(onClick = onClick),
+ backgroundColor = TangemTheme.colors.icon.primary1,
+ textColor = TangemTheme.colors.text.primary2,
+ )
}
private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) {
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt
index 4e651c9944..94ffccee63 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWords.kt
@@ -26,7 +26,10 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Composable
-fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) {
+internal fun MultiWalletSeedPhraseWords(
+ state: MultiWalletSeedPhraseUM.GenerateSeedPhrase,
+ modifier: Modifier = Modifier,
+) {
Column(modifier.fillMaxSize()) {
SegmentSeedBlock(
modifier = Modifier.padding(vertical = 20.dp),
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt
index 8f26de11b8..f8ea2bbdba 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/MultiWalletSeedPhraseWordsCheck.kt
@@ -13,18 +13,38 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
+import com.tangem.core.ui.components.BasicDialog
+import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.components.OutlineTextField
import com.tangem.core.ui.components.PrimaryButtonIconEnd
+import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
import kotlinx.collections.immutable.persistentListOf
@Composable
-fun MultiWalletSeedPhraseWordsCheck(
+internal fun MultiWalletSeedPhraseWordsCheck(
state: MultiWalletSeedPhraseUM.GeneratedWordsCheck,
modifier: Modifier = Modifier,
) {
+ if (state.dialog != null) {
+ BasicDialog(
+ title = state.dialog.title.resolveReference(),
+ message = state.dialog.message.resolveReference(),
+ confirmButton = DialogButtonUM(
+ title = state.dialog.confirmButtonText.resolveReference(),
+ onClick = state.dialog.onConfirmClick,
+ ),
+ dismissButton = DialogButtonUM(
+ title = state.dialog.dismissButtonText.resolveReference(),
+ warning = state.dialog.dismissWarningColor,
+ onClick = state.dialog.onDismissButtonClick,
+ ),
+ onDismissDialog = state.dialog.onDismiss,
+ )
+ }
+
Column(
modifier = modifier
.fillMaxSize()
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt
index 84e5b22628..f13a24e712 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/seedphrase/ui/state/MultiWalletSeedPhraseUM.kt
@@ -4,11 +4,12 @@ import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.input.TextFieldValue
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.TextReference
+import com.tangem.features.onboarding.v2.multiwallet.impl.common.ui.OnboardingDialogUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Immutable
-sealed class MultiWalletSeedPhraseUM(
+internal sealed class MultiWalletSeedPhraseUM(
val order: Int,
) {
@@ -35,6 +36,7 @@ sealed class MultiWalletSeedPhraseUM(
val createWalletButtonEnabled: Boolean = false,
val createWalletButtonProgress: Boolean = false,
val onCreateWalletButtonClick: () -> Unit = {},
+ val dialog: OnboardingDialogUM? = null,
) : MultiWalletSeedPhraseUM(order = 2) {
data class WordField(
val index: Int,
@@ -58,5 +60,6 @@ sealed class MultiWalletSeedPhraseUM(
val suggestionsList: ImmutableList = persistentListOf(),
val onSuggestionClick: (String) -> Unit = {},
val infoBottomSheetConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty,
+ val dialog: OnboardingDialogUM? = null,
) : MultiWalletSeedPhraseUM(order = 1)
}
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/OnboardingDialogUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/OnboardingDialogUM.kt
new file mode 100644
index 0000000000..d5050f50e6
--- /dev/null
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/OnboardingDialogUM.kt
@@ -0,0 +1,15 @@
+package com.tangem.features.onboarding.v2.multiwallet.impl.common.ui
+
+import com.tangem.common.ui.alerts.models.AlertUM
+import com.tangem.core.ui.extensions.TextReference
+
+internal data class OnboardingDialogUM(
+ override val title: TextReference,
+ override val message: TextReference,
+ val dismissButtonText: TextReference,
+ override val confirmButtonText: TextReference,
+ val dismissWarningColor: Boolean = false,
+ override val onConfirmClick: () -> Unit,
+ val onDismissButtonClick: () -> Unit,
+ val onDismiss: () -> Unit,
+) : AlertUM
\ No newline at end of file
diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt
similarity index 65%
rename from features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt
rename to features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt
index d3b790586d..7ef775ba52 100644
--- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/createwallet/model/Dialogs.kt
+++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/common/ui/ResetCardDialog.kt
@@ -1,18 +1,17 @@
-package com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model
+package com.tangem.features.onboarding.v2.multiwallet.impl.common.ui
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.features.onboarding.v2.impl.R
-import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.ui.state.MultiWalletCreateWalletUM
internal fun resetCardDialog(onConfirm: () -> Unit, dismiss: () -> Unit, onDismissButtonClick: () -> Unit) =
- MultiWalletCreateWalletUM.Dialog(
+ OnboardingDialogUM(
title = resourceReference(R.string.onboarding_activation_error_title),
- description = resourceReference(R.string.onboarding_activation_error_message),
+ message = resourceReference(R.string.onboarding_activation_error_message),
confirmButtonText = resourceReference(R.string.common_support),
dismissButtonText = resourceReference(R.string.common_ok),
dismissWarningColor = true,
onDismiss = dismiss,
- onConfirm = {
+ onConfirmClick = {
onConfirm()
dismiss()
},