From 3744216681b314188effd1b8f432ad1d93c77dfc Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 11 Mar 2023 14:29:02 +0300 Subject: [PATCH 01/36] Updated on 2026-08-14 --- app/src/main/res/drawable/ic_onboarding_chat.xml | 9 +++++++++ .../res/drawable/ic_onboarding_qr_code_scan.xml | 13 +++++++++++++ .../main/res/drawable/ic_onboarding_text_edit.xml | 9 +++++++++ 3 files changed, 31 insertions(+) create mode 100644 app/src/main/res/drawable/ic_onboarding_chat.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml create mode 100644 app/src/main/res/drawable/ic_onboarding_text_edit.xml diff --git a/app/src/main/res/drawable/ic_onboarding_chat.xml b/app/src/main/res/drawable/ic_onboarding_chat.xml new file mode 100644 index 0000000000..a54ee4625a --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_chat.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml b/app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml new file mode 100644 index 0000000000..93e77daea8 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_onboarding_text_edit.xml b/app/src/main/res/drawable/ic_onboarding_text_edit.xml new file mode 100644 index 0000000000..11e420c412 --- /dev/null +++ b/app/src/main/res/drawable/ic_onboarding_text_edit.xml @@ -0,0 +1,9 @@ + + + From 5ee06b0a31ae3c6df63ad24b9529c65c964f5471 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 07:48:17 +0300 Subject: [PATCH 02/36] Updated on 2026-08-14 --- .../com/tangem/utils/coroutines/CoroutineExt.kt | 15 +++++++++++++-- .../feature/swap/viewmodels/SwapViewModel.kt | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 59452dbf7a..872aeb1c64 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -8,6 +8,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext suspend fun runCatching(dispatcher: CoroutineDispatcher, block: suspend () -> R): Result { return runCatching { @@ -25,11 +27,20 @@ class Debouncer { private var debounceJob: Job? = null - fun debounce(waitMs: Long = 300L, coroutineScope: CoroutineScope, destinationFunction: () -> Unit) { + fun debounce( + coroutineScope: CoroutineScope, + waitMs: Long = 300L, + context: CoroutineContext = EmptyCoroutineContext, + destinationFunction: suspend () -> Unit, + ) { debounceJob?.cancel() - debounceJob = coroutineScope.launch { + debounceJob = coroutineScope.launch(context) { delay(waitMs) destinationFunction.invoke() } } + + fun release() { + debounceJob?.cancel() + } } \ No newline at end of file diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt index 1c1ea1538f..ffd683f981 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/viewmodels/SwapViewModel.kt @@ -359,7 +359,7 @@ internal class SwapViewModel @Inject constructor( lastAmount.value = cutValue uiState = stateBuilder.updateSwapAmount(uiState, inputNumberFormatter.formatWithThousands(cutValue, decimals)) - amountDebouncer.debounce(DEBOUNCE_AMOUNT_DELAY, viewModelScope) { + amountDebouncer.debounce(viewModelScope, DEBOUNCE_AMOUNT_DELAY) { startLoadingQuotes(fromToken, toToken, lastAmount.value) } } From bd8a72194e1b33a4df11babc4a7e1b30ac1cb70f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 07:51:16 +0300 Subject: [PATCH 03/36] Updated on 2026-08-14 --- .../tangem/core/ui/components/TextFields.kt | 32 ++++++++++++------- .../com/tangem/core/ui/res/TangemColors.kt | 4 +++ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index eafe741803..78a37aad9f 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -141,19 +141,27 @@ private fun TangemTextField( }, trailingIcon = { val iconRes by rememberUpdatedState( - newValue = if (isError) R.drawable.ic_alert_24 else R.drawable.ic_close_24, + newValue = if (isError) { + R.drawable.ic_alert_24 + } else if (value.text.isNotEmpty()) { + R.drawable.ic_close_24 + } else { + null + }, ) - IconButton( - modifier = Modifier.size(32.dp), - onClick = onClear, - enabled = !isError && enabled, - ) { - Icon( - modifier = Modifier.size(24.dp), - painter = painterResource(id = iconRes), - tint = colors.trailingIconColor(enabled = enabled, isError = isError).value, - contentDescription = "Clear input", - ) + if (iconRes != null) { + IconButton( + modifier = Modifier.size(32.dp), + onClick = onClear, + enabled = !isError && enabled, + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(id = iconRes!!), + tint = colors.trailingIconColor(enabled = enabled, isError = isError).value, + contentDescription = "Clear input", + ) + } } }, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt index 8dc45fd453..ec957fea57 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt @@ -148,6 +148,7 @@ class TangemColors internal constructor( plain: Color, action: Color, fade: Color, + warning: Color = TangemColorPalette.Flamingo, ) { var primary by mutableStateOf(primary) private set @@ -159,6 +160,8 @@ class TangemColors internal constructor( private set var fade by mutableStateOf(fade) private set + var warning by mutableStateOf(warning) + private set fun update(other: Background) { primary = other.primary @@ -166,6 +169,7 @@ class TangemColors internal constructor( plain = other.plain action = other.action fade = other.fade + warning = other.warning } } From f0d95b079a0e29464c70a515b04d06f466f3c24f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 07:58:54 +0300 Subject: [PATCH 04/36] Updated on 2026-08-14 --- .../res/drawable/card_placeholder_black.xml | 88 +++++++++++++++++++ .../main/res/drawable/ic_arrow_top_right.xml | 9 ++ .../res/drawable/ic_onboarding_text_edit.xml | 0 3 files changed, 97 insertions(+) create mode 100644 core/ui/src/main/res/drawable/card_placeholder_black.xml create mode 100644 core/ui/src/main/res/drawable/ic_arrow_top_right.xml rename {app => core/ui}/src/main/res/drawable/ic_onboarding_text_edit.xml (100%) diff --git a/core/ui/src/main/res/drawable/card_placeholder_black.xml b/core/ui/src/main/res/drawable/card_placeholder_black.xml new file mode 100644 index 0000000000..83fab8e0f7 --- /dev/null +++ b/core/ui/src/main/res/drawable/card_placeholder_black.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/ui/src/main/res/drawable/ic_arrow_top_right.xml b/core/ui/src/main/res/drawable/ic_arrow_top_right.xml new file mode 100644 index 0000000000..e790dac466 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_arrow_top_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_onboarding_text_edit.xml b/core/ui/src/main/res/drawable/ic_onboarding_text_edit.xml similarity index 100% rename from app/src/main/res/drawable/ic_onboarding_text_edit.xml rename to core/ui/src/main/res/drawable/ic_onboarding_text_edit.xml From ce360d9c2bbc51e5738197b75ad9b63642a1443c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:00:01 +0300 Subject: [PATCH 05/36] Updated on 2026-08-14 --- app/build.gradle.kts | 1 + app/src/main/assets/tangem-app-config | 2 +- features/onboarding/.gitignore | 1 + features/onboarding/build.gradle.kts | 51 +++++++++++++++++++ .../onboarding/src/main/AndroidManifest.xml | 2 + .../extension/BaseExtensionConfigurations.kt | 1 + settings.gradle.kts | 1 + 7 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 features/onboarding/.gitignore create mode 100644 features/onboarding/build.gradle.kts create mode 100644 features/onboarding/src/main/AndroidManifest.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1655f303f0..ea77304740 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(project(":libs:auth")) /** Features */ + implementation(project(":features:onboarding")) implementation(project(":features:referral:presentation")) implementation(project(":features:referral:domain")) implementation(project(":features:referral:data")) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index c714678d9c..f5f6ac9548 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d +Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4 diff --git a/features/onboarding/.gitignore b/features/onboarding/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/onboarding/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts new file mode 100644 index 0000000000..4edec7192d --- /dev/null +++ b/features/onboarding/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + alias(deps.plugins.kotlin.serialization) + alias(deps.plugins.hilt.android) + id("configuration") +} + +dependencies { + /** Core modules */ + implementation(project(":core:featuretoggles")) + implementation(project(":core:datasource")) + implementation(project(":core:analytics")) + implementation(project(":core:utils")) + implementation(project(":core:ui")) + implementation(project(":core:res")) + + /** AndroidX libraries */ + implementation(deps.androidx.core.ktx) + implementation(deps.androidx.appCompat) + implementation(deps.androidx.fragment.ktx) + implementation(deps.androidx.constraintLayout) + implementation(deps.androidx.activity.compose) + implementation(deps.lifecycle.runtime.ktx) + implementation(deps.lifecycle.common.java8) + implementation(deps.lifecycle.viewModel.ktx) + + /** Compose libraries */ + implementation(deps.compose.material) + implementation(deps.compose.animation) + implementation(deps.compose.foundation) + implementation(deps.compose.ui) + implementation(deps.compose.ui.tooling) + implementation(deps.compose.coil) + implementation(deps.compose.shimmer) + + implementation(deps.tangem.card.core) + // implementation(deps.tangem.card.android) { + // exclude(module = "joda-time") + // } + + /** Other libraries */ + implementation(deps.compose.shimmer) + implementation(deps.kotlin.serialization) + implementation(deps.timber) + + /** DI */ + implementation(deps.hilt.android) + kapt(deps.hilt.kapt) +} \ No newline at end of file diff --git a/features/onboarding/src/main/AndroidManifest.xml b/features/onboarding/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..21015a0f53 --- /dev/null +++ b/features/onboarding/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt index 5d0b3a45e4..c1af6d55fe 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt @@ -20,6 +20,7 @@ internal fun BaseExtension.configureCompilerOptions() { internal fun BaseExtension.configureCompose(project: Project) { val useCompose = with(project.path) { contains(":ui") || + contains(":onboarding") || contains(":presentation") || contains(":app") || // TODO: [REDACTED_JIRA] contains(":tester:impl") // TODO: Rename module diff --git a/settings.gradle.kts b/settings.gradle.kts index d3f3310ef6..f644efa866 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ include(":libs:auth") // endregion Libs modules // region Feature modules +include(":features:onboarding") include(":features:referral:data") include(":features:referral:domain") include(":features:referral:presentation") From e78f9b8018cb4058482b2d1eef780b31b01d8cdc Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:06:12 +0300 Subject: [PATCH 06/36] Updated on 2026-08-14 --- .../onboarding/data/SeedPhraseRepository.kt | 107 ++++++++++++++++++ .../onboarding/domain/SeedPhraseChecker.kt | 16 +++ .../onboarding/domain/SeedPhraseInteractor.kt | 21 ++++ 3 files changed, 144 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt new file mode 100644 index 0000000000..017d0e1b7d --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt @@ -0,0 +1,107 @@ +package com.tangem.feature.onboarding.data + +import com.tangem.crypto.bip39.EntropyLength +import com.tangem.crypto.bip39.Wordlist + +/** +[REDACTED_AUTHOR] + */ +interface SeedPhraseRepository { + fun getWordList(): Wordlist +} + +class DummySeedPhraseRepository : SeedPhraseRepository { + override fun getWordList(): Wordlist = DummyWordlist() +} + +class DummyWordlist( + entropyLength: EntropyLength = EntropyLength.Bits128Length, + random: Boolean = false, +) : Wordlist { + + private var _wordlist: List = listOf() + + override val wordlist: List + get() = _wordlist + + init { + if (random) regenerateRandom() else regenerate(entropyLength) + } + + fun regenerateRandom(entropyLength: EntropyLength = EntropyLength.Bits128Length) { + val minWordLength = 4 + val maxWordLength = 10 + _wordlist = (0 until entropyLength.wordCount()) + .map { + randomString(maxLength = randomInt(minWordLength, maxWordLength)) + } + } + + fun regenerate(entropyLength: EntropyLength = EntropyLength.Bits128Length) { + _wordlist = getStaticWordlist().subList(0, entropyLength.wordCount() - 1) + } + + private fun randomString(maxLength: Int): String { + val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') + return (0..maxLength) + .map { randomInt(0, charPool.size) } + .map(charPool::get) + .joinToString("") + } + + private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to) + + private fun getStaticWordlist(): List { + return listOf( + "lorem", + "ipsum", + "dolor", + "amet", + "consectetur", + "adipiscing", + "elit", + "eiusmod", + "tempor", + "incididunt", + "labore", + "dolore", + "magna", + "aliqua", + "enim", + "minim", + "veniam", + "quis", + "nostrud", + "exercitation", + "ullamco", + "laboris", + "nisi", + "aliquip", + "commodo", + "consequat", + "duis", + "aute", + "irure", + "reprehenderit", + "voluptate", + "velit", + "esse", + "cillum", + "fugiat", + "nulla", + "pariatur", + "excepteur", + "sint", + "occaecat", + "cupidatat", + "proident", + "sunt", + "culpa", + "officia", + "deserunt", + "mollit", + "anim", + "laborum", + ) + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt new file mode 100644 index 0000000000..e7aa5c0315 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt @@ -0,0 +1,16 @@ +package com.tangem.feature.onboarding.domain + +/** +[REDACTED_AUTHOR] + */ +interface SeedPhraseChecker { + suspend fun isSeedPhraseMatch(seedPhrase: List): Boolean + suspend fun isPhraseMatch(phrase: String, source: List): Boolean +} + +class DummySeedPhraseChecker : SeedPhraseChecker { + + override suspend fun isSeedPhraseMatch(seedPhrase: List): Boolean = false + + override suspend fun isPhraseMatch(phrase: String, source: List): Boolean = source.contains(phrase) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt new file mode 100644 index 0000000000..fef12f2d0d --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.feature.onboarding.data.DummySeedPhraseRepository +import com.tangem.feature.onboarding.data.SeedPhraseRepository + +/** +[REDACTED_AUTHOR] + */ +interface SeedPhraseInteractor : SeedPhraseChecker { + suspend fun generateSeedPhrase(): List +} + +class DummySeedPhraseInteractor( + private val sdkSeedPhraseRepository: SeedPhraseRepository = DummySeedPhraseRepository(), + private val seedPhraseChecker: SeedPhraseChecker = DummySeedPhraseChecker(), +) : SeedPhraseInteractor, SeedPhraseChecker by seedPhraseChecker { + + override suspend fun generateSeedPhrase(): List { + return sdkSeedPhraseRepository.getWordList().wordlist + } +} \ No newline at end of file From 331a15b433be5a127b9a2ba48e6d0c36b0a2b6ce Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Apr 2023 11:33:11 +0300 Subject: [PATCH 07/36] Updated on 2026-08-14 --- ...{ic_onboarding_chat.xml => ic_onboarding_chat_24.xml} | 0 ...r_code_scan.xml => ic_onboarding_qr_code_scan_24.xml} | 0 .../src/main/java/com/tangem/core/ui/res/TangemColors.kt | 4 ---- core/ui/src/main/res/drawable/ic_arrow_top_right.xml | 9 --------- ...ding_text_edit.xml => ic_onboarding_text_edit_56.xml} | 0 features/onboarding/build.gradle.kts | 3 --- .../feature/onboarding/data/SeedPhraseRepository.kt | 2 +- .../feature/onboarding/domain/SeedPhraseInteractor.kt | 2 +- 8 files changed, 2 insertions(+), 18 deletions(-) rename app/src/main/res/drawable/{ic_onboarding_chat.xml => ic_onboarding_chat_24.xml} (100%) rename app/src/main/res/drawable/{ic_onboarding_qr_code_scan.xml => ic_onboarding_qr_code_scan_24.xml} (100%) delete mode 100644 core/ui/src/main/res/drawable/ic_arrow_top_right.xml rename core/ui/src/main/res/drawable/{ic_onboarding_text_edit.xml => ic_onboarding_text_edit_56.xml} (100%) diff --git a/app/src/main/res/drawable/ic_onboarding_chat.xml b/app/src/main/res/drawable/ic_onboarding_chat_24.xml similarity index 100% rename from app/src/main/res/drawable/ic_onboarding_chat.xml rename to app/src/main/res/drawable/ic_onboarding_chat_24.xml diff --git a/app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml b/app/src/main/res/drawable/ic_onboarding_qr_code_scan_24.xml similarity index 100% rename from app/src/main/res/drawable/ic_onboarding_qr_code_scan.xml rename to app/src/main/res/drawable/ic_onboarding_qr_code_scan_24.xml diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt index ec957fea57..8dc45fd453 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt @@ -148,7 +148,6 @@ class TangemColors internal constructor( plain: Color, action: Color, fade: Color, - warning: Color = TangemColorPalette.Flamingo, ) { var primary by mutableStateOf(primary) private set @@ -160,8 +159,6 @@ class TangemColors internal constructor( private set var fade by mutableStateOf(fade) private set - var warning by mutableStateOf(warning) - private set fun update(other: Background) { primary = other.primary @@ -169,7 +166,6 @@ class TangemColors internal constructor( plain = other.plain action = other.action fade = other.fade - warning = other.warning } } diff --git a/core/ui/src/main/res/drawable/ic_arrow_top_right.xml b/core/ui/src/main/res/drawable/ic_arrow_top_right.xml deleted file mode 100644 index e790dac466..0000000000 --- a/core/ui/src/main/res/drawable/ic_arrow_top_right.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/core/ui/src/main/res/drawable/ic_onboarding_text_edit.xml b/core/ui/src/main/res/drawable/ic_onboarding_text_edit_56.xml similarity index 100% rename from core/ui/src/main/res/drawable/ic_onboarding_text_edit.xml rename to core/ui/src/main/res/drawable/ic_onboarding_text_edit_56.xml diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts index 4edec7192d..848e2870d9 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -36,9 +36,6 @@ dependencies { implementation(deps.compose.shimmer) implementation(deps.tangem.card.core) - // implementation(deps.tangem.card.android) { - // exclude(module = "joda-time") - // } /** Other libraries */ implementation(deps.compose.shimmer) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt index 017d0e1b7d..42270141ed 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt @@ -21,7 +21,7 @@ class DummyWordlist( private var _wordlist: List = listOf() - override val wordlist: List + override val words: List get() = _wordlist init { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt index fef12f2d0d..8d8dadd98a 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -16,6 +16,6 @@ class DummySeedPhraseInteractor( ) : SeedPhraseInteractor, SeedPhraseChecker by seedPhraseChecker { override suspend fun generateSeedPhrase(): List { - return sdkSeedPhraseRepository.getWordList().wordlist + return sdkSeedPhraseRepository.getWordList().words } } \ No newline at end of file From 4d965de90964e59a335eebba3101f7bd21f44de6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:09:22 +0300 Subject: [PATCH 08/36] Updated on 2026-08-14 --- .../presentation/wallet2/model/ButtonState.kt | 12 ++++ .../wallet2/model/OnboardingDescription.kt | 23 ++++++ .../ui/components/OnboardingActionBlock.kt | 37 ++++++++++ .../components/OnboardingDescriptionBlock.kt | 71 +++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt new file mode 100644 index 0000000000..110301c580 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt @@ -0,0 +1,12 @@ +package com.tangem.feature.onboarding.presentation.wallet2.model + +/** +[REDACTED_AUTHOR] + */ +data class ButtonState( + val enabled: Boolean = true, + val showProgress: Boolean = false, + val onClick: () -> Unit = {}, +) { + val isClickable: Boolean = !showProgress +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt new file mode 100644 index 0000000000..022160a690 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt @@ -0,0 +1,23 @@ +package com.tangem.feature.onboarding.presentation.wallet2.model + +import android.content.Context +import androidx.annotation.StringRes + +data class OnboardingDescription( + @StringRes val titleRes: Int? = null, + @StringRes val subTitleRes: Int? = null, + val title: String? = null, + val subTitle: String? = null, +) { + fun hasTitle(): Boolean = title != null || titleRes != null + + fun hasSubTitle(): Boolean = subTitle != null || subTitleRes != null + + fun getTitle(context: Context): String = context.get(titleRes, title) + + fun getSubTitle(context: Context): String = context.get(subTitleRes, subTitle) + + private fun Context.get(@StringRes resId: Int?, text: String?): String { + return resId?.let { this.getString(it) } ?: text ?: "" + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt new file mode 100644 index 0000000000..1ec67e386f --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt @@ -0,0 +1,37 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH12 + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun OnboardingActionBlock( + firstActionContent: @Composable (() -> Unit)? = null, + secondActionContent: @Composable (() -> Unit)? = null, +) { + Box( + modifier = Modifier + .wrapContentSize() + .padding( + top = 8.dp, + bottom = 32.dp, + ), + ) { + Column( + modifier = Modifier.align(Alignment.BottomCenter), + ) { + firstActionContent?.invoke() + if (firstActionContent != null && secondActionContent != null) SpacerH12() + secondActionContent?.invoke() + } + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt new file mode 100644 index 0000000000..7b6e4e7808 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -0,0 +1,71 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription + +@Composable +fun OnboardingDescriptionBlock( + modifier: Modifier = Modifier, + descriptionsList: List = emptyList(), +) { + if (descriptionsList.isEmpty()) return + + Box( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 24.dp), + ) { + if (descriptionsList.size == 1) { + SingleDescription(descriptionsList[0]) + } else { + CarouselDescription(descriptionsList) + } + } +} + +@Composable +private fun DescriptionTitleText(text: String) { + Text( + text = text, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) +} + +@Composable +private fun DescriptionSubTitleText(text: String) { + Text( + text = text, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) +} + +@Composable +private fun SingleDescription(description: OnboardingDescription) { + Column { + DescriptionTitleText(text = description.getTitle(LocalContext.current)) + if (description.hasTitle()) SpacerH12() + DescriptionSubTitleText(text = description.getSubTitle(LocalContext.current)) + } +} + +@Composable +private fun CarouselDescription(descriptionsList: List) { + Box {} +} From 1c0820be324ac8397622427314a7653b6488e49b Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:16:19 +0300 Subject: [PATCH 09/36] Updated on 2026-08-14 --- .../model/OnboardingSeedPhraseState.kt | 65 ++++++ .../presentation/wallet2/model/UiActions.kt | 54 +++++ .../presentation/wallet2/ui/StateBuilder.kt | 211 ++++++++++++++++++ .../ui/components/OnboardingActionBlock.kt | 6 +- .../components/OnboardingDescriptionBlock.kt | 38 ++-- 5 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt new file mode 100644 index 0000000000..f1bffaeb72 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -0,0 +1,65 @@ +package com.tangem.feature.onboarding.presentation.wallet2.model + +import androidx.annotation.StringRes +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.domain.SeedPhraseError + +/** +[REDACTED_AUTHOR] + */ +data class OnboardingSeedPhraseState( + val step: OnboardingSeedPhraseStep = OnboardingSeedPhraseStep.Intro, + val introState: IntroState = IntroState(), + val aboutState: AboutState = AboutState(), + val yourSeedPhraseState: YourSeedPhraseState = YourSeedPhraseState(), + val checkSeedPhraseState: CheckSeedPhraseState = CheckSeedPhraseState(), + val importSeedPhraseState: ImportSeedPhraseState = ImportSeedPhraseState(), + val menuButtonChat: ButtonState = ButtonState(), + val isOnboardingFinished: Boolean = false, +) + +enum class OnboardingSeedPhraseStep { + Intro, AboutSeedPhrase, YourSeedPhrase, CheckSeedPhrase, ImportSeedPhrase +} + +data class IntroState( + val buttonCreateWallet: ButtonState = ButtonState(), + val buttonOtherOptions: ButtonState = ButtonState(), +) + +data class AboutState( + val buttonReadMoreAboutSeedPhrase: ButtonState = ButtonState(), + val buttonGenerateSeedPhrase: ButtonState = ButtonState(), + val buttonImportSeedPhrase: ButtonState = ButtonState(), +) + +data class YourSeedPhraseState( + val mnemonicComponents: List = listOf(), + val buttonContinue: ButtonState = ButtonState(), +) + +data class CheckSeedPhraseState( + val tvSecondPhrase: TextFieldState = TextFieldState(), + val tvSeventhPhrase: TextFieldState = TextFieldState(), + val tvEleventhPhrase: TextFieldState = TextFieldState(), + val buttonCreateWallet: ButtonState = ButtonState(), +) + +data class ImportSeedPhraseState( + val tvSeedPhrase: TextFieldState = TextFieldState(), + val invalidWords: Set = emptySet(), + val suggestionsList: List = emptyList(), + val error: SeedPhraseError? = null, + val onSuggestedPhraseClick: (Int) -> Unit = {}, + val buttonCreateWallet: ButtonState = ButtonState(), +) + +data class TextFieldState( + val textFieldValue: TextFieldValue = TextFieldValue(), + val label: String? = null, + @StringRes val labelRes: Int? = null, + val isError: Boolean = false, + val isFocused: Boolean = false, + val onTextFieldValueChanged: (TextFieldValue) -> Unit = {}, + val onFocusChanged: (Boolean) -> Unit = {}, +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt new file mode 100644 index 0000000000..59b9bfe541 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -0,0 +1,54 @@ +package com.tangem.feature.onboarding.presentation.wallet2.model + +import androidx.compose.ui.text.input.TextFieldValue + +/** +[REDACTED_AUTHOR] + */ +data class UiActions( + val introActions: IntroUiAction = IntroUiAction(), + val aboutActions: AboutUiAction = AboutUiAction(), + val yourSeedPhraseActions: YourSeedPhraseUiAction = YourSeedPhraseUiAction(), + val checkSeedPhraseActions: CheckSeedPhraseUiAction = CheckSeedPhraseUiAction(), + val importSeedPhraseActions: ImportSeedPhraseUiAction = ImportSeedPhraseUiAction(), + val menuChatClick: () -> Unit = {}, +) + +data class IntroUiAction( + val buttonCreateWalletClick: () -> Unit = {}, + val buttonOtherOptionsClick: () -> Unit = {}, +) + +data class AboutUiAction( + val buttonReadMoreAboutSeedPhraseClick: () -> Unit = {}, + val buttonGenerateSeedPhraseClick: () -> Unit = {}, + val buttonImportSeedPhraseClick: () -> Unit = {}, +) + +data class YourSeedPhraseUiAction( + val buttonContinueClick: () -> Unit = {}, +) + +data class CheckSeedPhraseUiAction( + val secondTextFieldAction: TextFieldUiAction = TextFieldUiAction(), + val seventhTextFieldAction: TextFieldUiAction = TextFieldUiAction(), + val eleventhTextFieldAction: TextFieldUiAction = TextFieldUiAction(), + val buttonCreateWalletClick: () -> Unit = {}, +) + +data class ImportSeedPhraseUiAction( + val phraseTextFieldAction: TextFieldUiAction = TextFieldUiAction(), + val suggestedPhraseClick: (Int) -> Unit = {}, + val buttonCreateWalletClick: () -> Unit = {}, +) + +data class TextFieldUiAction( + val onFocusChanged: (Boolean) -> Unit = { }, + val onTextFieldChanged: (TextFieldValue) -> Unit = { }, +) + +enum class SeedPhraseField { + Second, + Seventh, + Eleventh, +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt new file mode 100644 index 0000000000..171e780b26 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt @@ -0,0 +1,211 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState +import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState +import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep +import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField +import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState +import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions +import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState + +/** +[REDACTED_AUTHOR] + */ +class StateBuilder( + private val uiActions: UiActions, +) { + + val checkSeedPhrase: CheckSeedPhraseStateBuilder = CheckSeedPhraseStateBuilder() + + fun init(): OnboardingSeedPhraseState = OnboardingSeedPhraseState( + step = OnboardingSeedPhraseStep.Intro, + introState = IntroState( + buttonCreateWallet = ButtonState( + onClick = uiActions.introActions.buttonCreateWalletClick, + ), + buttonOtherOptions = ButtonState( + onClick = uiActions.introActions.buttonOtherOptionsClick, + ), + ), + aboutState = AboutState( + buttonReadMoreAboutSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonReadMoreAboutSeedPhraseClick, + ), + buttonGenerateSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonGenerateSeedPhraseClick, + ), + buttonImportSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonImportSeedPhraseClick, + ), + ), + yourSeedPhraseState = YourSeedPhraseState( + buttonContinue = ButtonState( + onClick = uiActions.yourSeedPhraseActions.buttonContinueClick, + ), + ), + checkSeedPhraseState = CheckSeedPhraseState( + tvSecondPhrase = TextFieldState( + label = "2", + isFocused = true, + onTextFieldValueChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onTextFieldChanged, + onFocusChanged = uiActions.checkSeedPhraseActions.secondTextFieldAction.onFocusChanged, + ), + tvSeventhPhrase = TextFieldState( + label = "7", + isFocused = false, + onTextFieldValueChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onTextFieldChanged, + onFocusChanged = uiActions.checkSeedPhraseActions.seventhTextFieldAction.onFocusChanged, + ), + tvEleventhPhrase = TextFieldState( + label = "11", + isFocused = false, + onTextFieldValueChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onTextFieldChanged, + onFocusChanged = uiActions.checkSeedPhraseActions.eleventhTextFieldAction.onFocusChanged, + ), + buttonCreateWallet = ButtonState( + enabled = false, + onClick = uiActions.checkSeedPhraseActions.buttonCreateWalletClick, + ), + ), + importSeedPhraseState = ImportSeedPhraseState( + tvSeedPhrase = TextFieldState( + onTextFieldValueChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onTextFieldChanged, + onFocusChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onFocusChanged, + ), + onSuggestedPhraseClick = uiActions.importSeedPhraseActions.suggestedPhraseClick, + buttonCreateWallet = ButtonState( + enabled = false, + onClick = uiActions.importSeedPhraseActions.buttonCreateWalletClick, + ), + ), + menuButtonChat = ButtonState( + onClick = uiActions.menuChatClick, + ), + ) + + fun changeStep( + uiState: OnboardingSeedPhraseState, + step: OnboardingSeedPhraseStep, + ): OnboardingSeedPhraseState = uiState.copy( + step = step, + ) + + fun generateMnemonicComponents( + uiState: OnboardingSeedPhraseState, + ): OnboardingSeedPhraseState { + return uiState.copy( + aboutState = uiState.aboutState.copy( + buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy( + showProgress = true, + ), + buttonImportSeedPhrase = uiState.aboutState.buttonImportSeedPhrase.copy( + enabled = false, + ), + ), + ) + } + + fun mnemonicGenerated( + uiState: OnboardingSeedPhraseState, + mnemonicComponents: List, + ): OnboardingSeedPhraseState { + return updateMnemonicComponents(uiState, mnemonicComponents) + .copy( + step = OnboardingSeedPhraseStep.YourSeedPhrase, + aboutState = uiState.aboutState.copy( + buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy( + showProgress = false, + ), + buttonImportSeedPhrase = uiState.aboutState.buttonImportSeedPhrase.copy( + enabled = true, + ), + ), + ) + } + + fun updateMnemonicComponents( + uiState: OnboardingSeedPhraseState, + phraseList: List, + ): OnboardingSeedPhraseState = uiState.copy( + yourSeedPhraseState = uiState.yourSeedPhraseState.copy( + mnemonicComponents = phraseList, + ), + ) +} + +class CheckSeedPhraseStateBuilder { + + fun updateTextField( + uiState: OnboardingSeedPhraseState, + field: SeedPhraseField, + textFieldValue: TextFieldValue, + ): OnboardingSeedPhraseState = when (field) { + SeedPhraseField.Second -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + SeedPhraseField.Seventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + SeedPhraseField.Eleventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + } + + fun updateTextFieldError( + uiState: OnboardingSeedPhraseState, + field: SeedPhraseField, + hasError: Boolean, + ): OnboardingSeedPhraseState = when (field) { + SeedPhraseField.Second -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( + isError = hasError, + ), + ), + ) + SeedPhraseField.Seventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( + isError = hasError, + ), + ), + ) + SeedPhraseField.Eleventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( + isError = hasError, + ), + ), + ) + } + + fun updateCreateWalletButton( + uiState: OnboardingSeedPhraseState, + enabled: Boolean, + ): OnboardingSeedPhraseState { + return uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + buttonCreateWallet = uiState.checkSeedPhraseState.buttonCreateWallet.copy( + enabled = enabled, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt index 1ec67e386f..e8f9731e70 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt @@ -7,8 +7,8 @@ import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.res.TangemTheme /** [REDACTED_AUTHOR] @@ -22,8 +22,8 @@ fun OnboardingActionBlock( modifier = Modifier .wrapContentSize() .padding( - top = 8.dp, - bottom = 32.dp, + top = TangemTheme.dimens.size8, + bottom = TangemTheme.dimens.size32, ), ) { Column( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index 7b6e4e7808..f8ebabb624 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -9,11 +9,13 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +/** + * UI component witch provides single and carousel description for the onboarding process + */ @Composable fun OnboardingDescriptionBlock( modifier: Modifier = Modifier, @@ -24,7 +26,7 @@ fun OnboardingDescriptionBlock( Box( modifier = modifier .fillMaxWidth() - .padding(horizontal = 24.dp), + .padding(horizontal = TangemTheme.dimens.size24), ) { if (descriptionsList.size == 1) { SingleDescription(descriptionsList[0]) @@ -34,6 +36,22 @@ fun OnboardingDescriptionBlock( } } +@Composable +private fun SingleDescription(description: OnboardingDescription) { + Column { + if (description.hasTitle()) { + DescriptionTitleText(text = description.getTitle(LocalContext.current)) + SpacerH12() + } + DescriptionSubTitleText(text = description.getSubTitle(LocalContext.current)) + } +} + +@Composable +private fun CarouselDescription(descriptionsList: List) { + //TODO: implement +} + @Composable private fun DescriptionTitleText(text: String) { Text( @@ -54,18 +72,4 @@ private fun DescriptionSubTitleText(text: String) { textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) -} - -@Composable -private fun SingleDescription(description: OnboardingDescription) { - Column { - DescriptionTitleText(text = description.getTitle(LocalContext.current)) - if (description.hasTitle()) SpacerH12() - DescriptionSubTitleText(text = description.getSubTitle(LocalContext.current)) - } -} - -@Composable -private fun CarouselDescription(descriptionsList: List) { - Box {} -} +} \ No newline at end of file From a6b259ac6cb797d080ef9bdaa17d7758e7aff6d9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 10 Apr 2023 14:55:39 +0300 Subject: [PATCH 10/36] Updated on 2026-08-14 --- features/onboarding/build.gradle.kts | 1 + .../presentation/wallet2/model/ButtonState.kt | 7 +- .../wallet2/model/OnboardingDescription.kt | 21 +---- .../model/OnboardingSeedPhraseState.kt | 44 +++++----- .../presentation/wallet2/model/UiActions.kt | 42 +++++----- .../wallet2/ui/CheckSeedPhraseStateBuilder.kt | 80 +++++++++++++++++++ .../presentation/wallet2/ui/StateBuilder.kt | 74 ----------------- .../ui/components/OnboardingActionBlock.kt | 4 +- .../components/OnboardingDescriptionBlock.kt | 20 ++--- 9 files changed, 141 insertions(+), 152 deletions(-) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts index 848e2870d9..e36d079c6b 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { /** Other libraries */ implementation(deps.compose.shimmer) + implementation(deps.kotlin.immutable.collections) implementation(deps.kotlin.serialization) implementation(deps.timber) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt index 110301c580..842a7a5141 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/ButtonState.kt @@ -5,8 +5,7 @@ package com.tangem.feature.onboarding.presentation.wallet2.model */ data class ButtonState( val enabled: Boolean = true, + val isClickable: Boolean = true, val showProgress: Boolean = false, - val onClick: () -> Unit = {}, -) { - val isClickable: Boolean = !showProgress -} \ No newline at end of file + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt index 022160a690..b1be694b19 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt @@ -1,23 +1,8 @@ package com.tangem.feature.onboarding.presentation.wallet2.model -import android.content.Context import androidx.annotation.StringRes data class OnboardingDescription( - @StringRes val titleRes: Int? = null, - @StringRes val subTitleRes: Int? = null, - val title: String? = null, - val subTitle: String? = null, -) { - fun hasTitle(): Boolean = title != null || titleRes != null - - fun hasSubTitle(): Boolean = subTitle != null || subTitleRes != null - - fun getTitle(context: Context): String = context.get(titleRes, title) - - fun getSubTitle(context: Context): String = context.get(subTitleRes, subTitle) - - private fun Context.get(@StringRes resId: Int?, text: String?): String { - return resId?.let { this.getString(it) } ?: text ?: "" - } -} \ No newline at end of file + @StringRes val titleRes: Int, + @StringRes val subTitleRes: Int, +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index f1bffaeb72..1e4781ae22 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -1,6 +1,5 @@ package com.tangem.feature.onboarding.presentation.wallet2.model -import androidx.annotation.StringRes import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.domain.SeedPhraseError @@ -8,13 +7,13 @@ import com.tangem.feature.onboarding.domain.SeedPhraseError [REDACTED_AUTHOR] */ data class OnboardingSeedPhraseState( - val step: OnboardingSeedPhraseStep = OnboardingSeedPhraseStep.Intro, - val introState: IntroState = IntroState(), - val aboutState: AboutState = AboutState(), - val yourSeedPhraseState: YourSeedPhraseState = YourSeedPhraseState(), - val checkSeedPhraseState: CheckSeedPhraseState = CheckSeedPhraseState(), - val importSeedPhraseState: ImportSeedPhraseState = ImportSeedPhraseState(), - val menuButtonChat: ButtonState = ButtonState(), + val step: OnboardingSeedPhraseStep, + val introState: IntroState, + val aboutState: AboutState, + val yourSeedPhraseState: YourSeedPhraseState, + val checkSeedPhraseState: CheckSeedPhraseState, + val importSeedPhraseState: ImportSeedPhraseState, + val menuButtonChat: ButtonState, val isOnboardingFinished: Boolean = false, ) @@ -23,43 +22,42 @@ enum class OnboardingSeedPhraseStep { } data class IntroState( - val buttonCreateWallet: ButtonState = ButtonState(), - val buttonOtherOptions: ButtonState = ButtonState(), + val buttonCreateWallet: ButtonState, + val buttonOtherOptions: ButtonState, ) data class AboutState( - val buttonReadMoreAboutSeedPhrase: ButtonState = ButtonState(), - val buttonGenerateSeedPhrase: ButtonState = ButtonState(), - val buttonImportSeedPhrase: ButtonState = ButtonState(), + val buttonReadMoreAboutSeedPhrase: ButtonState, + val buttonGenerateSeedPhrase: ButtonState, + val buttonImportSeedPhrase: ButtonState, ) data class YourSeedPhraseState( val mnemonicComponents: List = listOf(), - val buttonContinue: ButtonState = ButtonState(), + val buttonContinue: ButtonState, ) data class CheckSeedPhraseState( - val tvSecondPhrase: TextFieldState = TextFieldState(), - val tvSeventhPhrase: TextFieldState = TextFieldState(), - val tvEleventhPhrase: TextFieldState = TextFieldState(), - val buttonCreateWallet: ButtonState = ButtonState(), + val tvSecondPhrase: TextFieldState, + val tvSeventhPhrase: TextFieldState, + val tvEleventhPhrase: TextFieldState, + val buttonCreateWallet: ButtonState, ) data class ImportSeedPhraseState( - val tvSeedPhrase: TextFieldState = TextFieldState(), + val tvSeedPhrase: TextFieldState, + val onSuggestedPhraseClick: (Int) -> Unit, + val buttonCreateWallet: ButtonState, val invalidWords: Set = emptySet(), val suggestionsList: List = emptyList(), val error: SeedPhraseError? = null, - val onSuggestedPhraseClick: (Int) -> Unit = {}, - val buttonCreateWallet: ButtonState = ButtonState(), ) data class TextFieldState( + val onTextFieldValueChanged: (TextFieldValue) -> Unit, val textFieldValue: TextFieldValue = TextFieldValue(), val label: String? = null, - @StringRes val labelRes: Int? = null, val isError: Boolean = false, val isFocused: Boolean = false, - val onTextFieldValueChanged: (TextFieldValue) -> Unit = {}, val onFocusChanged: (Boolean) -> Unit = {}, ) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index 59b9bfe541..db5573c0fa 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -6,45 +6,45 @@ import androidx.compose.ui.text.input.TextFieldValue [REDACTED_AUTHOR] */ data class UiActions( - val introActions: IntroUiAction = IntroUiAction(), - val aboutActions: AboutUiAction = AboutUiAction(), - val yourSeedPhraseActions: YourSeedPhraseUiAction = YourSeedPhraseUiAction(), - val checkSeedPhraseActions: CheckSeedPhraseUiAction = CheckSeedPhraseUiAction(), - val importSeedPhraseActions: ImportSeedPhraseUiAction = ImportSeedPhraseUiAction(), - val menuChatClick: () -> Unit = {}, + val introActions: IntroUiAction, + val aboutActions: AboutUiAction, + val yourSeedPhraseActions: YourSeedPhraseUiAction, + val checkSeedPhraseActions: CheckSeedPhraseUiAction, + val importSeedPhraseActions: ImportSeedPhraseUiAction, + val menuChatClick: () -> Unit, ) data class IntroUiAction( - val buttonCreateWalletClick: () -> Unit = {}, - val buttonOtherOptionsClick: () -> Unit = {}, + val buttonCreateWalletClick: () -> Unit, + val buttonOtherOptionsClick: () -> Unit, ) data class AboutUiAction( - val buttonReadMoreAboutSeedPhraseClick: () -> Unit = {}, - val buttonGenerateSeedPhraseClick: () -> Unit = {}, - val buttonImportSeedPhraseClick: () -> Unit = {}, + val buttonReadMoreAboutSeedPhraseClick: () -> Unit, + val buttonGenerateSeedPhraseClick: () -> Unit, + val buttonImportSeedPhraseClick: () -> Unit, ) data class YourSeedPhraseUiAction( - val buttonContinueClick: () -> Unit = {}, + val buttonContinueClick: () -> Unit, ) data class CheckSeedPhraseUiAction( - val secondTextFieldAction: TextFieldUiAction = TextFieldUiAction(), - val seventhTextFieldAction: TextFieldUiAction = TextFieldUiAction(), - val eleventhTextFieldAction: TextFieldUiAction = TextFieldUiAction(), - val buttonCreateWalletClick: () -> Unit = {}, + val secondTextFieldAction: TextFieldUiAction, + val seventhTextFieldAction: TextFieldUiAction, + val eleventhTextFieldAction: TextFieldUiAction, + val buttonCreateWalletClick: () -> Unit, ) data class ImportSeedPhraseUiAction( - val phraseTextFieldAction: TextFieldUiAction = TextFieldUiAction(), - val suggestedPhraseClick: (Int) -> Unit = {}, - val buttonCreateWalletClick: () -> Unit = {}, + val phraseTextFieldAction: TextFieldUiAction, + val suggestedPhraseClick: (Int) -> Unit, + val buttonCreateWalletClick: () -> Unit, ) data class TextFieldUiAction( - val onFocusChanged: (Boolean) -> Unit = { }, - val onTextFieldChanged: (TextFieldValue) -> Unit = { }, + val onFocusChanged: (Boolean) -> Unit, + val onTextFieldChanged: (TextFieldValue) -> Unit, ) enum class SeedPhraseField { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt new file mode 100644 index 0000000000..cb69985684 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt @@ -0,0 +1,80 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField + +/** +[REDACTED_AUTHOR] + */ +class CheckSeedPhraseStateBuilder { + + fun updateTextField( + uiState: OnboardingSeedPhraseState, + field: SeedPhraseField, + textFieldValue: TextFieldValue, + ): OnboardingSeedPhraseState = when (field) { + SeedPhraseField.Second -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + SeedPhraseField.Seventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + SeedPhraseField.Eleventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + } + + fun updateTextFieldError( + uiState: OnboardingSeedPhraseState, + field: SeedPhraseField, + hasError: Boolean, + ): OnboardingSeedPhraseState = when (field) { + SeedPhraseField.Second -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( + isError = hasError, + ), + ), + ) + SeedPhraseField.Seventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( + isError = hasError, + ), + ), + ) + SeedPhraseField.Eleventh -> uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( + isError = hasError, + ), + ), + ) + } + + fun updateCreateWalletButton( + uiState: OnboardingSeedPhraseState, + enabled: Boolean, + ): OnboardingSeedPhraseState { + return uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + buttonCreateWallet = uiState.checkSeedPhraseState.buttonCreateWallet.copy( + enabled = enabled, + ), + ), + ) + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt index 171e780b26..2819421add 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt @@ -1,6 +1,5 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui -import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState @@ -8,7 +7,6 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhrase import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep -import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState @@ -136,76 +134,4 @@ class StateBuilder( mnemonicComponents = phraseList, ), ) -} - -class CheckSeedPhraseStateBuilder { - - fun updateTextField( - uiState: OnboardingSeedPhraseState, - field: SeedPhraseField, - textFieldValue: TextFieldValue, - ): OnboardingSeedPhraseState = when (field) { - SeedPhraseField.Second -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( - textFieldValue = textFieldValue, - ), - ), - ) - SeedPhraseField.Seventh -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( - textFieldValue = textFieldValue, - ), - ), - ) - SeedPhraseField.Eleventh -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( - textFieldValue = textFieldValue, - ), - ), - ) - } - - fun updateTextFieldError( - uiState: OnboardingSeedPhraseState, - field: SeedPhraseField, - hasError: Boolean, - ): OnboardingSeedPhraseState = when (field) { - SeedPhraseField.Second -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvSecondPhrase = uiState.checkSeedPhraseState.tvSecondPhrase.copy( - isError = hasError, - ), - ), - ) - SeedPhraseField.Seventh -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvSeventhPhrase = uiState.checkSeedPhraseState.tvSeventhPhrase.copy( - isError = hasError, - ), - ), - ) - SeedPhraseField.Eleventh -> uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - tvEleventhPhrase = uiState.checkSeedPhraseState.tvEleventhPhrase.copy( - isError = hasError, - ), - ), - ) - } - - fun updateCreateWalletButton( - uiState: OnboardingSeedPhraseState, - enabled: Boolean, - ): OnboardingSeedPhraseState { - return uiState.copy( - checkSeedPhraseState = uiState.checkSeedPhraseState.copy( - buttonCreateWallet = uiState.checkSeedPhraseState.buttonCreateWallet.copy( - enabled = enabled, - ), - ), - ) - } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt index e8f9731e70..d8992181e0 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt @@ -3,7 +3,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui.components import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -18,9 +17,10 @@ fun OnboardingActionBlock( firstActionContent: @Composable (() -> Unit)? = null, secondActionContent: @Composable (() -> Unit)? = null, ) { + if (firstActionContent == null && secondActionContent == null) return + Box( modifier = Modifier - .wrapContentSize() .padding( top = TangemTheme.dimens.size8, bottom = TangemTheme.dimens.size32, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index f8ebabb624..73f52f656b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -7,11 +7,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf /** * UI component witch provides single and carousel description for the onboarding process @@ -19,7 +21,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescri @Composable fun OnboardingDescriptionBlock( modifier: Modifier = Modifier, - descriptionsList: List = emptyList(), + descriptionsList: ImmutableList = persistentListOf(), ) { if (descriptionsList.isEmpty()) return @@ -29,7 +31,7 @@ fun OnboardingDescriptionBlock( .padding(horizontal = TangemTheme.dimens.size24), ) { if (descriptionsList.size == 1) { - SingleDescription(descriptionsList[0]) + Description(descriptionsList[0]) } else { CarouselDescription(descriptionsList) } @@ -37,18 +39,16 @@ fun OnboardingDescriptionBlock( } @Composable -private fun SingleDescription(description: OnboardingDescription) { +private fun Description(description: OnboardingDescription) { Column { - if (description.hasTitle()) { - DescriptionTitleText(text = description.getTitle(LocalContext.current)) - SpacerH12() - } - DescriptionSubTitleText(text = description.getSubTitle(LocalContext.current)) + DescriptionTitleText(text = stringResource(id = description.titleRes)) + SpacerH12() + DescriptionSubTitleText(text = stringResource(id = description.subTitleRes)) } } @Composable -private fun CarouselDescription(descriptionsList: List) { +private fun CarouselDescription(descriptionsList: ImmutableList) { //TODO: implement } From 540734bb2ea2af3cecf13123e14fa1ffd4e6a89d Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:20:21 +0300 Subject: [PATCH 11/36] Updated on 2026-08-14 --- .../presentation/wallet2/ui/IntroScreen.kt | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt new file mode 100644 index 0000000000..84f281e56e --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -0,0 +1,121 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun IntroScreen( + modifier: Modifier = Modifier, + state: IntroState, +) { + Column( + modifier = modifier.fillMaxSize(), + ) { + Box( + modifier = Modifier.weight(1.5f), + ) { + //FIXME: path the url from the state + CardImageBlock("url") + } + Box( + modifier = Modifier.weight(1f), + ) { + OnboardingDescriptionBlock( + descriptionsList = listOf( + OnboardingDescription( + titleRes = R.string.onboarding_create_wallet_options_title, + subTitleRes = R.string.onboarding_create_wallet_options_message, + ), + ), + ) + } + Box( + modifier = Modifier.wrapContentSize(), + ) { + OnboardingActionBlock( + firstActionContent = { + PrimaryButtonIconLeft( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + icon = painterResource(id = R.drawable.ic_tangem_24), + enabled = state.buttonCreateWallet.enabled, + showProgress = state.buttonCreateWallet.showProgress, + onClick = state.buttonCreateWallet.onClick, + ) + }, + secondActionContent = { + SecondaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_create_wallet_options_button_options), + enabled = state.buttonOtherOptions.enabled, + showProgress = state.buttonOtherOptions.showProgress, + onClick = state.buttonOtherOptions.onClick, + ) + }, + ) + } + } +} + +@Composable +private fun CardImageBlock( + cardImageUrl: String? = null, +) { + Box(modifier = Modifier.fillMaxSize()) { + val cardImageModifier = Modifier + .fillMaxSize() + .align(Alignment.Center) + + SubcomposeAsyncImage( + modifier = cardImageModifier.padding(horizontal = 34.dp), + model = ImageRequest.Builder(LocalContext.current) + .data(cardImageUrl) + .crossfade(true) + .build(), + contentDescription = "Tangem Card", + loading = { CardPlaceHolder(cardImageModifier) }, + error = { CardPlaceHolder(cardImageModifier) }, + colorFilter = null, + ) + } +} + +@Composable +private fun CardPlaceHolder(modifier: Modifier) { + Image( + modifier = modifier, + painter = painterResource(id = R.drawable.card_placeholder_black), + contentDescription = "Tangem Card placeholder", + contentScale = ContentScale.Fit, + ) +} \ No newline at end of file From 6ac3fc8b8d49c8df36d5903fb84084d38c8d5bfd Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:21:21 +0300 Subject: [PATCH 12/36] Updated on 2026-08-14 --- .../wallet2/ui/AboutSeedPhraseScreen.kt | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt new file mode 100644 index 0000000000..a394bafc0a --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -0,0 +1,159 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SecondaryButton +import com.tangem.core.ui.components.SpacerH32 +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun AboutSeedPhraseScreen( + modifier: Modifier = Modifier, + state: AboutState = AboutState(), +) { + Column( + modifier = modifier.fillMaxSize(), + ) { + Box( + modifier = Modifier.weight(0.8f), + ) { + EditIconBlock() + } + Box( + modifier = Modifier.weight(1.2f), + ) { + Column { + OnboardingDescriptionBlock( + descriptionsList = listOf( + OnboardingDescription( + titleRes = R.string.onboarding_seed_intro_title, + subTitleRes = R.string.onboarding_seed_intro_message, + ), + ), + ) + ReadMoreBlock(state) + } + } + + Box( + modifier = Modifier.wrapContentSize(), + ) { + OnboardingActionBlock( + firstActionContent = { + SecondaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_seed_intro_button_generate), + enabled = state.buttonGenerateSeedPhrase.enabled, + showProgress = state.buttonGenerateSeedPhrase.showProgress, + onClick = state.buttonGenerateSeedPhrase.onClick, + ) + }, + secondActionContent = { + SecondaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_seed_intro_button_import), + enabled = state.buttonImportSeedPhrase.enabled, + showProgress = state.buttonImportSeedPhrase.showProgress, + onClick = state.buttonImportSeedPhrase.onClick, + ) + }, + ) + } + } +} + +@Composable +private fun EditIconBlock() { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Image( + modifier = Modifier + .size(44.dp), + painter = painterResource(id = R.drawable.ic_onboarding_text_edit), + contentDescription = "Edit icon", + ) + SpacerH32() + Box( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background(Color(0x1EE10101)), + ) { + Text( + modifier = Modifier + .padding(horizontal = 12.dp, vertical = 4.dp), + text = stringResource(id = R.string.onboarding_seed_phrase_intro_legacy), + color = TangemTheme.colors.text.warning, + style = TangemTheme.typography.body1, + ) + } + + } +} + +@Composable +private fun ReadMoreBlock(state: AboutState) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) { + val shape = RoundedCornerShape(24.dp) + Row( + modifier = Modifier + .align(Alignment.Center) + .clip(shape) + .border(1.dp, Color.Gray, shape) + .clickable(onClick = state.buttonReadMoreAboutSeedPhrase.onClick) + .padding( + horizontal = 16.dp, + vertical = 8.dp, + ), + ) { + Image( + painter = painterResource(id = R.drawable.ic_arrow_top_right), + contentDescription = "Go away icon", + ) + SpacerW8() + Text( + text = stringResource(id = R.string.onboarding_seed_button_read_more), + ) + } + } +} \ No newline at end of file From ca35718e95404a6041080c84f744181fe9820906 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:22:22 +0300 Subject: [PATCH 13/36] Updated on 2026-08-14 --- .../wallet2/ui/YourSeedPhraseScreen.kt | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt new file mode 100644 index 0000000000..fa8c4c0e56 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -0,0 +1,103 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SpacerW32 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun YourSeedPhraseScreen( + modifier: Modifier = Modifier, + state: YourSeedPhraseState = YourSeedPhraseState(), +) { + Column( + modifier = modifier.fillMaxSize(), + ) { + Box( + modifier = Modifier.weight(0.6f), + ) { + OnboardingDescriptionBlock( + modifier = Modifier.align(Alignment.Center), + descriptionsList = listOf( + OnboardingDescription( + titleRes = R.string.onboarding_seed_generate_title, + subTitleRes = R.string.onboarding_seed_generate_message, + ), + ), + ) + } + Box( + modifier = Modifier.weight(1f), + ) { + PhraseGreedBlock(state.phraseList) + } + + Box( + modifier = Modifier.wrapContentSize(), + ) { + OnboardingActionBlock( + firstActionContent = { + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.common_continue), + onClick = state.buttonContinue.onClick, + ) + }, + ) + } + } +} + +@Composable +private fun PhraseGreedBlock(phraseList: List) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + ) { + this.items( + count = phraseList.size, + ) { index -> + Row( + modifier = Modifier.padding(all = TangemTheme.dimens.size8), + verticalAlignment = Alignment.CenterVertically, + ) { + SpacerW32() + Text( + modifier = Modifier.width(TangemTheme.dimens.size40), + text = "${index + 1}.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + Text( + text = phraseList[index], + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + ) + } + + } + } +} \ No newline at end of file From 78e0d5e3b3d5b072f06b68b8c6062709e226a144 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:25:36 +0300 Subject: [PATCH 14/36] Updated on 2026-08-14 --- .../wallet2/ui/CheckSeedPhraseScreen.kt | 138 ++++++++++ .../wallet2/viewmodel/SeedPhraseViewModel.kt | 243 ++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt new file mode 100644 index 0000000000..0f9b44759f --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -0,0 +1,138 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import com.tangem.core.ui.R +import com.tangem.core.ui.components.OutlineTextField +import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun CheckSeedPhraseScreen( + modifier: Modifier = Modifier, + state: CheckSeedPhraseState = CheckSeedPhraseState(), +) { + Column( + modifier = modifier + .fillMaxSize(), + ) { + LazyColumn( + modifier = modifier + .weight(1f) + .fillMaxWidth(), + ) { + item { + OnboardingDescriptionBlock( + modifier = Modifier + .weight(1f) + .padding( + start = TangemTheme.dimens.size16, + top = TangemTheme.dimens.size48, + end = TangemTheme.dimens.size16, + ), + descriptionsList = listOf( + OnboardingDescription( + // FIXME: replace by string res + title = "So, let’s check", + subTitle = "To check wether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words", + ), + ), + ) + } + item { + CheckSeedPhraseBlock( + modifier = Modifier + .imePadding() + .weight(1f) + .padding( + start = TangemTheme.dimens.size16, + top = TangemTheme.dimens.size20, + end = TangemTheme.dimens.size16, + ), + state = state, + ) + } + } + OnboardingActionBlock( + firstActionContent = { + PrimaryButtonIconLeft( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + icon = painterResource(id = R.drawable.ic_tangem_24), + enabled = state.buttonCreateWallet.enabled, + showProgress = state.buttonCreateWallet.showProgress, + onClick = state.buttonCreateWallet.onClick, + ) + }, + ) + } +} + +@Composable +private fun CheckSeedPhraseBlock( + modifier: Modifier = Modifier, + state: CheckSeedPhraseState, +) { + Column(modifier) { + OutlineTextField( + value = state.tvSecondPhrase.textFieldValue, + onValueChange = state.tvSecondPhrase.onTextFieldValueChanged, + label = state.tvSecondPhrase.getLabel(), + isError = state.tvSecondPhrase.isError, + ) + + OutlineTextField( + value = state.tvSeventhPhrase.textFieldValue, + onValueChange = state.tvSeventhPhrase.onTextFieldValueChanged, + label = state.tvSeventhPhrase.getLabel(), + isError = state.tvSeventhPhrase.isError, + ) + + OutlineTextField( + value = state.tvEleventhPhrase.textFieldValue, + onValueChange = state.tvEleventhPhrase.onTextFieldValueChanged, + label = state.tvEleventhPhrase.getLabel(), + isError = state.tvEleventhPhrase.isError, + ) + } +} + +@Composable +private fun CheckSeedPhraseBlock2( + modifier: Modifier = Modifier, + state: CheckSeedPhraseState, +) { + Column(modifier) { + listOf(state.tvSecondPhrase, state.tvSeventhPhrase, state.tvEleventhPhrase).forEach { field -> + OutlineTextField( + value = field.textFieldValue, + onValueChange = field.onTextFieldValueChanged, + label = field.getLabel(), + isError = field.isError, + ) + } + } +} + +@Composable +private fun TextFieldState.getLabel(): String? { + return labelRes?.let { stringResource(id = it) } ?: label +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt new file mode 100644 index 0000000000..baa8f5aabf --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -0,0 +1,243 @@ +package com.tangem.feature.onboarding.presentation.wallet2.viewmodel + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.text.input.TextFieldValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tangem.feature.onboarding.domain.SeedPhraseInteractor +import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.IntroUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep +import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField +import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState +import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions +import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUiAction +import com.tangem.feature.onboarding.presentation.wallet2.ui.StateBuilder +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.Debouncer +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import javax.inject.Inject + +/** +[REDACTED_AUTHOR] + */ +@HiltViewModel +class SeedPhraseViewModel @Inject constructor( + private val seedPhraseInteractor: SeedPhraseInteractor, + private val dispatchers: CoroutineDispatcherProvider, +) : ViewModel() { + + var aboutSeedPhraseUriProvider: AboutSeedPhraseOpener? = null + var chatLauncher: ChatSupportOpener? = null + + private var uiBuilder = StateBuilder(createUiActions()) + + var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init()) + private set + + val currentStep: OnboardingSeedPhraseStep + get() = uiState.step + + private val textFieldsDebouncers = mutableMapOf() + + init { + prepareCheckYourSeedPhraseTest() + } + + // TODO: delete + private fun prepareCheckYourSeedPhraseTest() { + uiState.copy(step = OnboardingSeedPhraseStep.AboutSeedPhrase) + viewModelScope.launchIo { + delay(300) + buttonGenerateSeedPhraseClick() + } + } + + private fun createUiActions(): UiActions = UiActions( + introActions = IntroUiAction( + buttonCreateWalletClick = ::buttonCreateWalletClick, + buttonOtherOptionsClick = ::buttonOtherOptionsClick, + ), + aboutActions = AboutUiAction( + buttonReadMoreAboutSeedPhraseClick = ::buttonReadMoreAboutSeedPhraseClick, + buttonGenerateSeedPhraseClick = ::buttonGenerateSeedPhraseClick, + buttonImportSeedPhraseClick = ::buttonImportSeedPhraseClick, + ), + yourSeedPhraseActions = YourSeedPhraseUiAction( + buttonContinueClick = ::buttonContinueClick, + ), + checkSeedPhraseActions = CheckSeedPhraseUiAction( + buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, + secondTextFieldAction = TextFieldUiAction( + onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) }, + onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Second, isFocused) }, + ), + seventhTextFieldAction = TextFieldUiAction( + onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Seventh, value) }, + onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Seventh, isFocused) }, + ), + eleventhTextFieldAction = TextFieldUiAction( + onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Eleventh, value) }, + onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Eleventh, isFocused) }, + ), + ), + importSeedPhraseActions = ImportSeedPhraseUiAction( + phraseTextFieldAction = TextFieldUiAction( + // onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Eleventh, isFocused) }, + // onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Eleventh, value) }, + ), + suggestedPhraseClick = ::buttonSuggestedPhraseClick, + buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, + ), + menuChatClick = ::menuChatClick, + ) + + override fun onCleared() { + textFieldsDebouncers.forEach { entry -> entry.value.release() } + textFieldsDebouncers.clear() + super.onCleared() + } + + // region CheckSeedPhrase + private fun onFocusChanged(field: SeedPhraseField, isFocused: Boolean) { + when (field) { + SeedPhraseField.Second -> {} + SeedPhraseField.Seventh -> {} + SeedPhraseField.Eleventh -> {} + } + } + + private fun onTextFieldChanged(field: SeedPhraseField, textFieldValue: TextFieldValue) { + uiState = uiBuilder.checkSeedPhrase.updateTextField(uiState, field, textFieldValue) + + val fieldState = field.getState(uiState) + if (fieldState.textFieldValue.text.isEmpty()) { + uiState = uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError = false) + return + } + + viewModelScope.launchIo { + val textFieldDebouncer = textFieldsDebouncers[field] ?: Debouncer().apply { + textFieldsDebouncers[field] = this + } + textFieldDebouncer.debounce(viewModelScope, context = dispatchers.io) { + val hasError = !seedPhraseInteractor.isPhraseMatch( + phrase = textFieldValue.text, + source = uiState.yourSeedPhraseState.phraseList, + ) + if (fieldState.isError != hasError) { + withMainContext { + uiState = uiBuilder.checkSeedPhrase.updateTextFieldError( + uiState = uiState, + field = field, + hasError = hasError, + ) + } + } + + val allFieldsWithoutError = SeedPhraseField.values() + .map { it.getState(uiState) } + .all { !it.isError } + + if (uiState.checkSeedPhraseState.buttonCreateWallet.enabled != allFieldsWithoutError) { + withMainContext { + uiState = uiBuilder.checkSeedPhrase.updateCreateWalletButton( + uiState = uiState, + enabled = allFieldsWithoutError, + ) + } + } + } + } + } + // endregion CheckSeedPhrase + + // region ButtonClickHandlers + private fun buttonCreateWalletClick() { + } + + private fun buttonCreateWalletWithSeedPhraseClick() { + buttonGenerateSeedPhraseClick() + } + + private fun buttonOtherOptionsClick() { + uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.AboutSeedPhrase) + } + + private fun buttonReadMoreAboutSeedPhraseClick() { + aboutSeedPhraseUriProvider?.open() + } + + private fun buttonGenerateSeedPhraseClick() { + viewModelScope.launchIo { + withMainContext { + uiState = uiBuilder.generateSeedPhrase(uiState) + } + + delay(1000) + val seedPhraseList = seedPhraseInteractor.generateSeedPhrase() + withMainContext { + uiState = uiBuilder.seedPhraseGenerated(uiState, seedPhraseList) + } + } + } + + private fun buttonImportSeedPhraseClick() { + uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) + } + + private fun buttonContinueClick() { + uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.CheckSeedPhrase) + } + + private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { + } + + private fun menuChatClick() { + chatLauncher?.open() + } + // endregion ButtonClickHandlers + + // region Utils + private fun SeedPhraseField.getState(uiState: OnboardingSeedPhraseState): TextFieldState = when (this) { + SeedPhraseField.Second -> uiState.checkSeedPhraseState.tvSecondPhrase + SeedPhraseField.Seventh -> uiState.checkSeedPhraseState.tvSeventhPhrase + SeedPhraseField.Eleventh -> uiState.checkSeedPhraseState.tvEleventhPhrase + } + + private fun CoroutineScope.launchMain(block: suspend CoroutineScope.() -> Unit): Job { + return viewModelScope.launch(dispatchers.main, block = block) + } + + private fun CoroutineScope.launchIo(block: suspend CoroutineScope.() -> Unit): Job { + return viewModelScope.launch(dispatchers.main, block = block) + } + + private suspend fun withMainContext(block: suspend CoroutineScope.() -> T): T { + return withContext(dispatchers.main, block) + } + + private suspend fun withIoContext(block: suspend CoroutineScope.() -> T): T { + return withContext(dispatchers.io, block) + } + // endregion Utils +} + +interface AboutSeedPhraseOpener { + fun open() {} +} + +interface ChatSupportOpener { + fun open() {} +} \ No newline at end of file From aa3d131e57a960a3aef2e64098130782bf256ad5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 3 Apr 2023 08:33:48 +0300 Subject: [PATCH 15/36] Updated on 2026-08-14 --- .../wallet2/ui/ImportSeedPhraseScreen.kt | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt new file mode 100644 index 0000000000..42a9f8a75c --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -0,0 +1,158 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.PrimaryButtonIconLeft +import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock + +/** +[REDACTED_AUTHOR] + */ +@Composable +fun ImportSeedPhraseScreen( + modifier: Modifier = Modifier, + state: ImportSeedPhraseState = ImportSeedPhraseState(), +) { + Column( + modifier = modifier.fillMaxSize(), + ) { + Box( + modifier = Modifier.weight(0.6f), + ) { + OnboardingDescriptionBlock( + modifier = Modifier.align(Alignment.Center), + descriptionsList = listOf( + OnboardingDescription( + subTitle = "To import your wallet, enter your secret recovery phrase in the fields below or scan a QR-code", + ), + ), + ) + } + Box( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.size16) + .weight(1f), + ) { + PhraseBlock(state) + } + + Box( + modifier = Modifier.weight(1f), + ) { + SuggestionsBlock(state) + } + + Box( + modifier = Modifier.wrapContentSize(), + ) { + OnboardingActionBlock( + firstActionContent = { + PrimaryButtonIconLeft( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = TangemTheme.dimens.size16), + text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), + icon = painterResource(id = R.drawable.ic_tangem_24), + enabled = state.buttonCreateWallet.enabled, + showProgress = state.buttonCreateWallet.showProgress, + onClick = state.buttonCreateWallet.onClick, + ) + }, + ) + } + } +} + +@Composable +private fun PhraseBlock(state: ImportSeedPhraseState) { + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size142), + // value = TextFieldValue(text = state.tvPhrase.text), + value = TextFieldValue(text = "state.tvPhrase.text"), + onValueChange = { }, + // textStyle = TangemTheme.typography.body1, + // singleLine = false, + ) +} + +@Composable +private fun SuggestionsBlock(state: ImportSeedPhraseState) { + LazyRow( + modifier = Modifier.fillMaxSize(), + ) { + items(state.suggestionsList.size) { index -> + PrimaryButton( + modifier = Modifier + .padding(all = TangemTheme.dimens.size4) + .background(color = TangemTheme.colors.background.action), + text = state.suggestionsList[index], + onClick = { state.onSuggestedPhraseClick(index) }, + ) + } + } +} + +@Composable +private fun ReadMoreBlock() { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) { + val shape = RoundedCornerShape(24.dp) + Row( + modifier = Modifier + .align(Alignment.Center) + .clip(shape) + .border(1.dp, Color.Gray, shape) + .clickable( + onClick = {}, + ) + .padding( + horizontal = 16.dp, + vertical = 8.dp, + ), + ) { + Image( + painter = painterResource(id = R.drawable.ic_arrow_top_right), + contentDescription = "Go away icon", + ) + SpacerW8() + Text( + text = stringResource(id = R.string.onboarding_seed_button_read_more), + ) + } + } +} \ No newline at end of file From 8fbb94cd9921e82b634fc7a4df794fdb3d84e733 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Apr 2023 15:58:00 +0300 Subject: [PATCH 16/36] Updated on 2026-08-14 --- .../main/java/com/tangem/core/ui/components/TextFields.kt | 4 ++-- core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt | 1 + .../tangem/utils/coroutines/CoroutineDispatcherProvider.kt | 5 +++++ .../main/java/com/tangem/utils/coroutines/CoroutineExt.kt | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt index 78a37aad9f..2575fee4ad 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TextFields.kt @@ -201,7 +201,7 @@ private fun TangemTextFieldSize.toShape(): Shape = when (this) { TangemTextFieldSize.Default -> TangemTheme.shapes.roundedCornersSmall2 } -internal object TangemTextFieldsDefault { +object TangemTextFieldsDefault { val defaultTextFieldColors: TangemTextFieldColors @Composable @Stable get() = TangemTextFieldColors( textColor = TangemTheme.colors.text.primary1, @@ -232,7 +232,7 @@ internal object TangemTextFieldsDefault { } @Immutable -internal data class TangemTextFieldColors( +data class TangemTextFieldColors( private val textColor: Color, private val disabledTextColor: Color, private val cursorColor: Color, diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt index 96c0d99ffc..9d9362b84d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemDimens.kt @@ -47,6 +47,7 @@ data class TangemDimens internal constructor( val size24: Dp = 24.dp, val size28: Dp = 28.dp, val size32: Dp = 32.dp, + val size34: Dp = 34.dp, val size36: Dp = 36.dp, val size40: Dp = 40.dp, val size42: Dp = 42.dp, diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index 952424e215..fcfd4c86cc 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -2,19 +2,24 @@ package com.tangem.utils.coroutines import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors import javax.inject.Inject interface CoroutineDispatcherProvider { val main: CoroutineDispatcher val io: CoroutineDispatcher + val single: CoroutineDispatcher } class AppCoroutineDispatcherProvider @Inject constructor() : CoroutineDispatcherProvider { override val main: CoroutineDispatcher = Dispatchers.Main override val io: CoroutineDispatcher = Dispatchers.IO + override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() } class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, + override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), ) : CoroutineDispatcherProvider \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 872aeb1c64..42b911ad4d 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -23,7 +23,9 @@ suspend inline fun ifActive(crossinline block: suspend () -> Unit) = coroutineSc } } -class Debouncer { +class Debouncer( + val name: String = "Default", +) { private var debounceJob: Job? = null From bba40d0d7ae2bc6013694a9378ce744a0157d809 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Apr 2023 15:58:35 +0300 Subject: [PATCH 17/36] Updated on 2026-08-14 --- ...gDescription.kt => DescriptionResource.kt} | 2 +- .../model/OnboardingSeedPhraseState.kt | 5 +- .../wallet2/ui/AboutSeedPhraseScreen.kt | 53 +++--- .../wallet2/ui/CheckSeedPhraseScreen.kt | 64 ++----- .../wallet2/ui/ImportSeedPhraseScreen.kt | 156 ++++++++---------- .../presentation/wallet2/ui/IntroScreen.kt | 33 ++-- .../wallet2/ui/YourSeedPhraseScreen.kt | 30 ++-- .../ui/components/OnboardingActionBlock.kt | 1 + .../components/OnboardingDescriptionBlock.kt | 36 ++-- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 123 ++++++-------- 10 files changed, 221 insertions(+), 282 deletions(-) rename features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/{OnboardingDescription.kt => DescriptionResource.kt} (83%) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/DescriptionResource.kt similarity index 83% rename from features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt rename to features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/DescriptionResource.kt index b1be694b19..db8f0d469b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingDescription.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/DescriptionResource.kt @@ -2,7 +2,7 @@ package com.tangem.feature.onboarding.presentation.wallet2.model import androidx.annotation.StringRes -data class OnboardingDescription( +data class DescriptionResource( @StringRes val titleRes: Int, @StringRes val subTitleRes: Int, ) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index 1e4781ae22..da3edf8aaa 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -2,6 +2,8 @@ package com.tangem.feature.onboarding.presentation.wallet2.model import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.domain.SeedPhraseError +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf /** [REDACTED_AUTHOR] @@ -22,6 +24,7 @@ enum class OnboardingSeedPhraseStep { } data class IntroState( + val cardImageUrl: String? = null, val buttonCreateWallet: ButtonState, val buttonOtherOptions: ButtonState, ) @@ -33,7 +36,7 @@ data class AboutState( ) data class YourSeedPhraseState( - val mnemonicComponents: List = listOf(), + val mnemonicComponents: ImmutableList = persistentListOf(), val buttonContinue: ButtonState, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index a394bafc0a..d28d81d0f9 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -29,7 +29,7 @@ import com.tangem.core.ui.components.SpacerW8 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock @@ -38,8 +38,8 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi */ @Composable fun AboutSeedPhraseScreen( + state: AboutState, modifier: Modifier = Modifier, - state: AboutState = AboutState(), ) { Column( modifier = modifier.fillMaxSize(), @@ -53,14 +53,12 @@ fun AboutSeedPhraseScreen( modifier = Modifier.weight(1.2f), ) { Column { - OnboardingDescriptionBlock( - descriptionsList = listOf( - OnboardingDescription( - titleRes = R.string.onboarding_seed_intro_title, - subTitleRes = R.string.onboarding_seed_intro_message, - ), - ), - ) + OnboardingDescriptionBlock { + Description( + titleRes = R.string.onboarding_seed_intro_title, + subTitleRes = R.string.onboarding_seed_intro_message, + ) + } ReadMoreBlock(state) } } @@ -72,8 +70,7 @@ fun AboutSeedPhraseScreen( firstActionContent = { SecondaryButton( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_seed_intro_button_generate), enabled = state.buttonGenerateSeedPhrase.enabled, showProgress = state.buttonGenerateSeedPhrase.showProgress, @@ -99,25 +96,29 @@ fun AboutSeedPhraseScreen( @Composable private fun EditIconBlock() { Column( - modifier = Modifier.fillMaxSize(), + modifier = Modifier + .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { Image( modifier = Modifier - .size(44.dp), - painter = painterResource(id = R.drawable.ic_onboarding_text_edit), + .size(TangemTheme.dimens.size44), + painter = painterResource(id = R.drawable.ic_onboarding_text_edit_56), contentDescription = "Edit icon", ) SpacerH32() Box( modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(Color(0x1EE10101)), + .clip(RoundedCornerShape(TangemTheme.dimens.radius8)) + .background(TangemTheme.colors.icon.warning.copy(alpha = 0.12f)), ) { Text( modifier = Modifier - .padding(horizontal = 12.dp, vertical = 4.dp), + .padding( + horizontal = TangemTheme.dimens.size12, + vertical = TangemTheme.dimens.size4, + ), text = stringResource(id = R.string.onboarding_seed_phrase_intro_legacy), color = TangemTheme.colors.text.warning, style = TangemTheme.typography.body1, @@ -132,22 +133,26 @@ private fun ReadMoreBlock(state: AboutState) { Box( modifier = Modifier .fillMaxWidth() - .padding(top = 16.dp), + .padding(top = TangemTheme.dimens.size16), ) { - val shape = RoundedCornerShape(24.dp) + val shape = TangemTheme.shapes.roundedCornersLarge Row( modifier = Modifier .align(Alignment.Center) .clip(shape) - .border(1.dp, Color.Gray, shape) + .border( + width = 1.dp, + color = Color.Gray, + shape = shape, + ) .clickable(onClick = state.buttonReadMoreAboutSeedPhrase.onClick) .padding( - horizontal = 16.dp, - vertical = 8.dp, + horizontal = TangemTheme.dimens.size16, + vertical = TangemTheme.dimens.size8, ), ) { Image( - painter = painterResource(id = R.drawable.ic_arrow_top_right), + painter = painterResource(id = R.drawable.ic_arrow_top_right_24), contentDescription = "Go away icon", ) SpacerW8() diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt index 0f9b44759f..bb32ce441c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -15,8 +15,7 @@ import com.tangem.core.ui.components.OutlineTextField import com.tangem.core.ui.components.PrimaryButtonIconLeft import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription -import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock @@ -25,8 +24,8 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi */ @Composable fun CheckSeedPhraseScreen( + state: CheckSeedPhraseState, modifier: Modifier = Modifier, - state: CheckSeedPhraseState = CheckSeedPhraseState(), ) { Column( modifier = modifier @@ -41,30 +40,22 @@ fun CheckSeedPhraseScreen( OnboardingDescriptionBlock( modifier = Modifier .weight(1f) - .padding( - start = TangemTheme.dimens.size16, - top = TangemTheme.dimens.size48, - end = TangemTheme.dimens.size16, - ), - descriptionsList = listOf( - OnboardingDescription( - // FIXME: replace by string res - title = "So, let’s check", - subTitle = "To check wether you’ve written down your seed phrase correctly, please enter the 2nd, 7th and 11th words", - ), - ), - ) + .padding(top = TangemTheme.dimens.size48) + .padding(horizontal = TangemTheme.dimens.size16), + ) { + Description( + titleRes = R.string.onboarding_seed_user_validation_title, + subTitleRes = R.string.onboarding_seed_user_validation_message, + ) + } } item { CheckSeedPhraseBlock( modifier = Modifier .imePadding() .weight(1f) - .padding( - start = TangemTheme.dimens.size16, - top = TangemTheme.dimens.size20, - end = TangemTheme.dimens.size16, - ), + .padding(top = TangemTheme.dimens.size20) + .padding(horizontal = TangemTheme.dimens.size16), state = state, ) } @@ -73,8 +64,7 @@ fun CheckSeedPhraseScreen( firstActionContent = { PrimaryButtonIconLeft( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), icon = painterResource(id = R.drawable.ic_tangem_24), enabled = state.buttonCreateWallet.enabled, @@ -95,44 +85,22 @@ private fun CheckSeedPhraseBlock( OutlineTextField( value = state.tvSecondPhrase.textFieldValue, onValueChange = state.tvSecondPhrase.onTextFieldValueChanged, - label = state.tvSecondPhrase.getLabel(), + label = state.tvSecondPhrase.label, isError = state.tvSecondPhrase.isError, ) OutlineTextField( value = state.tvSeventhPhrase.textFieldValue, onValueChange = state.tvSeventhPhrase.onTextFieldValueChanged, - label = state.tvSeventhPhrase.getLabel(), + label = state.tvSeventhPhrase.label, isError = state.tvSeventhPhrase.isError, ) OutlineTextField( value = state.tvEleventhPhrase.textFieldValue, onValueChange = state.tvEleventhPhrase.onTextFieldValueChanged, - label = state.tvEleventhPhrase.getLabel(), + label = state.tvEleventhPhrase.label, isError = state.tvEleventhPhrase.isError, ) } -} - -@Composable -private fun CheckSeedPhraseBlock2( - modifier: Modifier = Modifier, - state: CheckSeedPhraseState, -) { - Column(modifier) { - listOf(state.tvSecondPhrase, state.tvSeventhPhrase, state.tvEleventhPhrase).forEach { field -> - OutlineTextField( - value = field.textFieldValue, - onValueChange = field.onTextFieldValueChanged, - label = field.getLabel(), - isError = field.isError, - ) - } - } -} - -@Composable -private fun TextFieldState.getLabel(): String? { - return labelRes?.let { stringResource(id = it) } ?: label } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index 42a9f8a75c..cbb84d7bc2 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -1,37 +1,33 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideIn +import androidx.compose.animation.slideOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.IntOffset import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconLeft -import com.tangem.core.ui.components.SpacerW8 +import com.tangem.core.ui.components.TangemTextFieldsDefault import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock @@ -40,38 +36,30 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi */ @Composable fun ImportSeedPhraseScreen( + state: ImportSeedPhraseState, modifier: Modifier = Modifier, - state: ImportSeedPhraseState = ImportSeedPhraseState(), ) { Column( modifier = modifier.fillMaxSize(), ) { - Box( - modifier = Modifier.weight(0.6f), - ) { - OnboardingDescriptionBlock( - modifier = Modifier.align(Alignment.Center), - descriptionsList = listOf( - OnboardingDescription( - subTitle = "To import your wallet, enter your secret recovery phrase in the fields below or scan a QR-code", - ), - ), - ) - } - Box( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.size16) - .weight(1f), - ) { - PhraseBlock(state) - } - Box( modifier = Modifier.weight(1f), ) { - SuggestionsBlock(state) + Column { + OnboardingDescriptionBlock(modifier) { + DescriptionSubTitleText(text = stringResource(id = R.string.onboarding_seed_import_message)) + } + PhraseBlock( + modifier = Modifier + .padding(top = TangemTheme.dimens.size62) + .padding(horizontal = TangemTheme.dimens.size16), + state = state, + ) + SuggestionsBlock( + state = state, + ) + } } - Box( modifier = Modifier.wrapContentSize(), ) { @@ -94,65 +82,63 @@ fun ImportSeedPhraseScreen( } @Composable -private fun PhraseBlock(state: ImportSeedPhraseState) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .height(TangemTheme.dimens.size142), - // value = TextFieldValue(text = state.tvPhrase.text), - value = TextFieldValue(text = "state.tvPhrase.text"), - onValueChange = { }, - // textStyle = TangemTheme.typography.body1, - // singleLine = false, - ) -} - -@Composable -private fun SuggestionsBlock(state: ImportSeedPhraseState) { - LazyRow( - modifier = Modifier.fillMaxSize(), +private fun PhraseBlock( + state: ImportSeedPhraseState, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth(), ) { - items(state.suggestionsList.size) { index -> - PrimaryButton( + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size142), + value = state.tvSeedPhrase.textFieldValue, + onValueChange = state.tvSeedPhrase.onTextFieldValueChanged, + textStyle = TangemTheme.typography.body1, + singleLine = false, + colors = TangemTextFieldsDefault.defaultTextFieldColors, + ) + Box( + modifier = Modifier + .fillMaxWidth() + .height(TangemTheme.dimens.size32), + ) { + Text( modifier = Modifier - .padding(all = TangemTheme.dimens.size4) - .background(color = TangemTheme.colors.background.action), - text = state.suggestionsList[index], - onClick = { state.onSuggestedPhraseClick(index) }, + .fillMaxSize(), + text = "TODO: implement error message", + style = TangemTheme.typography.caption.copy( + color = TangemTheme.colors.text.warning, + ), ) } } } @Composable -private fun ReadMoreBlock() { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(top = 16.dp), +private fun SuggestionsBlock( + state: ImportSeedPhraseState, + modifier: Modifier = Modifier, +) { + AnimatedVisibility( + enter = fadeIn() + slideIn(initialOffset = { IntOffset(200, 0) }), + exit = slideOut(targetOffset = { IntOffset(-200, 0) }) + fadeOut(), + visible = state.suggestionsList.isNotEmpty(), ) { - val shape = RoundedCornerShape(24.dp) - Row( - modifier = Modifier - .align(Alignment.Center) - .clip(shape) - .border(1.dp, Color.Gray, shape) - .clickable( - onClick = {}, - ) - .padding( - horizontal = 16.dp, - vertical = 8.dp, - ), + LazyRow( + modifier = modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16), ) { - Image( - painter = painterResource(id = R.drawable.ic_arrow_top_right), - contentDescription = "Go away icon", - ) - SpacerW8() - Text( - text = stringResource(id = R.string.onboarding_seed_button_read_more), - ) + items(state.suggestionsList.size) { index -> + PrimaryButton( + modifier = Modifier + .height(TangemTheme.dimens.size46) + .padding(all = TangemTheme.dimens.size4), + text = state.suggestionsList[index], + onClick = { state.onSuggestedPhraseClick(index) }, + ) + } } } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt index 84f281e56e..a865f937af 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -14,7 +14,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import coil.compose.SubcomposeAsyncImage import coil.request.ImageRequest import com.tangem.core.ui.components.PrimaryButtonIconLeft @@ -22,7 +21,7 @@ import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock @@ -31,8 +30,8 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi */ @Composable fun IntroScreen( - modifier: Modifier = Modifier, state: IntroState, + modifier: Modifier = Modifier, ) { Column( modifier = modifier.fillMaxSize(), @@ -40,20 +39,17 @@ fun IntroScreen( Box( modifier = Modifier.weight(1.5f), ) { - //FIXME: path the url from the state - CardImageBlock("url") + CardImageBlock(state.cardImageUrl) } Box( modifier = Modifier.weight(1f), ) { - OnboardingDescriptionBlock( - descriptionsList = listOf( - OnboardingDescription( - titleRes = R.string.onboarding_create_wallet_options_title, - subTitleRes = R.string.onboarding_create_wallet_options_message, - ), - ), - ) + OnboardingDescriptionBlock { + Description( + titleRes = R.string.onboarding_create_wallet_options_title, + subTitleRes = R.string.onboarding_create_wallet_options_message, + ) + } } Box( modifier = Modifier.wrapContentSize(), @@ -62,8 +58,7 @@ fun IntroScreen( firstActionContent = { PrimaryButtonIconLeft( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), icon = painterResource(id = R.drawable.ic_tangem_24), enabled = state.buttonCreateWallet.enabled, @@ -74,8 +69,7 @@ fun IntroScreen( secondActionContent = { SecondaryButton( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_options_button_options), enabled = state.buttonOtherOptions.enabled, showProgress = state.buttonOtherOptions.showProgress, @@ -97,15 +91,14 @@ private fun CardImageBlock( .align(Alignment.Center) SubcomposeAsyncImage( - modifier = cardImageModifier.padding(horizontal = 34.dp), + modifier = cardImageModifier.padding(horizontal = TangemTheme.dimens.size34), model = ImageRequest.Builder(LocalContext.current) .data(cardImageUrl) .crossfade(true) .build(), - contentDescription = "Tangem Card", loading = { CardPlaceHolder(cardImageModifier) }, error = { CardPlaceHolder(cardImageModifier) }, - colorFilter = null, + contentDescription = "Tangem Card", ) } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index fa8c4c0e56..f771266a37 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material.Text @@ -19,18 +18,19 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerW32 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock +import kotlinx.collections.immutable.ImmutableList /** [REDACTED_AUTHOR] */ @Composable fun YourSeedPhraseScreen( + state: YourSeedPhraseState, modifier: Modifier = Modifier, - state: YourSeedPhraseState = YourSeedPhraseState(), ) { Column( modifier = modifier.fillMaxSize(), @@ -40,29 +40,25 @@ fun YourSeedPhraseScreen( ) { OnboardingDescriptionBlock( modifier = Modifier.align(Alignment.Center), - descriptionsList = listOf( - OnboardingDescription( - titleRes = R.string.onboarding_seed_generate_title, - subTitleRes = R.string.onboarding_seed_generate_message, - ), - ), - ) + ) { + Description( + titleRes = R.string.onboarding_seed_generate_title, + subTitleRes = R.string.onboarding_seed_generate_message, + ) + } } Box( modifier = Modifier.weight(1f), ) { - PhraseGreedBlock(state.phraseList) + PhraseGreedBlock(state.mnemonicComponents) } - Box( - modifier = Modifier.wrapContentSize(), - ) { + Box { OnboardingActionBlock( firstActionContent = { PrimaryButton( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.common_continue), onClick = state.buttonContinue.onClick, ) @@ -73,7 +69,7 @@ fun YourSeedPhraseScreen( } @Composable -private fun PhraseGreedBlock(phraseList: List) { +private fun PhraseGreedBlock(phraseList: ImmutableList) { LazyVerticalGrid( columns = GridCells.Fixed(2), ) { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt index d8992181e0..b7706cb69e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingActionBlock.kt @@ -21,6 +21,7 @@ fun OnboardingActionBlock( Box( modifier = Modifier + .padding(horizontal = TangemTheme.dimens.size16) .padding( top = TangemTheme.dimens.size8, bottom = TangemTheme.dimens.size32, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index 73f52f656b..c72bbdff7b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -1,5 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui.components +import androidx.annotation.StringRes import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -11,9 +12,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import com.tangem.core.ui.components.SpacerH12 import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingDescription +import com.tangem.feature.onboarding.presentation.wallet2.model.DescriptionResource import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf /** * UI component witch provides single and carousel description for the onboarding process @@ -21,39 +21,41 @@ import kotlinx.collections.immutable.persistentListOf @Composable fun OnboardingDescriptionBlock( modifier: Modifier = Modifier, - descriptionsList: ImmutableList = persistentListOf(), + content: @Composable () -> Unit, ) { - if (descriptionsList.isEmpty()) return - Box( modifier = modifier .fillMaxWidth() .padding(horizontal = TangemTheme.dimens.size24), ) { - if (descriptionsList.size == 1) { - Description(descriptionsList[0]) - } else { - CarouselDescription(descriptionsList) - } + content() } } @Composable -private fun Description(description: OnboardingDescription) { +fun Description( + @StringRes titleRes: Int, + @StringRes subTitleRes: Int, +) { Column { - DescriptionTitleText(text = stringResource(id = description.titleRes)) + DescriptionTitleText(text = stringResource(id = titleRes)) SpacerH12() - DescriptionSubTitleText(text = stringResource(id = description.subTitleRes)) + DescriptionSubTitleText(text = stringResource(id = subTitleRes)) } } @Composable -private fun CarouselDescription(descriptionsList: ImmutableList) { - //TODO: implement +fun OnboardingCarouselDescriptionBlock( + modifier: Modifier = Modifier, + descriptionsList: ImmutableList, +) { + OnboardingDescriptionBlock(modifier) { + //TODO: implement + } } @Composable -private fun DescriptionTitleText(text: String) { +fun DescriptionTitleText(text: String) { Text( text = text, style = TangemTheme.typography.h2, @@ -64,7 +66,7 @@ private fun DescriptionTitleText(text: String) { } @Composable -private fun DescriptionSubTitleText(text: String) { +fun DescriptionSubTitleText(text: String) { Text( text = text, style = TangemTheme.typography.subtitle1, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index baa8f5aabf..eabb91923b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -24,7 +24,6 @@ import com.tangem.utils.coroutines.Debouncer import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import javax.inject.Inject @@ -34,7 +33,7 @@ import javax.inject.Inject */ @HiltViewModel class SeedPhraseViewModel @Inject constructor( - private val seedPhraseInteractor: SeedPhraseInteractor, + private val interactor: SeedPhraseInteractor, private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { @@ -49,20 +48,7 @@ class SeedPhraseViewModel @Inject constructor( val currentStep: OnboardingSeedPhraseStep get() = uiState.step - private val textFieldsDebouncers = mutableMapOf() - - init { - prepareCheckYourSeedPhraseTest() - } - - // TODO: delete - private fun prepareCheckYourSeedPhraseTest() { - uiState.copy(step = OnboardingSeedPhraseStep.AboutSeedPhrase) - viewModelScope.launchIo { - delay(300) - buttonGenerateSeedPhraseClick() - } - } + private val textFieldsDebouncers = mutableMapOf() private fun createUiActions(): UiActions = UiActions( introActions = IntroUiAction( @@ -94,8 +80,8 @@ class SeedPhraseViewModel @Inject constructor( ), importSeedPhraseActions = ImportSeedPhraseUiAction( phraseTextFieldAction = TextFieldUiAction( - // onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Eleventh, isFocused) }, - // onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Eleventh, value) }, + onFocusChanged = { isFocused -> /* [REDACTED_TODO_COMMENT] */ }, + onTextFieldChanged = { value -> /* [REDACTED_TODO_COMMENT] */ }, ), suggestedPhraseClick = ::buttonSuggestedPhraseClick, buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, @@ -119,44 +105,27 @@ class SeedPhraseViewModel @Inject constructor( } private fun onTextFieldChanged(field: SeedPhraseField, textFieldValue: TextFieldValue) { - uiState = uiBuilder.checkSeedPhrase.updateTextField(uiState, field, textFieldValue) + viewModelScope.launchSingle { + updateUi { uiBuilder.checkSeedPhrase.updateTextField(uiState, field, textFieldValue) } - val fieldState = field.getState(uiState) - if (fieldState.textFieldValue.text.isEmpty()) { - uiState = uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError = false) - return - } - - viewModelScope.launchIo { - val textFieldDebouncer = textFieldsDebouncers[field] ?: Debouncer().apply { - textFieldsDebouncers[field] = this + val fieldState = field.getState(uiState) + if (fieldState.textFieldValue.text.isEmpty()) { + updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError = false) } + return@launchSingle } - textFieldDebouncer.debounce(viewModelScope, context = dispatchers.io) { - val hasError = !seedPhraseInteractor.isPhraseMatch( - phrase = textFieldValue.text, - source = uiState.yourSeedPhraseState.phraseList, - ) + + createOrGetDebouncer(field.name).debounce(viewModelScope, context = dispatchers.io) { + val hasError = !interactor.wordIsMatch(textFieldValue.text) if (fieldState.isError != hasError) { - withMainContext { - uiState = uiBuilder.checkSeedPhrase.updateTextFieldError( - uiState = uiState, - field = field, - hasError = hasError, - ) - } + updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) } } val allFieldsWithoutError = SeedPhraseField.values() - .map { it.getState(uiState) } - .all { !it.isError } + .map { field -> field.getState(uiState) } + .all { fieldState -> !fieldState.isError } if (uiState.checkSeedPhraseState.buttonCreateWallet.enabled != allFieldsWithoutError) { - withMainContext { - uiState = uiBuilder.checkSeedPhrase.updateCreateWalletButton( - uiState = uiState, - enabled = allFieldsWithoutError, - ) - } + updateUi { uiBuilder.checkSeedPhrase.updateCreateWalletButton(uiState, allFieldsWithoutError) } } } } @@ -172,7 +141,9 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonOtherOptionsClick() { - uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.AboutSeedPhrase) + viewModelScope.launchSingle { + updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.AboutSeedPhrase) } + } } private fun buttonReadMoreAboutSeedPhraseClick() { @@ -180,25 +151,28 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonGenerateSeedPhraseClick() { - viewModelScope.launchIo { - withMainContext { - uiState = uiBuilder.generateSeedPhrase(uiState) - } - - delay(1000) - val seedPhraseList = seedPhraseInteractor.generateSeedPhrase() - withMainContext { - uiState = uiBuilder.seedPhraseGenerated(uiState, seedPhraseList) - } + viewModelScope.launchSingle { + updateUi { uiBuilder.generateMnemonicComponents(uiState) } + interactor.generateMnemonic() + .onSuccess { mnemonic -> + updateUi { uiBuilder.mnemonicGenerated(uiState, mnemonic.mnemonicComponents) } + } + .onFailure { + // show error + } } } private fun buttonImportSeedPhraseClick() { - uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) + viewModelScope.launchSingle { + updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) } + } } private fun buttonContinueClick() { - uiState = uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.CheckSeedPhrase) + viewModelScope.launchSingle { + updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.CheckSeedPhrase) } + } } private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { @@ -210,26 +184,37 @@ class SeedPhraseViewModel @Inject constructor( // endregion ButtonClickHandlers // region Utils + /** + * Updating the UI with a contract where all copying of objects are called in the Single thread context and + * updating the UiState in the main context. + */ + private suspend fun updateUi(updateBlock: suspend () -> OnboardingSeedPhraseState) { + withSingleContext { + val newState = updateBlock.invoke() + withMainContext { uiState = newState } + } + } + + private fun createOrGetDebouncer(name: String): Debouncer { + return textFieldsDebouncers[name] ?: Debouncer(name).apply { textFieldsDebouncers[name] = this } + } + private fun SeedPhraseField.getState(uiState: OnboardingSeedPhraseState): TextFieldState = when (this) { SeedPhraseField.Second -> uiState.checkSeedPhraseState.tvSecondPhrase SeedPhraseField.Seventh -> uiState.checkSeedPhraseState.tvSeventhPhrase SeedPhraseField.Eleventh -> uiState.checkSeedPhraseState.tvEleventhPhrase } - private fun CoroutineScope.launchMain(block: suspend CoroutineScope.() -> Unit): Job { - return viewModelScope.launch(dispatchers.main, block = block) - } - - private fun CoroutineScope.launchIo(block: suspend CoroutineScope.() -> Unit): Job { - return viewModelScope.launch(dispatchers.main, block = block) + private fun CoroutineScope.launchSingle(block: suspend CoroutineScope.() -> Unit): Job { + return viewModelScope.launch(dispatchers.single, block = block) } private suspend fun withMainContext(block: suspend CoroutineScope.() -> T): T { return withContext(dispatchers.main, block) } - private suspend fun withIoContext(block: suspend CoroutineScope.() -> T): T { - return withContext(dispatchers.io, block) + private suspend fun withSingleContext(block: suspend CoroutineScope.() -> T): T { + return withContext(dispatchers.single, block) } // endregion Utils } From 49f14d1115020ae080fae2860b76cf53725c6baf Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Apr 2023 19:37:31 +0300 Subject: [PATCH 18/36] Updated on 2026-08-14 --- .../tangem/utils/coroutines/CoroutineExt.kt | 4 +- .../presentation/wallet2/model/UiActions.kt | 2 +- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 40 +++++-------------- 3 files changed, 11 insertions(+), 35 deletions(-) diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt index 42b911ad4d..872aeb1c64 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineExt.kt @@ -23,9 +23,7 @@ suspend inline fun ifActive(crossinline block: suspend () -> Unit) = coroutineSc } } -class Debouncer( - val name: String = "Default", -) { +class Debouncer { private var debounceJob: Job? = null diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index db5573c0fa..168cbaca76 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -43,8 +43,8 @@ data class ImportSeedPhraseUiAction( ) data class TextFieldUiAction( - val onFocusChanged: (Boolean) -> Unit, val onTextFieldChanged: (TextFieldValue) -> Unit, + val onFocusChanged: (Boolean) -> Unit = {}, ) enum class SeedPhraseField { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index eabb91923b..14c5167dd3 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -37,9 +37,6 @@ class SeedPhraseViewModel @Inject constructor( private val dispatchers: CoroutineDispatcherProvider, ) : ViewModel() { - var aboutSeedPhraseUriProvider: AboutSeedPhraseOpener? = null - var chatLauncher: ChatSupportOpener? = null - private var uiBuilder = StateBuilder(createUiActions()) var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init()) @@ -50,6 +47,12 @@ class SeedPhraseViewModel @Inject constructor( private val textFieldsDebouncers = mutableMapOf() + override fun onCleared() { + textFieldsDebouncers.forEach { entry -> entry.value.release() } + textFieldsDebouncers.clear() + super.onCleared() + } + private fun createUiActions(): UiActions = UiActions( introActions = IntroUiAction( buttonCreateWalletClick = ::buttonCreateWalletClick, @@ -67,15 +70,12 @@ class SeedPhraseViewModel @Inject constructor( buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, secondTextFieldAction = TextFieldUiAction( onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) }, - onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Second, isFocused) }, ), seventhTextFieldAction = TextFieldUiAction( onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Seventh, value) }, - onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Seventh, isFocused) }, ), eleventhTextFieldAction = TextFieldUiAction( onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Eleventh, value) }, - onFocusChanged = { isFocused -> onFocusChanged(SeedPhraseField.Eleventh, isFocused) }, ), ), importSeedPhraseActions = ImportSeedPhraseUiAction( @@ -89,21 +89,7 @@ class SeedPhraseViewModel @Inject constructor( menuChatClick = ::menuChatClick, ) - override fun onCleared() { - textFieldsDebouncers.forEach { entry -> entry.value.release() } - textFieldsDebouncers.clear() - super.onCleared() - } - // region CheckSeedPhrase - private fun onFocusChanged(field: SeedPhraseField, isFocused: Boolean) { - when (field) { - SeedPhraseField.Second -> {} - SeedPhraseField.Seventh -> {} - SeedPhraseField.Eleventh -> {} - } - } - private fun onTextFieldChanged(field: SeedPhraseField, textFieldValue: TextFieldValue) { viewModelScope.launchSingle { updateUi { uiBuilder.checkSeedPhrase.updateTextField(uiState, field, textFieldValue) } @@ -147,7 +133,7 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonReadMoreAboutSeedPhraseClick() { - aboutSeedPhraseUriProvider?.open() + // TODO: open the about info through a router } private fun buttonGenerateSeedPhraseClick() { @@ -179,7 +165,7 @@ class SeedPhraseViewModel @Inject constructor( } private fun menuChatClick() { - chatLauncher?.open() + // TODO: open the support chat through a router } // endregion ButtonClickHandlers @@ -196,7 +182,7 @@ class SeedPhraseViewModel @Inject constructor( } private fun createOrGetDebouncer(name: String): Debouncer { - return textFieldsDebouncers[name] ?: Debouncer(name).apply { textFieldsDebouncers[name] = this } + return textFieldsDebouncers[name] ?: Debouncer().apply { textFieldsDebouncers[name] = this } } private fun SeedPhraseField.getState(uiState: OnboardingSeedPhraseState): TextFieldState = when (this) { @@ -217,12 +203,4 @@ class SeedPhraseViewModel @Inject constructor( return withContext(dispatchers.single, block) } // endregion Utils -} - -interface AboutSeedPhraseOpener { - fun open() {} -} - -interface ChatSupportOpener { - fun open() {} } \ No newline at end of file From e7557659d3eef522753bbbf7379e6719af1535a8 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 14 Apr 2023 14:08:53 +0300 Subject: [PATCH 19/36] Updated on 2026-08-14 --- .../wallet2/ui/AboutSeedPhraseScreen.kt | 22 ++++++++----------- .../wallet2/ui/ImportSeedPhraseScreen.kt | 5 +---- .../presentation/wallet2/ui/IntroScreen.kt | 9 +++----- .../presentation/wallet2/ui/StateBuilder.kt | 5 +++-- 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index d28d81d0f9..94ac274ac6 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -12,17 +12,14 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SecondaryButton import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerW8 @@ -62,10 +59,7 @@ fun AboutSeedPhraseScreen( ReadMoreBlock(state) } } - - Box( - modifier = Modifier.wrapContentSize(), - ) { + Box { OnboardingActionBlock( firstActionContent = { SecondaryButton( @@ -105,13 +99,15 @@ private fun EditIconBlock() { modifier = Modifier .size(TangemTheme.dimens.size44), painter = painterResource(id = R.drawable.ic_onboarding_text_edit_56), - contentDescription = "Edit icon", + contentDescription = null, ) SpacerH32() Box( modifier = Modifier - .clip(RoundedCornerShape(TangemTheme.dimens.radius8)) - .background(TangemTheme.colors.icon.warning.copy(alpha = 0.12f)), + .background( + color = TangemTheme.colors.icon.warning.copy(alpha = 0.12f), + shape = RoundedCornerShape(TangemTheme.dimens.radius8), + ), ) { Text( modifier = Modifier @@ -141,8 +137,8 @@ private fun ReadMoreBlock(state: AboutState) { .align(Alignment.Center) .clip(shape) .border( - width = 1.dp, - color = Color.Gray, + width = TangemTheme.dimens.size1, + color = TangemTheme.colors.stroke.primary, shape = shape, ) .clickable(onClick = state.buttonReadMoreAboutSeedPhrase.onClick) @@ -153,7 +149,7 @@ private fun ReadMoreBlock(state: AboutState) { ) { Image( painter = painterResource(id = R.drawable.ic_arrow_top_right_24), - contentDescription = "Go away icon", + contentDescription = null, ) SpacerW8() Text( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index cbb84d7bc2..82618a84a9 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text @@ -60,9 +59,7 @@ fun ImportSeedPhraseScreen( ) } } - Box( - modifier = Modifier.wrapContentSize(), - ) { + Box { OnboardingActionBlock( firstActionContent = { PrimaryButtonIconLeft( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt index a865f937af..2b57d09193 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -51,9 +50,7 @@ fun IntroScreen( ) } } - Box( - modifier = Modifier.wrapContentSize(), - ) { + Box { OnboardingActionBlock( firstActionContent = { PrimaryButtonIconLeft( @@ -98,7 +95,7 @@ private fun CardImageBlock( .build(), loading = { CardPlaceHolder(cardImageModifier) }, error = { CardPlaceHolder(cardImageModifier) }, - contentDescription = "Tangem Card", + contentDescription = null, ) } } @@ -108,7 +105,7 @@ private fun CardPlaceHolder(modifier: Modifier) { Image( modifier = modifier, painter = painterResource(id = R.drawable.card_placeholder_black), - contentDescription = "Tangem Card placeholder", contentScale = ContentScale.Fit, + contentDescription = null, ) } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt index 2819421add..0a2f23c8b1 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt @@ -10,6 +10,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPh import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState +import kotlinx.collections.immutable.ImmutableList /** [REDACTED_AUTHOR] @@ -110,7 +111,7 @@ class StateBuilder( fun mnemonicGenerated( uiState: OnboardingSeedPhraseState, - mnemonicComponents: List, + mnemonicComponents: ImmutableList, ): OnboardingSeedPhraseState { return updateMnemonicComponents(uiState, mnemonicComponents) .copy( @@ -128,7 +129,7 @@ class StateBuilder( fun updateMnemonicComponents( uiState: OnboardingSeedPhraseState, - phraseList: List, + phraseList: ImmutableList, ): OnboardingSeedPhraseState = uiState.copy( yourSeedPhraseState = uiState.yourSeedPhraseState.copy( mnemonicComponents = phraseList, From de222c7cb718bc2dfc781999543d69e727bff1a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 11:41:02 +0300 Subject: [PATCH 20/36] Updated on 2026-08-14 --- .../com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt | 1 + .../feature/onboarding/presentation/wallet2/model/UiActions.kt | 1 + .../onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt | 1 + .../onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt | 1 + 4 files changed, 4 insertions(+) diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index fcfd4c86cc..597114b801 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -22,4 +22,5 @@ class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), + ) : CoroutineDispatcherProvider \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index 168cbaca76..a616d93578 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -45,6 +45,7 @@ data class ImportSeedPhraseUiAction( data class TextFieldUiAction( val onTextFieldChanged: (TextFieldValue) -> Unit, val onFocusChanged: (Boolean) -> Unit = {}, + ) enum class SeedPhraseField { diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt index bb32ce441c..6287eaf5df 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -36,6 +36,7 @@ fun CheckSeedPhraseScreen( .weight(1f) .fillMaxWidth(), ) { + item { OnboardingDescriptionBlock( modifier = Modifier diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index 82618a84a9..d79aa76288 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -95,6 +95,7 @@ private fun PhraseBlock( textStyle = TangemTheme.typography.body1, singleLine = false, colors = TangemTextFieldsDefault.defaultTextFieldColors, + ) Box( modifier = Modifier From 3d95481b13afe4f867f81c79c64e8c8a275b33fd Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 15:45:16 +0300 Subject: [PATCH 21/36] Updated on 2026-08-14 --- .../com/tangem/datasource/asset/AndroidAssetReader.kt | 9 +++++++-- .../java/com/tangem/datasource/asset/AssetReader.kt | 11 ++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt index 7e33fefae3..ce19ff204e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/AndroidAssetReader.kt @@ -3,6 +3,7 @@ package com.tangem.datasource.asset import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import java.io.BufferedReader +import java.io.InputStream import javax.inject.Inject /** @@ -15,9 +16,13 @@ internal class AndroidAssetReader @Inject constructor( ) : AssetReader { override fun readJson(fileName: String): String { - return context.assets - .open("$fileName.json") + return openFile("$fileName.json") .bufferedReader() .use(BufferedReader::readText) } + + override fun openFile(fileName: String): InputStream { + return context.assets + .open(fileName) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt index 6475ad3909..5c02f67a7c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt @@ -1,5 +1,7 @@ package com.tangem.datasource.asset +import java.io.InputStream + /** * Asset file reader * @@ -7,6 +9,13 @@ package com.tangem.datasource.asset */ interface AssetReader { - /** Read content of json file [fileName] from asset */ + /** Read content of json file from asset + * @param fileName - name of the file + * */ fun readJson(fileName: String): String + + /** Read content of a file [file] from asset as InputStream + * @param file - name of the file with extension + * */ + fun openFile(file: String): InputStream } \ No newline at end of file From f3458357928d907b3d63b1652222ca03eb60bb5e Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 15:46:56 +0300 Subject: [PATCH 22/36] Updated on 2026-08-14 --- .../tangem/common/module/ModuleErrorCode.kt | 1 + .../mnemonic/mnemonic_dictionary_en.txt | 2048 +++++++++++++++++ .../onboarding/data/MnemonicRepository.kt | 44 + .../data/di/OnboardingDataModule.kt | 21 + .../domain/OnboardingModuleError.kt | 41 + .../onboarding/domain/SeedPhraseChecker.kt | 16 - .../onboarding/domain/SeedPhraseInteractor.kt | 108 +- .../domain/di/OnboardingDomainModule.kt | 21 + 8 files changed, 2274 insertions(+), 26 deletions(-) create mode 100644 features/onboarding/src/main/assets/mnemonic/mnemonic_dictionary_en.txt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt delete mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/di/OnboardingDomainModule.kt diff --git a/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt b/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt index 11da28c037..ef7941cc63 100644 --- a/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt +++ b/common/src/main/java/com/tangem/common/module/ModuleErrorCode.kt @@ -10,4 +10,5 @@ object ModuleErrorCode { const val NETWORK = 20000 const val DOMAIN = 30000 const val SALT_PAY = 40000 + const val ONBOARDING = 50000 } \ No newline at end of file diff --git a/features/onboarding/src/main/assets/mnemonic/mnemonic_dictionary_en.txt b/features/onboarding/src/main/assets/mnemonic/mnemonic_dictionary_en.txt new file mode 100644 index 0000000000..f78ccaf2ce --- /dev/null +++ b/features/onboarding/src/main/assets/mnemonic/mnemonic_dictionary_en.txt @@ -0,0 +1,2048 @@ +abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt new file mode 100644 index 0000000000..4425671e1a --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt @@ -0,0 +1,44 @@ +package com.tangem.feature.onboarding.data + +import com.tangem.common.core.TangemSdkError +import com.tangem.crypto.bip39.BIP39Wordlist +import com.tangem.crypto.bip39.DefaultMnemonic +import com.tangem.crypto.bip39.EntropyLength +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.Wordlist +import com.tangem.datasource.asset.AssetReader +import javax.inject.Inject + +/** +[REDACTED_AUTHOR] + */ +interface MnemonicRepository { + + fun getWordsDictionary(): Set + + @Throws(TangemSdkError.MnemonicException::class) + fun generateDefaultMnemonic(): Mnemonic + + @Throws(TangemSdkError.MnemonicException::class) + fun createMnemonic(mnemonicString: String): Mnemonic +} + +class DefaultMnemonicRepository @Inject constructor( + private val assetReader: AssetReader, +) : MnemonicRepository { + + private val bip39Wordlist: Wordlist by lazy { BIP39Wordlist(assetReader.openFile(MNEMONIC_FILE_NAME)) } + private val bip39Words: Set by lazy { bip39Wordlist.words.toHashSet() } + + override fun getWordsDictionary(): Set = bip39Words + + override fun generateDefaultMnemonic(): Mnemonic { + return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) + } + + override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) + + companion object { + private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt new file mode 100644 index 0000000000..fe7e292403 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/di/OnboardingDataModule.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.onboarding.data.di + +import com.tangem.datasource.asset.AssetReader +import com.tangem.feature.onboarding.data.DefaultMnemonicRepository +import com.tangem.feature.onboarding.data.MnemonicRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class OnboardingDataModule { + + @Provides + @Singleton + fun provideSeedPhraseSdkRepository(assetReader: AssetReader): MnemonicRepository { + return DefaultMnemonicRepository(assetReader) + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt new file mode 100644 index 0000000000..1ce4c0588e --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt @@ -0,0 +1,41 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.common.module.ModuleError +import com.tangem.common.module.ModuleErrorCode +import com.tangem.common.module.ModuleMessage + +/** +[REDACTED_AUTHOR] + */ +abstract class OnboardingModuleError( + subCode: Int, + override val message: String, + override val data: Any?, +) : ModuleMessage, ModuleError() { + override val code: Int = ModuleErrorCode.ONBOARDING + subCode + + companion object { + // base code used for all errors in the module + internal const val ERROR_CODE_SEED_PHRASE = 100 +// const val CODE_ANY_OTHER = 200..299, 300..399 etc + } +} + +sealed class SeedPhraseError( + subCode: Int = 0, + message: String? = null, + data: Any? = null, +) : OnboardingModuleError( + subCode = ERROR_CODE_SEED_PHRASE + subCode, + message = message ?: this::class.java.simpleName, + data = data, +) { + object InvalidWordCount : SeedPhraseError(0) + object InvalidEntropyLength : SeedPhraseError(1) + object InvalidWordsFile : SeedPhraseError(2) + object InvalidChecksum : SeedPhraseError(3) + object MnenmonicCreationFailed : SeedPhraseError(4) + object NormalizationFailed : SeedPhraseError(5) + object UnsupportedLanguage : SeedPhraseError(6) + data class InvalidWords(val words: Set) : SeedPhraseError(7) +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt deleted file mode 100644 index e7aa5c0315..0000000000 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseChecker.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.feature.onboarding.domain - -/** -[REDACTED_AUTHOR] - */ -interface SeedPhraseChecker { - suspend fun isSeedPhraseMatch(seedPhrase: List): Boolean - suspend fun isPhraseMatch(phrase: String, source: List): Boolean -} - -class DummySeedPhraseChecker : SeedPhraseChecker { - - override suspend fun isSeedPhraseMatch(seedPhrase: List): Boolean = false - - override suspend fun isPhraseMatch(phrase: String, source: List): Boolean = source.contains(phrase) -} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt index 8d8dadd98a..3958fd0a9f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -1,21 +1,109 @@ package com.tangem.feature.onboarding.domain -import com.tangem.feature.onboarding.data.DummySeedPhraseRepository -import com.tangem.feature.onboarding.data.SeedPhraseRepository +import com.tangem.common.core.TangemSdkError +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.MnemonicErrorResult +import com.tangem.feature.onboarding.data.MnemonicRepository +import javax.inject.Inject /** [REDACTED_AUTHOR] */ -interface SeedPhraseInteractor : SeedPhraseChecker { - suspend fun generateSeedPhrase(): List +interface SeedPhraseInteractor { + suspend fun generateMnemonic(): Result + suspend fun getMnemonicComponents(): Result> + suspend fun wordIsMatch(word: String): Boolean + suspend fun validateMnemonicString(text: String): Result> + suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List + suspend fun insertSuggestionWord(text: String, suggestion: String, cursorPosition: Int): InsertSuggestionResult + + companion object { + const val MNEMONIC_DELIMITER = " " + } } -class DummySeedPhraseInteractor( - private val sdkSeedPhraseRepository: SeedPhraseRepository = DummySeedPhraseRepository(), - private val seedPhraseChecker: SeedPhraseChecker = DummySeedPhraseChecker(), -) : SeedPhraseInteractor, SeedPhraseChecker by seedPhraseChecker { +class DefaultSeedPhraseInteractor @Inject constructor( + private val repository: MnemonicRepository, +) : SeedPhraseInteractor { - override suspend fun generateSeedPhrase(): List { - return sdkSeedPhraseRepository.getWordList().words + private var currentMnemonic: Mnemonic? = null + + override suspend fun generateMnemonic(): Result { + return try { + currentMnemonic = repository.generateDefaultMnemonic() + Result.success(currentMnemonic!!) + } catch (ex: TangemSdkError.MnemonicException) { + Result.failure(ex) + } } + + override suspend fun getMnemonicComponents(): Result> { + return try { + val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { + currentMnemonic = this + } + Result.success(mnemonic.mnemonicComponents) + } catch (ex: TangemSdkError.MnemonicException) { + Result.failure(ex) + } + } + + override suspend fun wordIsMatch(word: String): Boolean { + return repository.getWordsDictionary().contains(word) + } + + override suspend fun validateMnemonicString(text: String): Result> { + val inputWords = text.split("\\s+".toRegex()).toSet() + val wordsDictionary = repository.getWordsDictionary() + + val invalidWords = inputWords + .filter { it.isNotEmpty() && !wordsDictionary.contains(it) } + .toSet() + + return if (invalidWords.isNotEmpty()) { + Result.failure(SeedPhraseError.InvalidWords(invalidWords)) + } else { + try { + val mnemonic = repository.createMnemonic(text) + Result.success(mnemonic.mnemonicComponents) + } catch (ex: TangemSdkError.MnemonicException) { + val error = ex.mnemonicResult.mapToError() + Result.failure(error) + } + } + } + + override suspend fun getSuggestions( + text: String, + hasSelection: Boolean, + cursorPosition: Int, + ): List { + // TODO: todo + return emptyList() + } + + override suspend fun insertSuggestionWord( + text: String, + suggestion: String, + cursorPosition: Int, + ): InsertSuggestionResult { + // TODO: todo + return InsertSuggestionResult("", 0) + } +} + +data class InsertSuggestionResult( + val text: String, + val cursorPosition: Int, +) + +private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { + MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile + MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum + MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed + MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed + MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage + is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words) } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/di/OnboardingDomainModule.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/di/OnboardingDomainModule.kt new file mode 100644 index 0000000000..108700efe3 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/di/OnboardingDomainModule.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.onboarding.domain.di + +import com.tangem.feature.onboarding.data.MnemonicRepository +import com.tangem.feature.onboarding.domain.DefaultSeedPhraseInteractor +import com.tangem.feature.onboarding.domain.SeedPhraseInteractor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class OnboardingDomainModule { + + @Provides + @Singleton + fun provideSeedPhraseInteractor(repository: MnemonicRepository): SeedPhraseInteractor { + return DefaultSeedPhraseInteractor(repository) + } +} \ No newline at end of file From d4eb48822a893f4234b7ddfa296248eac2f41eae Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 15:49:36 +0300 Subject: [PATCH 23/36] Updated on 2026-08-14 --- .../java/com/tangem/utils/extensions/Int.kt | 8 +++++ features/onboarding/build.gradle.kts | 1 + .../model/OnboardingSeedPhraseState.kt | 7 +++- .../presentation/wallet2/ui/StateBuilder.kt | 9 ++--- .../wallet2/ui/YourSeedPhraseScreen.kt | 12 ++++--- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 34 +++++++++++++++++-- 6 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/Int.kt diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Int.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Int.kt new file mode 100644 index 0000000000..af00c9f75a --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Int.kt @@ -0,0 +1,8 @@ +package com.tangem.utils.extensions + +/** +[REDACTED_AUTHOR] + */ +fun Int.isEven(): Boolean = this % 2 == 0 + +fun Int.isOdd(): Boolean = !this.isEven() \ No newline at end of file diff --git a/features/onboarding/build.gradle.kts b/features/onboarding/build.gradle.kts index e36d079c6b..61df104ad9 100644 --- a/features/onboarding/build.gradle.kts +++ b/features/onboarding/build.gradle.kts @@ -9,6 +9,7 @@ plugins { dependencies { /** Core modules */ + implementation(project(":common")) implementation(project(":core:featuretoggles")) implementation(project(":core:datasource")) implementation(project(":core:analytics")) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt index da3edf8aaa..9eea61c93b 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/OnboardingSeedPhraseState.kt @@ -36,10 +36,15 @@ data class AboutState( ) data class YourSeedPhraseState( - val mnemonicComponents: ImmutableList = persistentListOf(), + val mnemonicGridItems: ImmutableList = persistentListOf(), val buttonContinue: ButtonState, ) +data class MnemonicGridItem( + val index: Int, + val mnemonic: String, +) + data class CheckSeedPhraseState( val tvSecondPhrase: TextFieldState, val tvSeventhPhrase: TextFieldState, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt index 0a2f23c8b1..1a8a30975e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt @@ -5,6 +5,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState +import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState @@ -111,9 +112,9 @@ class StateBuilder( fun mnemonicGenerated( uiState: OnboardingSeedPhraseState, - mnemonicComponents: ImmutableList, + mnemonicGridItems: ImmutableList, ): OnboardingSeedPhraseState { - return updateMnemonicComponents(uiState, mnemonicComponents) + return updateMnemonicComponents(uiState, mnemonicGridItems) .copy( step = OnboardingSeedPhraseStep.YourSeedPhrase, aboutState = uiState.aboutState.copy( @@ -129,10 +130,10 @@ class StateBuilder( fun updateMnemonicComponents( uiState: OnboardingSeedPhraseState, - phraseList: ImmutableList, + mnemonicGridItems: ImmutableList, ): OnboardingSeedPhraseState = uiState.copy( yourSeedPhraseState = uiState.yourSeedPhraseState.copy( - mnemonicComponents = phraseList, + mnemonicGridItems = mnemonicGridItems, ), ) } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index f771266a37..75bfdc15e4 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -18,6 +18,7 @@ import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerW32 import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Description import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock @@ -50,7 +51,7 @@ fun YourSeedPhraseScreen( Box( modifier = Modifier.weight(1f), ) { - PhraseGreedBlock(state.mnemonicComponents) + PhraseGreedBlock(state.mnemonicGridItems) } Box { @@ -69,26 +70,27 @@ fun YourSeedPhraseScreen( } @Composable -private fun PhraseGreedBlock(phraseList: ImmutableList) { +private fun PhraseGreedBlock(mnemonicGridItems: ImmutableList) { LazyVerticalGrid( columns = GridCells.Fixed(2), ) { this.items( - count = phraseList.size, + count = mnemonicGridItems.size, ) { index -> Row( modifier = Modifier.padding(all = TangemTheme.dimens.size8), verticalAlignment = Alignment.CenterVertically, ) { + val item = mnemonicGridItems[index] SpacerW32() Text( modifier = Modifier.width(TangemTheme.dimens.size40), - text = "${index + 1}.", + text = "${item.index}.", style = TangemTheme.typography.body2, color = TangemTheme.colors.text.secondary, ) Text( - text = phraseList[index], + text = item.mnemonic, style = TangemTheme.typography.button, color = TangemTheme.colors.text.primary1, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 14c5167dd3..524c8f0adb 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -11,6 +11,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseUiAction import com.tangem.feature.onboarding.presentation.wallet2.model.IntroUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField @@ -21,7 +22,10 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUi import com.tangem.feature.onboarding.presentation.wallet2.ui.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer +import com.tangem.utils.extensions.isEven import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -123,7 +127,6 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonCreateWalletWithSeedPhraseClick() { - buttonGenerateSeedPhraseClick() } private fun buttonOtherOptionsClick() { @@ -141,14 +144,39 @@ class SeedPhraseViewModel @Inject constructor( updateUi { uiBuilder.generateMnemonicComponents(uiState) } interactor.generateMnemonic() .onSuccess { mnemonic -> - updateUi { uiBuilder.mnemonicGenerated(uiState, mnemonic.mnemonicComponents) } + val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents) + updateUi { uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList()) } } .onFailure { - // show error + // TODO: show error } } } + private suspend fun generateMnemonicGridList(mnemonicComponents: List): ImmutableList { + val size = mnemonicComponents.size + val splitIndex = if (size.isEven()) size / 2 else size / 2 + 1 + val leftColumn = mnemonicComponents.subList(0, splitIndex) + val rightColumn = mnemonicComponents.subList(splitIndex, size) + + val mnemonicGridItems = mutableListOf() + for (index in 0 until splitIndex) { + if (index <= leftColumn.size) mnemonicGridItems.add( + MnemonicGridItem( + index = index + 1, + mnemonic = leftColumn[index], + ), + ) + if (index < rightColumn.size) mnemonicGridItems.add( + MnemonicGridItem( + index = index + splitIndex + 1, + mnemonic = rightColumn[index], + ), + ) + } + return mnemonicGridItems.toImmutableList() + } + private fun buttonImportSeedPhraseClick() { viewModelScope.launchSingle { updateUi { uiBuilder.changeStep(uiState, OnboardingSeedPhraseStep.ImportSeedPhrase) } From f084f256b4c4f3e2e40d1a63bfdeb4ee01bb7d2f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Apr 2023 12:37:49 +0300 Subject: [PATCH 24/36] Updated on 2026-08-14 --- .../tangem/datasource/asset/AssetReader.kt | 8 +- .../data/DefaultMnemonicRepository.kt | 36 ++++++++ .../onboarding/data/MnemonicRepository.kt | 26 ------ .../domain/DefaultSeedPhraseInteractor.kt | 87 ++++++++++++++++++ .../domain/OnboardingModuleError.kt | 3 +- .../onboarding/domain/SeedPhraseInteractor.kt | 89 +------------------ .../wallet2/viewmodel/SeedPhraseViewModel.kt | 2 +- 7 files changed, 129 insertions(+), 122 deletions(-) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt index 5c02f67a7c..a3196c64aa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt @@ -9,13 +9,9 @@ import java.io.InputStream */ interface AssetReader { - /** Read content of json file from asset - * @param fileName - name of the file - * */ + /** Read content of json file [fileName] from asset */ fun readJson(fileName: String): String - /** Read content of a file [file] from asset as InputStream - * @param file - name of the file with extension - * */ + /** Open a file [file] from asset as InputStream */ fun openFile(file: String): InputStream } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt new file mode 100644 index 0000000000..d43a1a9e98 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt @@ -0,0 +1,36 @@ +package com.tangem.feature.onboarding.data + +import com.tangem.crypto.bip39.BIP39Wordlist +import com.tangem.crypto.bip39.DefaultMnemonic +import com.tangem.crypto.bip39.EntropyLength +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.Wordlist +import com.tangem.datasource.asset.AssetReader + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultMnemonicRepository( + private val assetReader: AssetReader, +) : MnemonicRepository { + + // TODO: change initialization after adding it to the sdk + private val bip39Wordlist: Wordlist by lazy { + assetReader.openFile(MNEMONIC_FILE_NAME) + .use { BIP39Wordlist(it) } + } + + private val bip39Words: Set by lazy { bip39Wordlist.words.toHashSet() } + + override fun getWordsDictionary(): Set = bip39Words + + override fun generateDefaultMnemonic(): Mnemonic { + return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) + } + + override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) + + companion object { + private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt index 4425671e1a..b1b7ebfa2c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt @@ -1,13 +1,7 @@ package com.tangem.feature.onboarding.data import com.tangem.common.core.TangemSdkError -import com.tangem.crypto.bip39.BIP39Wordlist -import com.tangem.crypto.bip39.DefaultMnemonic -import com.tangem.crypto.bip39.EntropyLength import com.tangem.crypto.bip39.Mnemonic -import com.tangem.crypto.bip39.Wordlist -import com.tangem.datasource.asset.AssetReader -import javax.inject.Inject /** [REDACTED_AUTHOR] @@ -21,24 +15,4 @@ interface MnemonicRepository { @Throws(TangemSdkError.MnemonicException::class) fun createMnemonic(mnemonicString: String): Mnemonic -} - -class DefaultMnemonicRepository @Inject constructor( - private val assetReader: AssetReader, -) : MnemonicRepository { - - private val bip39Wordlist: Wordlist by lazy { BIP39Wordlist(assetReader.openFile(MNEMONIC_FILE_NAME)) } - private val bip39Words: Set by lazy { bip39Wordlist.words.toHashSet() } - - override fun getWordsDictionary(): Set = bip39Words - - override fun generateDefaultMnemonic(): Mnemonic { - return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) - } - - override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) - - companion object { - private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" - } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt new file mode 100644 index 0000000000..1056aa7996 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -0,0 +1,87 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.common.core.TangemSdkError +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.MnemonicErrorResult +import com.tangem.feature.onboarding.data.MnemonicRepository + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultSeedPhraseInteractor constructor( + private val repository: MnemonicRepository, +) : SeedPhraseInteractor { + + private var currentMnemonic: Mnemonic? = null + + override suspend fun generateMnemonic(): Result { + return runCatching { + repository.generateDefaultMnemonic().apply { + currentMnemonic = this + } + } + } + + override suspend fun getMnemonicComponents(): Result> { + return runCatching { + val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { + currentMnemonic = this + } + mnemonic.mnemonicComponents + } + } + + override suspend fun isWordMatch(word: String): Boolean { + return repository.getWordsDictionary().contains(word) + } + + override suspend fun validateMnemonicString(text: String): Result> { + val inputWords = text.split("\\s+".toRegex()).toSet() + val wordsDictionary = repository.getWordsDictionary() + + val invalidWords = inputWords + .filter { it.isNotEmpty() && !wordsDictionary.contains(it) } + .toSet() + + return if (invalidWords.isNotEmpty()) { + Result.failure(SeedPhraseError.InvalidWords(invalidWords)) + } else { + try { + val mnemonic = repository.createMnemonic(text) + Result.success(mnemonic.mnemonicComponents) + } catch (ex: TangemSdkError.MnemonicException) { + val error = ex.mnemonicResult.mapToError() + Result.failure(error) + } + } + } + + override suspend fun getSuggestions( + text: String, + hasSelection: Boolean, + cursorPosition: Int, + ): List { + // TODO: todo + return emptyList() + } + + override suspend fun insertSuggestionWord( + text: String, + suggestion: String, + cursorPosition: Int, + ): InsertSuggestionResult { + // TODO: todo + return InsertSuggestionResult("", 0) + } +} + +private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { + MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile + MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum + MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed + MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed + MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage + is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt index 1ce4c0588e..be71e9203f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt @@ -6,6 +6,7 @@ import com.tangem.common.module.ModuleMessage /** [REDACTED_AUTHOR] + * For each pool of errors, use the codes lying in range 100 (200..299, 300..399, etc) */ abstract class OnboardingModuleError( subCode: Int, @@ -15,9 +16,7 @@ abstract class OnboardingModuleError( override val code: Int = ModuleErrorCode.ONBOARDING + subCode companion object { - // base code used for all errors in the module internal const val ERROR_CODE_SEED_PHRASE = 100 -// const val CODE_ANY_OTHER = 200..299, 300..399 etc } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt index 3958fd0a9f..43ed7ac068 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -1,10 +1,6 @@ package com.tangem.feature.onboarding.domain -import com.tangem.common.core.TangemSdkError import com.tangem.crypto.bip39.Mnemonic -import com.tangem.crypto.bip39.MnemonicErrorResult -import com.tangem.feature.onboarding.data.MnemonicRepository -import javax.inject.Inject /** [REDACTED_AUTHOR] @@ -12,7 +8,7 @@ import javax.inject.Inject interface SeedPhraseInteractor { suspend fun generateMnemonic(): Result suspend fun getMnemonicComponents(): Result> - suspend fun wordIsMatch(word: String): Boolean + suspend fun isWordMatch(word: String): Boolean suspend fun validateMnemonicString(text: String): Result> suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List suspend fun insertSuggestionWord(text: String, suggestion: String, cursorPosition: Int): InsertSuggestionResult @@ -22,88 +18,7 @@ interface SeedPhraseInteractor { } } -class DefaultSeedPhraseInteractor @Inject constructor( - private val repository: MnemonicRepository, -) : SeedPhraseInteractor { - - private var currentMnemonic: Mnemonic? = null - - override suspend fun generateMnemonic(): Result { - return try { - currentMnemonic = repository.generateDefaultMnemonic() - Result.success(currentMnemonic!!) - } catch (ex: TangemSdkError.MnemonicException) { - Result.failure(ex) - } - } - - override suspend fun getMnemonicComponents(): Result> { - return try { - val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { - currentMnemonic = this - } - Result.success(mnemonic.mnemonicComponents) - } catch (ex: TangemSdkError.MnemonicException) { - Result.failure(ex) - } - } - - override suspend fun wordIsMatch(word: String): Boolean { - return repository.getWordsDictionary().contains(word) - } - - override suspend fun validateMnemonicString(text: String): Result> { - val inputWords = text.split("\\s+".toRegex()).toSet() - val wordsDictionary = repository.getWordsDictionary() - - val invalidWords = inputWords - .filter { it.isNotEmpty() && !wordsDictionary.contains(it) } - .toSet() - - return if (invalidWords.isNotEmpty()) { - Result.failure(SeedPhraseError.InvalidWords(invalidWords)) - } else { - try { - val mnemonic = repository.createMnemonic(text) - Result.success(mnemonic.mnemonicComponents) - } catch (ex: TangemSdkError.MnemonicException) { - val error = ex.mnemonicResult.mapToError() - Result.failure(error) - } - } - } - - override suspend fun getSuggestions( - text: String, - hasSelection: Boolean, - cursorPosition: Int, - ): List { - // TODO: todo - return emptyList() - } - - override suspend fun insertSuggestionWord( - text: String, - suggestion: String, - cursorPosition: Int, - ): InsertSuggestionResult { - // TODO: todo - return InsertSuggestionResult("", 0) - } -} - data class InsertSuggestionResult( val text: String, val cursorPosition: Int, -) - -private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { - MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength - MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength - MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile - MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum - MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed - MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed - MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage - is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words) -} \ No newline at end of file +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 524c8f0adb..77db2b3f13 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -105,7 +105,7 @@ class SeedPhraseViewModel @Inject constructor( } createOrGetDebouncer(field.name).debounce(viewModelScope, context = dispatchers.io) { - val hasError = !interactor.wordIsMatch(textFieldValue.text) + val hasError = !interactor.isWordMatch(textFieldValue.text) if (fieldState.isError != hasError) { updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) } } From 6631ad696c0b5d417ac06fe19bb50bb6c28bf052 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 21:14:54 +0300 Subject: [PATCH 25/36] Updated on 2026-08-14 --- .../java/com/tangem/utils/extensions/Char.kt | 6 ++ .../com/tangem/utils/extensions/Collection.kt | 6 ++ .../wallet2/ui/ImportSeedPhraseScreen.kt | 10 ++- .../ui/ImportSeedPhraseStateBuilder.kt | 55 +++++++++++++ .../presentation/wallet2/ui/StateBuilder.kt | 1 + .../InvalidWordsColorTransformation.kt | 79 +++++++++++++++++++ .../wallet2/viewmodel/SeedPhraseViewModel.kt | 69 +++++++++++++++- 7 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/Char.kt create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt new file mode 100644 index 0000000000..d4c95154b3 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt @@ -0,0 +1,6 @@ +package com.tangem.utils.extensions + +/** +[REDACTED_AUTHOR] + */ +fun Char.isNotWhitespace(): Boolean = !this.isWhitespace() diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt new file mode 100644 index 0000000000..89fd92851b --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Collection.kt @@ -0,0 +1,6 @@ +package com.tangem.utils.extensions + +/** +[REDACTED_AUTHOR] + */ +fun Collection.isSingleItem(): Boolean = this.size == 1 \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index d79aa76288..f47740fc81 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.unit.IntOffset import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.PrimaryButtonIconLeft @@ -29,6 +30,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhrase import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.InvalidWordsColorTransformation /** [REDACTED_AUTHOR] @@ -45,7 +47,9 @@ fun ImportSeedPhraseScreen( modifier = Modifier.weight(1f), ) { Column { - OnboardingDescriptionBlock(modifier) { + OnboardingDescriptionBlock( + modifier = Modifier.padding(top = TangemTheme.dimens.size16), + ) { DescriptionSubTitleText(text = stringResource(id = R.string.onboarding_seed_import_message)) } PhraseBlock( @@ -94,6 +98,10 @@ private fun PhraseBlock( onValueChange = state.tvSeedPhrase.onTextFieldValueChanged, textStyle = TangemTheme.typography.body1, singleLine = false, + visualTransformation = InvalidWordsColorTransformation( + wordsToBrush = state.invalidWords, + style = SpanStyle(color = TangemTheme.colors.text.warning), + ), colors = TangemTextFieldsDefault.defaultTextFieldColors, ) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt new file mode 100644 index 0000000000..563152917c --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt @@ -0,0 +1,55 @@ +package com.tangem.feature.onboarding.presentation.wallet2.ui + +import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.domain.SeedPhraseError +import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState + +/** +[REDACTED_AUTHOR] + */ +class ImportSeedPhraseStateBuilder { + + fun updateTextField( + uiState: OnboardingSeedPhraseState, + textFieldValue: TextFieldValue, + ): OnboardingSeedPhraseState = uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + tvSeedPhrase = uiState.importSeedPhraseState.tvSeedPhrase.copy( + textFieldValue = textFieldValue, + ), + ), + ) + + fun updateCreateWalletButton( + uiState: OnboardingSeedPhraseState, + enabled: Boolean, + ): OnboardingSeedPhraseState { + return uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + buttonCreateWallet = uiState.importSeedPhraseState.buttonCreateWallet.copy( + enabled = enabled, + ), + ), + ) + } + + fun updateInvalidWords( + uiState: OnboardingSeedPhraseState, + invalidWords: Set, + ): OnboardingSeedPhraseState = uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + invalidWords = invalidWords, + ), + ) + + fun updateError( + uiState: OnboardingSeedPhraseState, + error: SeedPhraseError?, + ): OnboardingSeedPhraseState { + return uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + error = error, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt index 1a8a30975e..ddd2e242d3 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt @@ -21,6 +21,7 @@ class StateBuilder( ) { val checkSeedPhrase: CheckSeedPhraseStateBuilder = CheckSeedPhraseStateBuilder() + val importSeedPhrase: ImportSeedPhraseStateBuilder = ImportSeedPhraseStateBuilder() fun init(): OnboardingSeedPhraseState = OnboardingSeedPhraseState( step = OnboardingSeedPhraseStep.Intro, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt new file mode 100644 index 0000000000..ec566f8013 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.onboarding.presentation.wallet2.viewmodel + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation +import com.tangem.utils.extensions.isSingleItem + +/** +[REDACTED_AUTHOR] + */ +class InvalidWordsColorTransformation( + private val wordsToBrush: Set, + private val style: SpanStyle, +) : VisualTransformation { + + override fun filter(text: AnnotatedString): TransformedText { + val plainText = text.text + if (wordsToBrush.isSingleItem() && wordsToBrush.first() == plainText) { + return plainText.annotate().toTransformedText() + } + + val wordList = makeSeparatedWordlist(plainText) + val annotatedString = makeAnnotatedString(wordList) + + return annotatedString.toTransformedText() + } + + private fun makeSeparatedWordlist(plainText: String): List { + val plainTextCharArray = plainText.toCharArray() + val wordlist = mutableListOf() + var wordBuilder = StringBuilder() + + for (index in plainTextCharArray.indices) { + val char = plainTextCharArray[index] + if (char.isWhitespace()) { + if (wordBuilder.isEmpty()) { + wordlist.add(WORD_SEPARATOR) + } else { + wordlist.add(wordBuilder.toString()) + wordBuilder = StringBuilder() + wordlist.add(WORD_SEPARATOR) + } + } else { + if (index == plainTextCharArray.lastIndex) { + wordBuilder.append(char) + wordlist.add(wordBuilder.toString()) + wordBuilder = StringBuilder() + } else { + wordBuilder.append(char) + } + } + } + return wordlist.toList() + } + + private fun makeAnnotatedString(wordList: List): AnnotatedString = buildAnnotatedString { + wordList.forEach { + if (it == WORD_SEPARATOR) { + append(WHITE_SPACE) + } else if (wordsToBrush.contains(it)) { + append(it.annotate()) + } else { + append(it) + } + } + } + + private fun String.annotate(): AnnotatedString = AnnotatedString(this, style) + + private fun AnnotatedString.toTransformedText(): TransformedText = TransformedText(this, OffsetMapping.Identity) + + companion object { + private const val WHITE_SPACE = " " + private const val WORD_SEPARATOR = "_$@-*" + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 77db2b3f13..473781349e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.domain.SeedPhraseInteractor import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction @@ -84,8 +85,7 @@ class SeedPhraseViewModel @Inject constructor( ), importSeedPhraseActions = ImportSeedPhraseUiAction( phraseTextFieldAction = TextFieldUiAction( - onFocusChanged = { isFocused -> /* [REDACTED_TODO_COMMENT] */ }, - onTextFieldChanged = { value -> /* [REDACTED_TODO_COMMENT] */ }, + onTextFieldChanged = { value -> onSeedPhraseTextFieldChanged(value) }, ), suggestedPhraseClick = ::buttonSuggestedPhraseClick, buttonCreateWalletClick = ::buttonCreateWalletWithSeedPhraseClick, @@ -122,6 +122,66 @@ class SeedPhraseViewModel @Inject constructor( } // endregion CheckSeedPhrase + // region ImportSeedPhrase + private fun onSeedPhraseTextFieldChanged(textFieldValue: TextFieldValue) { + viewModelScope.launchSingle { + val oldTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue + val isSameText = textFieldValue.text == oldTextFieldValue.text + val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection + + updateUi { + val mediateState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue) + uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false) + } + + val fieldState = uiState.importSeedPhraseState.tvSeedPhrase + val inputMnemonic = fieldState.textFieldValue.text + + when { + isSameText && !isCursorMoved -> { + return@launchSingle + } + isSameText && isCursorMoved -> { + return@launchSingle + } + } + + val isPasteFromClipboard = textFieldValue.text.length - oldTextFieldValue.text.length > 1 + if (isPasteFromClipboard) { + validateMnemonic(inputMnemonic) + } else { + val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) + debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.io) { + validateMnemonic(inputMnemonic) + } + } + } + } + + private suspend fun validateMnemonic(inputMnemonic: String) { + if (inputMnemonic.isEmpty() && uiState.importSeedPhraseState.error != null) { + updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) } + return + } + + interactor.validateMnemonicString(inputMnemonic) + .onSuccess { + updateUi { uiBuilder.importSeedPhrase.updateError(uiState, null) } + } + .onFailure { + val error = it as? SeedPhraseError ?: return + + val mediateState = when (error) { + is SeedPhraseError.InvalidWords -> { + uiBuilder.importSeedPhrase.updateInvalidWords(uiState, error.words) + } + else -> uiState + } + updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) } + } + } + // endregion ImportSeedPhrase + // region ButtonClickHandlers private fun buttonCreateWalletClick() { } @@ -231,4 +291,9 @@ class SeedPhraseViewModel @Inject constructor( return withContext(dispatchers.single, block) } // endregion Utils + + companion object { + private const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer" + private const val MNEMONIC_DEBOUNCE_DELAY = 700L + } } \ No newline at end of file From 0c9200cf87d9348f31fff96d4a169a48437a774d Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 21:20:12 +0300 Subject: [PATCH 26/36] Updated on 2026-08-14 --- .../wallet2/ui/ImportSeedPhraseScreen.kt | 20 ++++++++----- .../viewmodel/SeedPhraseErrorConverter.kt | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index f47740fc81..8885e2055d 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -31,6 +32,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Descript import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingActionBlock import com.tangem.feature.onboarding.presentation.wallet2.ui.components.OnboardingDescriptionBlock import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.InvalidWordsColorTransformation +import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseErrorConverter /** [REDACTED_AUTHOR] @@ -110,14 +112,16 @@ private fun PhraseBlock( .fillMaxWidth() .height(TangemTheme.dimens.size32), ) { - Text( - modifier = Modifier - .fillMaxSize(), - text = "TODO: implement error message", - style = TangemTheme.typography.caption.copy( - color = TangemTheme.colors.text.warning, - ), - ) + val message = SeedPhraseErrorConverter(LocalContext.current).convert(state.error) + if (message != null) { + Text( + modifier = Modifier.fillMaxSize(), + text = message, + style = TangemTheme.typography.caption.copy( + color = TangemTheme.colors.text.warning, + ), + ) + } } } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt new file mode 100644 index 0000000000..a8a387cde6 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt @@ -0,0 +1,30 @@ +package com.tangem.feature.onboarding.presentation.wallet2.viewmodel + +import android.content.Context +import com.tangem.common.module.ModuleMessageConverter +import com.tangem.feature.onboarding.R +import com.tangem.feature.onboarding.domain.SeedPhraseError + +/** +[REDACTED_AUTHOR] + */ +class SeedPhraseErrorConverter( + private val context: Context, +) : ModuleMessageConverter { + + override fun convert(message: SeedPhraseError?): String? { + message ?: return null + + val convertedMessage = when (message) { + SeedPhraseError.InvalidEntropyLength -> { + context.getString(R.string.onboarding_seed_mnemonic_invalid_checksum) + } + is SeedPhraseError.InvalidWords -> { + context.getString(R.string.onboarding_seed_mnemonic_wrong_words) + } + else -> null + } + + return convertedMessage + } +} \ No newline at end of file From 8f845111f33eead4035a821f254aa5d62f59f18c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Apr 2023 15:47:17 +0300 Subject: [PATCH 27/36] Updated on 2026-08-14 --- .../presentation/wallet2/ui/ImportSeedPhraseScreen.kt | 6 +++++- .../wallet2/viewmodel/SeedPhraseErrorConverter.kt | 8 +++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index 8885e2055d..c81feb5cfb 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -89,6 +90,9 @@ private fun PhraseBlock( state: ImportSeedPhraseState, modifier: Modifier = Modifier, ) { + // TODO: replace by TextReference + val errorConverter = remember { SeedPhraseErrorConverter() } + Column( modifier = modifier.fillMaxWidth(), ) { @@ -112,7 +116,7 @@ private fun PhraseBlock( .fillMaxWidth() .height(TangemTheme.dimens.size32), ) { - val message = SeedPhraseErrorConverter(LocalContext.current).convert(state.error) + val message = errorConverter.convert(LocalContext.current to state.error) if (message != null) { Text( modifier = Modifier.fillMaxSize(), diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt index a8a387cde6..dbb553ba63 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseErrorConverter.kt @@ -8,12 +8,10 @@ import com.tangem.feature.onboarding.domain.SeedPhraseError /** [REDACTED_AUTHOR] */ -class SeedPhraseErrorConverter( - private val context: Context, -) : ModuleMessageConverter { +class SeedPhraseErrorConverter : ModuleMessageConverter, String?> { - override fun convert(message: SeedPhraseError?): String? { - message ?: return null + override fun convert(source: Pair): String? { + val (context, message) = source val convertedMessage = when (message) { SeedPhraseError.InvalidEntropyLength -> { From 3a20cd143cc440af9be962a30aefaa8b53e49e7b Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 16 Apr 2023 21:54:28 +0300 Subject: [PATCH 28/36] Updated on 2026-08-14 --- .../onboarding/domain/PartWordFinder.kt | 49 +++++++++++++++++++ .../ui/ImportSeedPhraseStateBuilder.kt | 22 +++++++++ .../wallet2/viewmodel/SeedPhraseViewModel.kt | 34 +++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt new file mode 100644 index 0000000000..6441764a34 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt @@ -0,0 +1,49 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.utils.extensions.isNotWhitespace + +/** +[REDACTED_AUTHOR] + */ +internal class PartWordFinder { + + fun getLeadPartOfWord(text: String, cursorPosition: Int): String? { + val charText = text.toCharArray().asList() + if (cursorPosition > charText.size || cursorPosition < 0) { + // cursorPosition is out of text size + return null + } + if (cursorPosition == 0) { + // cursorPosition at start of text + return null + } + if (charText[cursorPosition - 1].isWhitespace()) { + // white space to the left of cursorPosition + return null + } + if (charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace()) { + // cursorPosition in the middle of a word + return null + } + + for (charIndex in cursorPosition - 1 downTo 0) { + val char = charText[charIndex] + when { + charIndex == 0 -> { + return text.substring(charIndex, cursorPosition) + } + char.isWhitespace() -> { + return text.substring(charIndex + 1, cursorPosition) + } + } + } + + return null + } + + fun getLastPartOfWord(text: String, word: String, cursorPosition: Int): String { + val leadPart = getLeadPartOfWord(text, cursorPosition) ?: return "" + + return word.substring(leadPart.length, word.length) + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt index 563152917c..f8974e1207 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt @@ -1,6 +1,8 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue +import com.tangem.feature.onboarding.domain.InsertSuggestionResult import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState @@ -42,6 +44,26 @@ class ImportSeedPhraseStateBuilder { ), ) + fun updateSuggestions( + uiState: OnboardingSeedPhraseState, + suggestions: List, + ): OnboardingSeedPhraseState = uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + suggestionsList = suggestions, + ), + ) + + fun insertSuggestionWord( + uiState: OnboardingSeedPhraseState, + insertResult: InsertSuggestionResult, + ): OnboardingSeedPhraseState { + val newTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue.copy( + text = insertResult.text, + selection = TextRange(insertResult.cursorPosition, insertResult.cursorPosition), + ) + return updateTextField(uiState, newTextFieldValue) + } + fun updateError( uiState: OnboardingSeedPhraseState, error: SeedPhraseError?, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 473781349e..ae997b06d7 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject /** @@ -51,6 +52,7 @@ class SeedPhraseViewModel @Inject constructor( get() = uiState.step private val textFieldsDebouncers = mutableMapOf() + private var suggestionWordInserted: AtomicBoolean = AtomicBoolean(false) override fun onCleared() { textFieldsDebouncers.forEach { entry -> entry.value.release() } @@ -138,18 +140,25 @@ class SeedPhraseViewModel @Inject constructor( val inputMnemonic = fieldState.textFieldValue.text when { + suggestionWordInserted.getAndSet(false) -> { + validateMnemonic(inputMnemonic) + return@launchSingle + } isSameText && !isCursorMoved -> { return@launchSingle } isSameText && isCursorMoved -> { + updateSuggestions(fieldState) return@launchSingle } } val isPasteFromClipboard = textFieldValue.text.length - oldTextFieldValue.text.length > 1 if (isPasteFromClipboard) { + updateSuggestions(fieldState) validateMnemonic(inputMnemonic) } else { + updateSuggestions(fieldState) val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.io) { validateMnemonic(inputMnemonic) @@ -180,6 +189,15 @@ class SeedPhraseViewModel @Inject constructor( updateUi { uiBuilder.importSeedPhrase.updateError(mediateState, error) } } } + + private suspend fun updateSuggestions(fieldState: TextFieldState) { + val suggestions = interactor.getSuggestions( + text = fieldState.textFieldValue.text, + hasSelection = !fieldState.textFieldValue.selection.collapsed, + cursorPosition = fieldState.textFieldValue.selection.end, + ) + updateUi { uiBuilder.importSeedPhrase.updateSuggestions(uiState, suggestions) } + } // endregion ImportSeedPhrase // region ButtonClickHandlers @@ -250,6 +268,22 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { + viewModelScope.launchSingle { + suggestionWordInserted.set(true) + val textFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue + val word = uiState.importSeedPhraseState.suggestionsList[suggestionIndex] + val cursorPosition = textFieldValue.selection.end + + val insertResult = interactor.insertSuggestionWord( + text = textFieldValue.text, + suggestion = word, + cursorPosition = cursorPosition, + ) + updateUi { + val mediateState = uiBuilder.importSeedPhrase.insertSuggestionWord(uiState, insertResult) + uiBuilder.importSeedPhrase.updateSuggestions(mediateState, emptyList()) + } + } } private fun menuChatClick() { From 9b6100c09c36c4af01abcb7c7f4e5a5d3418f5dd Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Apr 2023 16:20:34 +0300 Subject: [PATCH 29/36] Updated on 2026-08-14 --- .../domain/DefaultSeedPhraseInteractor.kt | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt index 1056aa7996..2860aa5b8a 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -4,6 +4,7 @@ import com.tangem.common.core.TangemSdkError import com.tangem.crypto.bip39.Mnemonic import com.tangem.crypto.bip39.MnemonicErrorResult import com.tangem.feature.onboarding.data.MnemonicRepository +import com.tangem.utils.extensions.isNotWhitespace /** [REDACTED_AUTHOR] @@ -13,6 +14,7 @@ internal class DefaultSeedPhraseInteractor constructor( ) : SeedPhraseInteractor { private var currentMnemonic: Mnemonic? = null + private val partWordFinder: PartWordFinder = PartWordFinder() override suspend fun generateMnemonic(): Result { return runCatching { @@ -61,8 +63,15 @@ internal class DefaultSeedPhraseInteractor constructor( hasSelection: Boolean, cursorPosition: Int, ): List { - // TODO: todo - return emptyList() + if (text.isEmpty() || cursorPosition == 0 || hasSelection) return emptyList() + val word = partWordFinder.getLeadPartOfWord(text, cursorPosition) + ?: return emptyList() + + val suggestions = repository.getWordsDictionary() + .filter { it.startsWith(word, ignoreCase = false) && it != word } + .toList() + + return suggestions } override suspend fun insertSuggestionWord( @@ -70,8 +79,23 @@ internal class DefaultSeedPhraseInteractor constructor( suggestion: String, cursorPosition: Int, ): InsertSuggestionResult { - // TODO: todo - return InsertSuggestionResult("", 0) + val textPartLead = text.substring(0, cursorPosition) + val textPartLast = text.substring(cursorPosition, text.length) + val wordPartLast = partWordFinder.getLastPartOfWord(text, suggestion, cursorPosition) + + val textBuilder = StringBuilder() + .append(textPartLead) + .append(wordPartLast) + + if (textPartLast.isEmpty() || textPartLast[0].isNotWhitespace()) { + textBuilder.append(SeedPhraseInteractor.MNEMONIC_DELIMITER) + } + textBuilder.append(textPartLast) + + val textWithSuggestion = textBuilder.toString() + val newCursorPosition = cursorPosition + wordPartLast.length + SeedPhraseInteractor.MNEMONIC_DELIMITER.length + + return InsertSuggestionResult(textWithSuggestion, newCursorPosition) } } From 282772a0fc4b612c10b83f78f521300c097f9435 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Apr 2023 20:12:05 +0300 Subject: [PATCH 30/36] Updated on 2026-08-14 --- .../tangem/feature/onboarding/domain/PartWordFinder.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt index 6441764a34..46518bacce 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt @@ -4,9 +4,13 @@ import com.tangem.utils.extensions.isNotWhitespace /** [REDACTED_AUTHOR] + * Class which provides helper functions to determine lead or last part of words for suggestions */ internal class PartWordFinder { + /** + * Searches and return part of a word on the left side of the [cursorPosition] into the [text] + */ fun getLeadPartOfWord(text: String, cursorPosition: Int): String? { val charText = text.toCharArray().asList() if (cursorPosition > charText.size || cursorPosition < 0) { @@ -41,6 +45,10 @@ internal class PartWordFinder { return null } + /** + * Returns a substring of the [word] which excludes the first word part found on the left of [cursorPosition] in + * the [text] + */ fun getLastPartOfWord(text: String, word: String, cursorPosition: Int): String { val leadPart = getLeadPartOfWord(text, cursorPosition) ?: return "" From 500706ecbbe10b5c2d1cd6074a7773f58352f61c Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 20 Apr 2023 15:36:10 +0300 Subject: [PATCH 31/36] Updated on 2026-08-14 --- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index ae997b06d7..d4329b1d9f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -126,41 +126,34 @@ class SeedPhraseViewModel @Inject constructor( // region ImportSeedPhrase private fun onSeedPhraseTextFieldChanged(textFieldValue: TextFieldValue) { - viewModelScope.launchSingle { - val oldTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue - val isSameText = textFieldValue.text == oldTextFieldValue.text - val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection + val oldTextFieldValue = uiState.importSeedPhraseState.tvSeedPhrase.textFieldValue + val isSameText = textFieldValue.text == oldTextFieldValue.text + val isCursorMoved = textFieldValue.selection != oldTextFieldValue.selection - updateUi { - val mediateState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue) - uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false) - } + val mediateState = uiBuilder.importSeedPhrase.updateTextField(uiState, textFieldValue) + uiState = uiBuilder.importSeedPhrase.updateCreateWalletButton(mediateState, enabled = false) - val fieldState = uiState.importSeedPhraseState.tvSeedPhrase - val inputMnemonic = fieldState.textFieldValue.text + val fieldState = uiState.importSeedPhraseState.tvSeedPhrase + val inputMnemonic = fieldState.textFieldValue.text - when { - suggestionWordInserted.getAndSet(false) -> { + val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) + when { + suggestionWordInserted.getAndSet(false) -> { + debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { validateMnemonic(inputMnemonic) - return@launchSingle - } - isSameText && !isCursorMoved -> { - return@launchSingle - } - isSameText && isCursorMoved -> { - updateSuggestions(fieldState) - return@launchSingle } } - - val isPasteFromClipboard = textFieldValue.text.length - oldTextFieldValue.text.length > 1 - if (isPasteFromClipboard) { - updateSuggestions(fieldState) - validateMnemonic(inputMnemonic) - } else { - updateSuggestions(fieldState) - val debouncer = createOrGetDebouncer(MNEMONIC_DEBOUNCER) - debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.io) { + isSameText && !isCursorMoved -> { + // do nothing + } + isSameText && isCursorMoved -> { + debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { + updateSuggestions(fieldState) + } + } + else -> { + debouncer.debounce(viewModelScope, MNEMONIC_DEBOUNCE_DELAY, dispatchers.single) { + updateSuggestions(fieldState) validateMnemonic(inputMnemonic) } } From 6460876262f3909107215ffc6e35dafdb6ae2226 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 21 Apr 2023 10:44:25 +0300 Subject: [PATCH 32/36] Updated on 2026-08-14 --- .../onboarding/domain/PartWordFinder.kt | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt index 46518bacce..083948ff1e 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt @@ -13,20 +13,12 @@ internal class PartWordFinder { */ fun getLeadPartOfWord(text: String, cursorPosition: Int): String? { val charText = text.toCharArray().asList() - if (cursorPosition > charText.size || cursorPosition < 0) { - // cursorPosition is out of text size - return null - } - if (cursorPosition == 0) { - // cursorPosition at start of text - return null - } - if (charText[cursorPosition - 1].isWhitespace()) { - // white space to the left of cursorPosition - return null - } - if (charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace()) { - // cursorPosition in the middle of a word + val cursorIsOutOfTextRange = cursorPosition > charText.size || cursorPosition < 0 + val cursorAtStartOfText = cursorPosition == 0 + val hadWhiteSpaceOnLeftOfCursor = charText[cursorPosition - 1].isWhitespace() + val cursorInMiddleOfWord = charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace() + + if (cursorIsOutOfTextRange || cursorAtStartOfText || hadWhiteSpaceOnLeftOfCursor || cursorInMiddleOfWord) { return null } From 796f9ea50430145c14ab5b88a4aa56d515a258e7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Apr 2023 14:48:38 +0300 Subject: [PATCH 33/36] Updated on 2026-08-14 --- .../onboarding/data/SeedPhraseRepository.kt | 107 ------------------ .../CheckSeedPhraseStateBuilder.kt | 2 +- .../ImportSeedPhraseStateBuilder.kt | 2 +- .../ui/{ => stateBuiders}/StateBuilder.kt | 2 +- 4 files changed, 3 insertions(+), 110 deletions(-) delete mode 100644 features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt rename features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/{ => stateBuiders}/CheckSeedPhraseStateBuilder.kt (97%) rename features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/{ => stateBuiders}/ImportSeedPhraseStateBuilder.kt (97%) rename features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/{ => stateBuiders}/StateBuilder.kt (98%) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt deleted file mode 100644 index 42270141ed..0000000000 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/SeedPhraseRepository.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.tangem.feature.onboarding.data - -import com.tangem.crypto.bip39.EntropyLength -import com.tangem.crypto.bip39.Wordlist - -/** -[REDACTED_AUTHOR] - */ -interface SeedPhraseRepository { - fun getWordList(): Wordlist -} - -class DummySeedPhraseRepository : SeedPhraseRepository { - override fun getWordList(): Wordlist = DummyWordlist() -} - -class DummyWordlist( - entropyLength: EntropyLength = EntropyLength.Bits128Length, - random: Boolean = false, -) : Wordlist { - - private var _wordlist: List = listOf() - - override val words: List - get() = _wordlist - - init { - if (random) regenerateRandom() else regenerate(entropyLength) - } - - fun regenerateRandom(entropyLength: EntropyLength = EntropyLength.Bits128Length) { - val minWordLength = 4 - val maxWordLength = 10 - _wordlist = (0 until entropyLength.wordCount()) - .map { - randomString(maxLength = randomInt(minWordLength, maxWordLength)) - } - } - - fun regenerate(entropyLength: EntropyLength = EntropyLength.Bits128Length) { - _wordlist = getStaticWordlist().subList(0, entropyLength.wordCount() - 1) - } - - private fun randomString(maxLength: Int): String { - val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9') - return (0..maxLength) - .map { randomInt(0, charPool.size) } - .map(charPool::get) - .joinToString("") - } - - private fun randomInt(from: Int, to: Int): Int = kotlin.random.Random.nextInt(from, to) - - private fun getStaticWordlist(): List { - return listOf( - "lorem", - "ipsum", - "dolor", - "amet", - "consectetur", - "adipiscing", - "elit", - "eiusmod", - "tempor", - "incididunt", - "labore", - "dolore", - "magna", - "aliqua", - "enim", - "minim", - "veniam", - "quis", - "nostrud", - "exercitation", - "ullamco", - "laboris", - "nisi", - "aliquip", - "commodo", - "consequat", - "duis", - "aute", - "irure", - "reprehenderit", - "voluptate", - "velit", - "esse", - "cillum", - "fugiat", - "nulla", - "pariatur", - "excepteur", - "sint", - "occaecat", - "cupidatat", - "proident", - "sunt", - "culpa", - "officia", - "deserunt", - "mollit", - "anim", - "laborum", - ) - } -} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt similarity index 97% rename from features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt rename to features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt index cb69985684..5d2fe283b6 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.onboarding.presentation.wallet2.ui +package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt similarity index 97% rename from features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt rename to features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt index f8974e1207..8bfa374b82 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.onboarding.presentation.wallet2.ui +package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt similarity index 98% rename from features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt rename to features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt index ddd2e242d3..61be456d93 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.onboarding.presentation.wallet2.ui +package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState From 2a52fcd7edbf586322ee3213ea45a019ff002ce2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Apr 2023 14:49:39 +0300 Subject: [PATCH 34/36] Updated on 2026-08-14 --- .../presentation/wallet2/ui/AboutSeedPhraseScreen.kt | 3 +-- .../presentation/wallet2/ui/ImportSeedPhraseScreen.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index 94ac274ac6..ecc135c5f2 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -74,8 +74,7 @@ fun AboutSeedPhraseScreen( secondActionContent = { SecondaryButton( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_seed_intro_button_import), enabled = state.buttonImportSeedPhrase.enabled, showProgress = state.buttonImportSeedPhrase.showProgress, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index c81feb5cfb..d159f76094 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -71,8 +71,7 @@ fun ImportSeedPhraseScreen( firstActionContent = { PrimaryButtonIconLeft( modifier = Modifier - .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size16), + .fillMaxWidth(), text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet), icon = painterResource(id = R.drawable.ic_tangem_24), enabled = state.buttonCreateWallet.enabled, From 5024408d45e9a54042226a62b3dd1771eed066d7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Apr 2023 14:50:01 +0300 Subject: [PATCH 35/36] Updated on 2026-08-14 --- .../presentation/wallet2/viewmodel/SeedPhraseViewModel.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index d4329b1d9f..09de62d798 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -20,7 +20,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldUiAction import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUiAction -import com.tangem.feature.onboarding.presentation.wallet2.ui.StateBuilder +import com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer import com.tangem.utils.extensions.isEven @@ -29,6 +29,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.concurrent.atomic.AtomicBoolean @@ -213,6 +214,7 @@ class SeedPhraseViewModel @Inject constructor( private fun buttonGenerateSeedPhraseClick() { viewModelScope.launchSingle { updateUi { uiBuilder.generateMnemonicComponents(uiState) } + delay(300) interactor.generateMnemonic() .onSuccess { mnemonic -> val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents) From 4e81ef251da5edaa10ea6f372d739cbd15581671 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Apr 2023 09:37:15 +0300 Subject: [PATCH 36/36] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- .../coroutines/CoroutineDispatcherProvider.kt | 4 +- .../java/com/tangem/utils/extensions/Char.kt | 2 +- .../data/DefaultMnemonicRepository.kt | 8 +--- .../domain/DefaultSeedPhraseInteractor.kt | 6 +-- .../domain/OnboardingModuleError.kt | 18 ++++---- .../onboarding/domain/PartWordFinder.kt | 3 +- .../presentation/wallet2/model/UiActions.kt | 3 +- .../wallet2/ui/AboutSeedPhraseScreen.kt | 19 ++------- .../wallet2/ui/CheckSeedPhraseScreen.kt | 17 ++------ .../wallet2/ui/ImportSeedPhraseScreen.kt | 37 +++++----------- .../presentation/wallet2/ui/IntroScreen.kt | 21 +++------- .../wallet2/ui/YourSeedPhraseScreen.kt | 20 +++------ .../components/OnboardingDescriptionBlock.kt | 15 +++---- .../CheckSeedPhraseStateBuilder.kt | 7 +--- .../ImportSeedPhraseStateBuilder.kt | 38 +++++++---------- .../wallet2/ui/stateBuiders/StateBuilder.kt | 26 +++--------- .../InvalidWordsColorTransformation.kt | 2 +- .../wallet2/viewmodel/SeedPhraseViewModel.kt | 42 +++++++------------ 19 files changed, 93 insertions(+), 197 deletions(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index f5f6ac9548..c714678d9c 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit f5f6ac954894ef66331db8fe383b9773feb69ef4 +Subproject commit c714678d9c46dee1b15ef9e8d27aa23afa1dfd8d diff --git a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt index 597114b801..6f28c0350e 100644 --- a/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt +++ b/core/utils/src/main/java/com/tangem/utils/coroutines/CoroutineDispatcherProvider.kt @@ -22,5 +22,5 @@ class TestingCoroutineDispatcherProvider( override val main: CoroutineDispatcher = Dispatchers.Unconfined, override val io: CoroutineDispatcher = Dispatchers.Unconfined, override val single: CoroutineDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher(), - -) : CoroutineDispatcherProvider \ No newline at end of file +) : CoroutineDispatcherProvider + diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt b/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt index d4c95154b3..0eed425110 100644 --- a/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt +++ b/core/utils/src/main/java/com/tangem/utils/extensions/Char.kt @@ -3,4 +3,4 @@ package com.tangem.utils.extensions /** [REDACTED_AUTHOR] */ -fun Char.isNotWhitespace(): Boolean = !this.isWhitespace() +fun Char.isNotWhitespace(): Boolean = !this.isWhitespace() \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt index d43a1a9e98..116b7239c3 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt @@ -1,10 +1,6 @@ package com.tangem.feature.onboarding.data -import com.tangem.crypto.bip39.BIP39Wordlist -import com.tangem.crypto.bip39.DefaultMnemonic -import com.tangem.crypto.bip39.EntropyLength -import com.tangem.crypto.bip39.Mnemonic -import com.tangem.crypto.bip39.Wordlist +import com.tangem.crypto.bip39.* import com.tangem.datasource.asset.AssetReader /** @@ -33,4 +29,4 @@ internal class DefaultMnemonicRepository( companion object { private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" } -} +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt index 2860aa5b8a..d0cd349b55 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -58,11 +58,7 @@ internal class DefaultSeedPhraseInteractor constructor( } } - override suspend fun getSuggestions( - text: String, - hasSelection: Boolean, - cursorPosition: Int, - ): List { + override suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List { if (text.isEmpty() || cursorPosition == 0 || hasSelection) return emptyList() val word = partWordFinder.getLeadPartOfWord(text, cursorPosition) ?: return emptyList() diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt index be71e9203f..60a22f0fec 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt @@ -29,12 +29,12 @@ sealed class SeedPhraseError( message = message ?: this::class.java.simpleName, data = data, ) { - object InvalidWordCount : SeedPhraseError(0) - object InvalidEntropyLength : SeedPhraseError(1) - object InvalidWordsFile : SeedPhraseError(2) - object InvalidChecksum : SeedPhraseError(3) - object MnenmonicCreationFailed : SeedPhraseError(4) - object NormalizationFailed : SeedPhraseError(5) - object UnsupportedLanguage : SeedPhraseError(6) - data class InvalidWords(val words: Set) : SeedPhraseError(7) -} + object InvalidWordCount : SeedPhraseError(subCode = 0) + object InvalidEntropyLength : SeedPhraseError(subCode = 1) + object InvalidWordsFile : SeedPhraseError(subCode = 2) + object InvalidChecksum : SeedPhraseError(subCode = 3) + object MnenmonicCreationFailed : SeedPhraseError(subCode = 4) + object NormalizationFailed : SeedPhraseError(subCode = 5) + object UnsupportedLanguage : SeedPhraseError(subCode = 6) + data class InvalidWords(val words: Set) : SeedPhraseError(subCode = 7) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt index 083948ff1e..97ca9cd8d9 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/PartWordFinder.kt @@ -18,6 +18,7 @@ internal class PartWordFinder { val hadWhiteSpaceOnLeftOfCursor = charText[cursorPosition - 1].isWhitespace() val cursorInMiddleOfWord = charText.size > cursorPosition && charText[cursorPosition].isNotWhitespace() + @Suppress("ComplexCondition") if (cursorIsOutOfTextRange || cursorAtStartOfText || hadWhiteSpaceOnLeftOfCursor || cursorInMiddleOfWord) { return null } @@ -46,4 +47,4 @@ internal class PartWordFinder { return word.substring(leadPart.length, word.length) } -} +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt index a616d93578..419b277b9d 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/model/UiActions.kt @@ -45,9 +45,10 @@ data class ImportSeedPhraseUiAction( data class TextFieldUiAction( val onTextFieldChanged: (TextFieldValue) -> Unit, val onFocusChanged: (Boolean) -> Unit = {}, - ) + + enum class SeedPhraseField { Second, Seventh, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt index ecc135c5f2..fc583bccc7 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/AboutSeedPhraseScreen.kt @@ -4,14 +4,7 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -34,20 +27,17 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi [REDACTED_AUTHOR] */ @Composable -fun AboutSeedPhraseScreen( - state: AboutState, - modifier: Modifier = Modifier, -) { +fun AboutSeedPhraseScreen(state: AboutState, modifier: Modifier = Modifier) { Column( modifier = modifier.fillMaxSize(), ) { Box( - modifier = Modifier.weight(0.8f), + modifier = Modifier.weight(weight = 0.8f), ) { EditIconBlock() } Box( - modifier = Modifier.weight(1.2f), + modifier = Modifier.weight(weight = 1.2f), ) { Column { OnboardingDescriptionBlock { @@ -119,7 +109,6 @@ private fun EditIconBlock() { style = TangemTheme.typography.body1, ) } - } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt index 6287eaf5df..b67af3906f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/CheckSeedPhraseScreen.kt @@ -1,10 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.imePadding -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -22,11 +18,9 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi /** [REDACTED_AUTHOR] */ +@Suppress("ReusedModifierInstance") @Composable -fun CheckSeedPhraseScreen( - state: CheckSeedPhraseState, - modifier: Modifier = Modifier, -) { +fun CheckSeedPhraseScreen(state: CheckSeedPhraseState, modifier: Modifier = Modifier) { Column( modifier = modifier .fillMaxSize(), @@ -78,10 +72,7 @@ fun CheckSeedPhraseScreen( } @Composable -private fun CheckSeedPhraseBlock( - modifier: Modifier = Modifier, - state: CheckSeedPhraseState, -) { +private fun CheckSeedPhraseBlock(state: CheckSeedPhraseState, modifier: Modifier = Modifier) { Column(modifier) { OutlineTextField( value = state.tvSecondPhrase.textFieldValue, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt index d159f76094..1db145ce7f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/ImportSeedPhraseScreen.kt @@ -1,17 +1,7 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideIn -import androidx.compose.animation.slideOut -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.animation.* +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyRow import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text @@ -39,10 +29,7 @@ import com.tangem.feature.onboarding.presentation.wallet2.viewmodel.SeedPhraseEr [REDACTED_AUTHOR] */ @Composable -fun ImportSeedPhraseScreen( - state: ImportSeedPhraseState, - modifier: Modifier = Modifier, -) { +fun ImportSeedPhraseScreen(state: ImportSeedPhraseState, modifier: Modifier = Modifier) { Column( modifier = modifier.fillMaxSize(), ) { @@ -85,10 +72,7 @@ fun ImportSeedPhraseScreen( } @Composable -private fun PhraseBlock( - state: ImportSeedPhraseState, - modifier: Modifier = Modifier, -) { +private fun PhraseBlock(state: ImportSeedPhraseState, modifier: Modifier = Modifier) { // TODO: replace by TextReference val errorConverter = remember { SeedPhraseErrorConverter() } @@ -108,8 +92,9 @@ private fun PhraseBlock( style = SpanStyle(color = TangemTheme.colors.text.warning), ), colors = TangemTextFieldsDefault.defaultTextFieldColors, - ) + + Box( modifier = Modifier .fillMaxWidth() @@ -129,14 +114,12 @@ private fun PhraseBlock( } } +@Suppress("ReusedModifierInstance") @Composable -private fun SuggestionsBlock( - state: ImportSeedPhraseState, - modifier: Modifier = Modifier, -) { +private fun SuggestionsBlock(state: ImportSeedPhraseState, modifier: Modifier = Modifier) { AnimatedVisibility( - enter = fadeIn() + slideIn(initialOffset = { IntOffset(200, 0) }), - exit = slideOut(targetOffset = { IntOffset(-200, 0) }) + fadeOut(), + enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }), + exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(), visible = state.suggestionsList.isNotEmpty(), ) { LazyRow( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt index 2b57d09193..c99a233f33 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/IntroScreen.kt @@ -1,11 +1,7 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -28,20 +24,17 @@ import com.tangem.feature.onboarding.presentation.wallet2.ui.components.Onboardi [REDACTED_AUTHOR] */ @Composable -fun IntroScreen( - state: IntroState, - modifier: Modifier = Modifier, -) { +fun IntroScreen(state: IntroState, modifier: Modifier = Modifier) { Column( modifier = modifier.fillMaxSize(), ) { Box( - modifier = Modifier.weight(1.5f), + modifier = Modifier.weight(weight = 1.5f), ) { CardImageBlock(state.cardImageUrl) } Box( - modifier = Modifier.weight(1f), + modifier = Modifier.weight(weight = 1f), ) { OnboardingDescriptionBlock { Description( @@ -79,9 +72,7 @@ fun IntroScreen( } @Composable -private fun CardImageBlock( - cardImageUrl: String? = null, -) { +private fun CardImageBlock(cardImageUrl: String? = null) { Box(modifier = Modifier.fillMaxSize()) { val cardImageModifier = Modifier .fillMaxSize() @@ -101,7 +92,7 @@ private fun CardImageBlock( } @Composable -private fun CardPlaceHolder(modifier: Modifier) { +private fun CardPlaceHolder(modifier: Modifier = Modifier) { Image( modifier = modifier, painter = painterResource(id = R.drawable.card_placeholder_black), diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt index 75bfdc15e4..76a7e48923 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/YourSeedPhraseScreen.kt @@ -1,12 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material.Text @@ -29,15 +23,12 @@ import kotlinx.collections.immutable.ImmutableList [REDACTED_AUTHOR] */ @Composable -fun YourSeedPhraseScreen( - state: YourSeedPhraseState, - modifier: Modifier = Modifier, -) { +fun YourSeedPhraseScreen(state: YourSeedPhraseState, modifier: Modifier = Modifier) { Column( modifier = modifier.fillMaxSize(), ) { Box( - modifier = Modifier.weight(0.6f), + modifier = Modifier.weight(weight = 0.6f), ) { OnboardingDescriptionBlock( modifier = Modifier.align(Alignment.Center), @@ -49,7 +40,7 @@ fun YourSeedPhraseScreen( } } Box( - modifier = Modifier.weight(1f), + modifier = Modifier.weight(weight = 1f), ) { PhraseGreedBlock(state.mnemonicGridItems) } @@ -72,7 +63,7 @@ fun YourSeedPhraseScreen( @Composable private fun PhraseGreedBlock(mnemonicGridItems: ImmutableList) { LazyVerticalGrid( - columns = GridCells.Fixed(2), + columns = GridCells.Fixed(count = 2), ) { this.items( count = mnemonicGridItems.size, @@ -95,7 +86,6 @@ private fun PhraseGreedBlock(mnemonicGridItems: ImmutableList) color = TangemTheme.colors.text.primary1, ) } - } } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt index c72bbdff7b..27a76c7f38 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/components/OnboardingDescriptionBlock.kt @@ -19,10 +19,7 @@ import kotlinx.collections.immutable.ImmutableList * UI component witch provides single and carousel description for the onboarding process */ @Composable -fun OnboardingDescriptionBlock( - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { +fun OnboardingDescriptionBlock(modifier: Modifier = Modifier, content: @Composable () -> Unit) { Box( modifier = modifier .fillMaxWidth() @@ -33,10 +30,7 @@ fun OnboardingDescriptionBlock( } @Composable -fun Description( - @StringRes titleRes: Int, - @StringRes subTitleRes: Int, -) { +fun Description(@StringRes titleRes: Int, @StringRes subTitleRes: Int) { Column { DescriptionTitleText(text = stringResource(id = titleRes)) SpacerH12() @@ -44,13 +38,14 @@ fun Description( } } +@Suppress("UnusedPrivateMember") @Composable fun OnboardingCarouselDescriptionBlock( - modifier: Modifier = Modifier, descriptionsList: ImmutableList, + modifier: Modifier = Modifier, ) { OnboardingDescriptionBlock(modifier) { - //TODO: implement + // TODO: implement } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt index 5d2fe283b6..aad9659b9d 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/CheckSeedPhraseStateBuilder.kt @@ -65,10 +65,7 @@ class CheckSeedPhraseStateBuilder { ) } - fun updateCreateWalletButton( - uiState: OnboardingSeedPhraseState, - enabled: Boolean, - ): OnboardingSeedPhraseState { + fun updateCreateWalletButton(uiState: OnboardingSeedPhraseState, enabled: Boolean): OnboardingSeedPhraseState { return uiState.copy( checkSeedPhraseState = uiState.checkSeedPhraseState.copy( buttonCreateWallet = uiState.checkSeedPhraseState.buttonCreateWallet.copy( @@ -77,4 +74,4 @@ class CheckSeedPhraseStateBuilder { ), ) } -} +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt index 8bfa374b82..5ffb9b5c41 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/ImportSeedPhraseStateBuilder.kt @@ -22,10 +22,7 @@ class ImportSeedPhraseStateBuilder { ), ) - fun updateCreateWalletButton( - uiState: OnboardingSeedPhraseState, - enabled: Boolean, - ): OnboardingSeedPhraseState { + fun updateCreateWalletButton(uiState: OnboardingSeedPhraseState, enabled: Boolean): OnboardingSeedPhraseState { return uiState.copy( importSeedPhraseState = uiState.importSeedPhraseState.copy( buttonCreateWallet = uiState.importSeedPhraseState.buttonCreateWallet.copy( @@ -35,23 +32,19 @@ class ImportSeedPhraseStateBuilder { ) } - fun updateInvalidWords( - uiState: OnboardingSeedPhraseState, - invalidWords: Set, - ): OnboardingSeedPhraseState = uiState.copy( - importSeedPhraseState = uiState.importSeedPhraseState.copy( - invalidWords = invalidWords, - ), - ) + fun updateInvalidWords(uiState: OnboardingSeedPhraseState, invalidWords: Set): OnboardingSeedPhraseState = + uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + invalidWords = invalidWords, + ), + ) - fun updateSuggestions( - uiState: OnboardingSeedPhraseState, - suggestions: List, - ): OnboardingSeedPhraseState = uiState.copy( - importSeedPhraseState = uiState.importSeedPhraseState.copy( - suggestionsList = suggestions, - ), - ) + fun updateSuggestions(uiState: OnboardingSeedPhraseState, suggestions: List): OnboardingSeedPhraseState = + uiState.copy( + importSeedPhraseState = uiState.importSeedPhraseState.copy( + suggestionsList = suggestions, + ), + ) fun insertSuggestionWord( uiState: OnboardingSeedPhraseState, @@ -64,10 +57,7 @@ class ImportSeedPhraseStateBuilder { return updateTextField(uiState, newTextFieldValue) } - fun updateError( - uiState: OnboardingSeedPhraseState, - error: SeedPhraseError?, - ): OnboardingSeedPhraseState { + fun updateError(uiState: OnboardingSeedPhraseState, error: SeedPhraseError?): OnboardingSeedPhraseState { return uiState.copy( importSeedPhraseState = uiState.importSeedPhraseState.copy( error = error, diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt index 61be456d93..f240c101ce 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/ui/stateBuiders/StateBuilder.kt @@ -1,16 +1,6 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders -import com.tangem.feature.onboarding.presentation.wallet2.model.AboutState -import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState -import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.IntroState -import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep -import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState -import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions -import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseState +import com.tangem.feature.onboarding.presentation.wallet2.model.* import kotlinx.collections.immutable.ImmutableList /** @@ -89,16 +79,12 @@ class StateBuilder( ), ) - fun changeStep( - uiState: OnboardingSeedPhraseState, - step: OnboardingSeedPhraseStep, - ): OnboardingSeedPhraseState = uiState.copy( - step = step, - ) + fun changeStep(uiState: OnboardingSeedPhraseState, step: OnboardingSeedPhraseStep): OnboardingSeedPhraseState = + uiState.copy( + step = step, + ) - fun generateMnemonicComponents( - uiState: OnboardingSeedPhraseState, - ): OnboardingSeedPhraseState { + fun generateMnemonicComponents(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState { return uiState.copy( aboutState = uiState.aboutState.copy( buttonGenerateSeedPhrase = uiState.aboutState.buttonGenerateSeedPhrase.copy( diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt index ec566f8013..7d6f3a4685 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/InvalidWordsColorTransformation.kt @@ -76,4 +76,4 @@ class InvalidWordsColorTransformation( private const val WHITE_SPACE = " " private const val WORD_SEPARATOR = "_$@-*" } -} +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 09de62d798..4fbee7352f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -8,18 +8,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.domain.SeedPhraseInteractor -import com.tangem.feature.onboarding.presentation.wallet2.model.AboutUiAction -import com.tangem.feature.onboarding.presentation.wallet2.model.CheckSeedPhraseUiAction -import com.tangem.feature.onboarding.presentation.wallet2.model.ImportSeedPhraseUiAction -import com.tangem.feature.onboarding.presentation.wallet2.model.IntroUiAction -import com.tangem.feature.onboarding.presentation.wallet2.model.MnemonicGridItem -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseState -import com.tangem.feature.onboarding.presentation.wallet2.model.OnboardingSeedPhraseStep -import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField -import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldState -import com.tangem.feature.onboarding.presentation.wallet2.model.TextFieldUiAction -import com.tangem.feature.onboarding.presentation.wallet2.model.UiActions -import com.tangem.feature.onboarding.presentation.wallet2.model.YourSeedPhraseUiAction +import com.tangem.feature.onboarding.presentation.wallet2.model.* import com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders.StateBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.Debouncer @@ -27,11 +16,7 @@ import com.tangem.utils.extensions.isEven import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @@ -195,9 +180,11 @@ class SeedPhraseViewModel @Inject constructor( // endregion ImportSeedPhrase // region ButtonClickHandlers + @Suppress("EmptyFunctionBlock") private fun buttonCreateWalletClick() { } + @Suppress("EmptyFunctionBlock") private fun buttonCreateWalletWithSeedPhraseClick() { } @@ -214,7 +201,7 @@ class SeedPhraseViewModel @Inject constructor( private fun buttonGenerateSeedPhraseClick() { viewModelScope.launchSingle { updateUi { uiBuilder.generateMnemonicComponents(uiState) } - delay(300) + delay(DELAY_GENERATE_SEED_PHRASE) interactor.generateMnemonic() .onSuccess { mnemonic -> val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents) @@ -234,18 +221,20 @@ class SeedPhraseViewModel @Inject constructor( val mnemonicGridItems = mutableListOf() for (index in 0 until splitIndex) { - if (index <= leftColumn.size) mnemonicGridItems.add( - MnemonicGridItem( + if (index <= leftColumn.size) { + val item = MnemonicGridItem( index = index + 1, mnemonic = leftColumn[index], - ), - ) - if (index < rightColumn.size) mnemonicGridItems.add( - MnemonicGridItem( + ) + mnemonicGridItems.add(item) + } + if (index < rightColumn.size) { + val item = MnemonicGridItem( index = index + splitIndex + 1, mnemonic = rightColumn[index], - ), - ) + ) + mnemonicGridItems.add(item) + } } return mnemonicGridItems.toImmutableList() } @@ -324,5 +313,6 @@ class SeedPhraseViewModel @Inject constructor( companion object { private const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer" private const val MNEMONIC_DEBOUNCE_DELAY = 700L + private const val DELAY_GENERATE_SEED_PHRASE = 300L } } \ No newline at end of file