diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 95cd7e9498..41a397e283 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -313,7 +313,6 @@ Другой кошелек уже был создан на карте, которую вы пытаетесь добавить. Хотите сбросить его и использовать карту для бэкапа? Резервная копия Прочитать о seed-фразе - Запишите эти 12 слов в порядке, указанном ниже, и сохраните их в надежном месте. Запишите эти %d слова в порядке, указанном ниже, и сохраните их в надежном месте. diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index 841f0df3bb..e0b8c69381 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -174,7 +174,6 @@ 您要添加的卡上已經創建了另一個錢包。你想重置它並將卡用於新錢包嗎? 創建備份 閱讀更多關於助記詞的訊息 - 按照下面給出的順序寫下這 12 個單詞,並將它們存放在安全秘密的地方。 按照下面給出的順序寫下這 %d 個單詞,並將它們存放在安全秘密的地方。 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 597b184e40..11cf3d5316 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -313,7 +313,6 @@ Another wallet has already been created on the card you\'re trying to add. Do you want to reset it and use the card for a new wallet? Creating a backup Read more about seed phrase - Write these 12 words down in the order given below and store them in a safe and secret place. Write these %d words down in the order given below and store them in a safe and secret place. diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt index 44d622f9eb..712fc8cb52 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/segmentedbutton/SegmentedButton.kt @@ -47,11 +47,15 @@ inline fun SegmentedButtons( selectedColor: Color = TangemTheme.colors.background.action, dividerColor: Color = TangemTheme.colors.stroke.primary, showIndication: Boolean = true, + selectedItem: T? = null, crossinline buttonContent: @Composable (T) -> Unit, ) { if (config.isEmpty() || config.size == 1) return - var selected by remember { mutableIntStateOf(0) } + var selected by remember { + val selectedIndex = if (selectedItem == null) 0 else config.indexOf(selectedItem) + mutableIntStateOf(selectedIndex) + } Row( modifier = modifier 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 13ec4eb908..d835895565 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 @@ -125,6 +125,7 @@ data class TangemDimens internal constructor( val spacing56: Dp = 56.dp, val spacing60: Dp = 60.dp, val spacing70: Dp = 70.dp, + val spacing76: Dp = 76.dp, val spacing80: Dp = 80.dp, val spacing92: Dp = 92.dp, val spacing94: Dp = 94.dp, 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 71ac3393b7..09bc1f8964 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 @@ -4,6 +4,7 @@ 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.feature.onboarding.domain.models.MnemonicType /** [REDACTED_AUTHOR] @@ -20,5 +21,12 @@ class DefaultMnemonicRepository( return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) } + override fun generateMnemonic(mnemonicType: MnemonicType): Mnemonic { + return when (mnemonicType) { + MnemonicType.Mnemonic12 -> DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) + MnemonicType.Mnemonic24 -> DefaultMnemonic(EntropyLength.Bits256Length, bip39Wordlist) + } + } + override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) } \ 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 index b1b7ebfa2c..389ca4d949 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 @@ -2,6 +2,7 @@ package com.tangem.feature.onboarding.data import com.tangem.common.core.TangemSdkError import com.tangem.crypto.bip39.Mnemonic +import com.tangem.feature.onboarding.domain.models.MnemonicType /** [REDACTED_AUTHOR] @@ -13,6 +14,9 @@ interface MnemonicRepository { @Throws(TangemSdkError.MnemonicException::class) fun generateDefaultMnemonic(): Mnemonic + @Throws(TangemSdkError.MnemonicException::class) + fun generateMnemonic(mnemonicType: MnemonicType): Mnemonic + @Throws(TangemSdkError.MnemonicException::class) fun createMnemonic(mnemonicString: String): Mnemonic } \ 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 92ed7ae69c..a0ef98bf30 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.feature.onboarding.domain.models.MnemonicType import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField import com.tangem.utils.extensions.isNotWhitespace import kotlinx.collections.immutable.ImmutableList @@ -13,27 +14,23 @@ import kotlinx.collections.immutable.toPersistentList /** [REDACTED_AUTHOR] */ -internal class DefaultSeedPhraseInteractor constructor( +internal class DefaultSeedPhraseInteractor( private val repository: MnemonicRepository, ) : SeedPhraseInteractor { - private var currentMnemonic: Mnemonic? = null private val partWordFinder: PartWordFinder = PartWordFinder() override suspend fun generateMnemonic(): Result { return runCatching { - repository.generateDefaultMnemonic().apply { - currentMnemonic = this - } + repository.generateDefaultMnemonic() } } - override suspend fun getMnemonicComponents(): Result> { + override suspend fun generateMnemonics(mnemonicTypes: List): Result> { return runCatching { - val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { - currentMnemonic = this + mnemonicTypes.associateWith { + repository.generateMnemonic(it) } - mnemonic.mnemonicComponents } } 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 d9420a9def..37e0c182da 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,6 +1,7 @@ package com.tangem.feature.onboarding.domain import com.tangem.crypto.bip39.Mnemonic +import com.tangem.feature.onboarding.domain.models.MnemonicType import com.tangem.feature.onboarding.presentation.wallet2.model.SeedPhraseField import kotlinx.collections.immutable.ImmutableList @@ -9,7 +10,7 @@ import kotlinx.collections.immutable.ImmutableList */ interface SeedPhraseInteractor { suspend fun generateMnemonic(): Result - suspend fun getMnemonicComponents(): Result> + suspend fun generateMnemonics(mnemonicTypes: List): Result> suspend fun isWordMatch(mnemonicComponents: List?, field: SeedPhraseField, word: String): Boolean suspend fun validateMnemonicString(text: String): Result> suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): ImmutableList diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/models/MnemonicType.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/models/MnemonicType.kt new file mode 100644 index 0000000000..ea4a74f87f --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/models/MnemonicType.kt @@ -0,0 +1,5 @@ +package com.tangem.feature.onboarding.domain.models + +enum class MnemonicType { + Mnemonic12, Mnemonic24; +} \ 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 44bdac25c3..5591aa1013 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,7 +2,9 @@ package com.tangem.feature.onboarding.presentation.wallet2.model import androidx.compose.ui.text.input.TextFieldValue import com.tangem.feature.onboarding.domain.SeedPhraseError +import com.tangem.feature.onboarding.domain.models.MnemonicType import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf /** @@ -31,10 +33,42 @@ data class AboutState( ) data class YourSeedPhraseState( - val mnemonicGridItems: ImmutableList = persistentListOf(), + val segmentSeedState: SegmentSeedState, + val mnemonicGridItems: PersistentList = persistentListOf(), val buttonContinue: ButtonState, ) +data class SegmentSeedState( + val seedSegments: PersistentList, + val selectedSeedType: SegmentSeedType, + val onSelectType: (SegmentSeedType) -> Unit, +) + +enum class SegmentSeedType(val count: Int) { + SEED_12(count = 12), SEED_24(count = 24); + + fun toMnemonicType(): MnemonicType { + return when (this) { + SEED_12 -> MnemonicType.Mnemonic12 + SEED_24 -> MnemonicType.Mnemonic24 + } + } + + companion object { + fun fromMnemonicType(mnemonicType: MnemonicType): SegmentSeedType { + return when (mnemonicType) { + MnemonicType.Mnemonic12 -> SEED_12 + MnemonicType.Mnemonic24 -> SEED_24 + } + } + } +} + +data class MnemonicState( + val seedType: SegmentSeedType, + val mnemonicItems: PersistentList, +) + data class MnemonicGridItem( val index: Int, val mnemonic: String, 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 dec0e444b9..afb379bdca 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 @@ -28,6 +28,7 @@ data class AboutUiAction( data class YourSeedPhraseUiAction( val buttonContinueClick: () -> Unit, + val onSelectType: (SegmentSeedType) -> Unit, ) data class CheckSeedPhraseUiAction( 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 23e21b9c92..94f072a0ac 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 @@ -2,93 +2,162 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll 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.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.SpacerW32 +import com.tangem.core.ui.components.SpacerH16 +import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.onboarding.R -import com.tangem.feature.onboarding.presentation.wallet2.model.ButtonState -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.model.* +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionSubTitleText +import com.tangem.feature.onboarding.presentation.wallet2.ui.components.DescriptionTitleText 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 -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList /** [REDACTED_AUTHOR] */ @Composable fun YourSeedPhraseScreen(state: YourSeedPhraseState, modifier: Modifier = Modifier) { - Column( + Box( modifier = modifier.fillMaxSize(), ) { - Box( - modifier = Modifier.weight(weight = 0.6f), + Column( + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing92) + .verticalScroll(rememberScrollState()) + .fillMaxSize(), ) { - OnboardingDescriptionBlock( - modifier = Modifier.align(Alignment.Center), - ) { - Description( - titleRes = R.string.onboarding_seed_generate_title, - subTitleRes = R.string.onboarding_seed_generate_message, - ) - } - } - Box( - modifier = Modifier.weight(weight = 1f), - ) { - PhraseGreedBlock(state.mnemonicGridItems) - } + SegmentSeedBlock( + modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), + state = state.segmentSeedState, + ) - Box { - OnboardingActionBlock( - firstActionContent = { - PrimaryButton( - modifier = Modifier - .fillMaxWidth(), - text = stringResource(id = R.string.common_continue), - onClick = state.buttonContinue.onClick, - ) - }, + TitleBlock(state.segmentSeedState) + + SeedPhraseGridBlock( + modifier = Modifier + .padding(top = TangemTheme.dimens.spacing20) + .padding(horizontal = TangemTheme.dimens.size16), + mnemonicGridItems = state.mnemonicGridItems, + ) + } + OnboardingActionBlock( + modifier = Modifier.align(Alignment.BottomCenter), + firstActionContent = { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResource(id = R.string.common_continue), + onClick = state.buttonContinue.onClick, + ) + }, + ) + } +} + +@Composable +private fun TitleBlock(state: SegmentSeedState) { + Box( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.size36) + .padding(top = TangemTheme.dimens.spacing20) + .fillMaxWidth(), + ) { + Column { + DescriptionTitleText(text = stringResource(id = R.string.onboarding_seed_generate_title)) + SpacerH16() + DescriptionSubTitleText( + text = pluralStringResource( + id = R.plurals.onboarding_seed_generate_message_words_count, + count = state.selectedSeedType.count, + state.selectedSeedType.count, + ), ) } } } @Composable -private fun PhraseGreedBlock(mnemonicGridItems: ImmutableList) { - LazyVerticalGrid( - columns = GridCells.Fixed(count = 2), +private fun SegmentSeedBlock(state: SegmentSeedState, modifier: Modifier = Modifier) { + SegmentedButtons( + modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76), + config = state.seedSegments, + selectedItem = state.selectedSeedType, + onClick = { + state.onSelectType(it) + }, ) { - this.items( - 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 = "${item.index}.", - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, - ) - Text( - text = item.mnemonic, - style = TangemTheme.typography.button, - color = TangemTheme.colors.text.primary1, - ) + Text( + text = pluralStringResource( + id = R.plurals.onboarding_seed_generate_words_count, + count = it.count, + it.count, + ), + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + } +} + +@Composable +private fun SeedPhraseGridBlock(mnemonicGridItems: ImmutableList, modifier: Modifier = Modifier) { + VerticalGrid( + modifier = modifier, + items = mnemonicGridItems, + columns = 2, + ) { item -> + Row( + modifier = Modifier.padding(all = TangemTheme.dimens.size8), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + modifier = Modifier.width(TangemTheme.dimens.size40), + text = "${item.index}.", + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + ) + Text( + text = item.mnemonic, + style = TangemTheme.typography.button, + color = TangemTheme.colors.text.primary1, + ) + } + } +} + +@Composable +private inline fun VerticalGrid( + items: ImmutableList, + columns: Int, + modifier: Modifier = Modifier, + crossinline content: @Composable (T) -> Unit, +) { + val columnLength = items.size / columns + Row( + modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + for (index in 0 until columns) { + Column { + for (i in 0 until columnLength) { + val item = items[index * columnLength + i] + content(item) + } } } } @@ -100,7 +169,15 @@ private fun YourSeedPhraseScreenPreview_Light() { TangemTheme(isDark = false) { YourSeedPhraseScreen( state = YourSeedPhraseState( - mnemonicGridItems = (1..12).map { MnemonicGridItem(it, it.toString()) }.toImmutableList(), + segmentSeedState = SegmentSeedState( + seedSegments = persistentListOf( + SegmentSeedType.SEED_12, + SegmentSeedType.SEED_24, + ), + selectedSeedType = SegmentSeedType.SEED_24, + onSelectType = {}, + ), + mnemonicGridItems = (1..24).map { MnemonicGridItem(it, it.toString()) }.toPersistentList(), buttonContinue = ButtonState( enabled = true, isClickable = true, 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 b7706cb69e..986bcac496 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 @@ -14,13 +14,14 @@ import com.tangem.core.ui.res.TangemTheme */ @Composable fun OnboardingActionBlock( + modifier: Modifier = Modifier, firstActionContent: @Composable (() -> Unit)? = null, secondActionContent: @Composable (() -> Unit)? = null, ) { if (firstActionContent == null && secondActionContent == null) return Box( - modifier = Modifier + modifier = modifier .padding(horizontal = TangemTheme.dimens.size16) .padding( top = TangemTheme.dimens.size8, 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 4134269aaa..5dc8a2a1c0 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 @@ -11,10 +11,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp -import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.onboarding.presentation.wallet2.model.DescriptionResource -import kotlinx.collections.immutable.ImmutableList /** * UI component witch provides single and carousel description for the onboarding process @@ -24,7 +22,7 @@ fun OnboardingDescriptionBlock(modifier: Modifier = Modifier, content: @Composab Box( modifier = modifier .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.size24), + .padding(horizontal = TangemTheme.dimens.size36), ) { content() } @@ -34,22 +32,11 @@ fun OnboardingDescriptionBlock(modifier: Modifier = Modifier, content: @Composab fun Description(@StringRes titleRes: Int, @StringRes subTitleRes: Int) { Column { DescriptionTitleText(text = stringResource(id = titleRes)) - SpacerH12() + SpacerH16() DescriptionSubTitleText(text = stringResource(id = subTitleRes)) } } -@Suppress("UnusedPrivateMember") -@Composable -fun OnboardingCarouselDescriptionBlock( - descriptionsList: ImmutableList, - modifier: Modifier = Modifier, -) { - OnboardingDescriptionBlock(modifier) { - // TODO: implement - } -} - @Composable fun DescriptionTitleText(text: String) { Text( 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 8683c0c3c0..749d4be6a0 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,7 +1,8 @@ package com.tangem.feature.onboarding.presentation.wallet2.ui.stateBuiders import com.tangem.feature.onboarding.presentation.wallet2.model.* -import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList +import kotlinx.collections.immutable.persistentListOf /** [REDACTED_AUTHOR] @@ -13,32 +14,34 @@ class StateBuilder( val checkSeedPhrase: CheckSeedPhraseStateBuilder = CheckSeedPhraseStateBuilder() val importSeedPhrase: ImportSeedPhraseStateBuilder = ImportSeedPhraseStateBuilder() - fun init(): OnboardingSeedPhraseState = OnboardingSeedPhraseState( - introState = IntroState( + fun init(selectedSeedType: SegmentSeedType): OnboardingSeedPhraseState = OnboardingSeedPhraseState( + introState = getInitialIntroState(), + aboutState = getInitialAboutState(), + yourSeedPhraseState = getInitialYourSeedPhraseState(selectedSeedType), + checkSeedPhraseState = getInitialCheckSeedPhraseState(), + importSeedPhraseState = getInitialImportSeedPhraseState(), + menuButtonChat = ButtonState( + onClick = uiActions.menuChatClick, + ), + onBackClick = uiActions.menuNavigateBackClick, + ) + + private fun getInitialImportSeedPhraseState(): ImportSeedPhraseState { + return ImportSeedPhraseState( + tvSeedPhrase = TextFieldState( + onTextFieldValueChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onTextFieldChanged, + onFocusChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onFocusChanged, + ), + onSuggestedPhraseClick = uiActions.importSeedPhraseActions.suggestedPhraseClick, buttonCreateWallet = ButtonState( - onClick = uiActions.introActions.buttonCreateWalletClick, + enabled = false, + onClick = uiActions.importSeedPhraseActions.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( + ) + } + + private fun getInitialCheckSeedPhraseState(): CheckSeedPhraseState { + return CheckSeedPhraseState( tvSecondPhrase = TextFieldState( label = "2", isFocused = true, @@ -61,23 +64,49 @@ class StateBuilder( enabled = false, onClick = uiActions.checkSeedPhraseActions.buttonCreateWalletClick, ), - ), - importSeedPhraseState = ImportSeedPhraseState( - tvSeedPhrase = TextFieldState( - onTextFieldValueChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onTextFieldChanged, - onFocusChanged = uiActions.importSeedPhraseActions.phraseTextFieldAction.onFocusChanged, + ) + } + + private fun getInitialYourSeedPhraseState(selectedSeedType: SegmentSeedType): YourSeedPhraseState { + return YourSeedPhraseState( + segmentSeedState = SegmentSeedState( + seedSegments = persistentListOf( + SegmentSeedType.SEED_12, + SegmentSeedType.SEED_24, + ), + selectedSeedType = selectedSeedType, + onSelectType = uiActions.yourSeedPhraseActions.onSelectType, ), - onSuggestedPhraseClick = uiActions.importSeedPhraseActions.suggestedPhraseClick, + buttonContinue = ButtonState( + onClick = uiActions.yourSeedPhraseActions.buttonContinueClick, + ), + ) + } + + private fun getInitialAboutState(): AboutState { + return AboutState( + buttonReadMoreAboutSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonReadMoreAboutSeedPhraseClick, + ), + buttonGenerateSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonGenerateSeedPhraseClick, + ), + buttonImportSeedPhrase = ButtonState( + onClick = uiActions.aboutActions.buttonImportSeedPhraseClick, + ), + ) + } + + private fun getInitialIntroState(): IntroState { + return IntroState( buttonCreateWallet = ButtonState( - enabled = false, - onClick = uiActions.importSeedPhraseActions.buttonCreateWalletClick, + onClick = uiActions.introActions.buttonCreateWalletClick, ), - ), - menuButtonChat = ButtonState( - onClick = uiActions.menuChatClick, - ), - onBackClick = uiActions.menuNavigateBackClick, - ) + buttonOtherOptions = ButtonState( + onClick = uiActions.introActions.buttonOtherOptionsClick, + ), + ) + } fun setCardArtwork(uiState: OnboardingSeedPhraseState, cardArtwork: String): OnboardingSeedPhraseState = uiState.copy( @@ -101,7 +130,7 @@ class StateBuilder( fun mnemonicGenerated( uiState: OnboardingSeedPhraseState, - mnemonicGridItems: ImmutableList, + mnemonicGridItems: PersistentList, ): OnboardingSeedPhraseState { return updateMnemonicComponents(uiState, mnemonicGridItems) .copy( @@ -116,9 +145,45 @@ class StateBuilder( ) } - fun updateMnemonicComponents( + fun selectSeedType( uiState: OnboardingSeedPhraseState, - mnemonicGridItems: ImmutableList, + mnemonicGridItems: PersistentList, + selectedSeedType: SegmentSeedType, + ): OnboardingSeedPhraseState = uiState.copy( + yourSeedPhraseState = uiState.yourSeedPhraseState.copy( + mnemonicGridItems = mnemonicGridItems, + segmentSeedState = uiState.yourSeedPhraseState.segmentSeedState.copy( + selectedSeedType = selectedSeedType, + ), + ), + ) + + fun clearCheckSeedPhraseState(uiState: OnboardingSeedPhraseState): OnboardingSeedPhraseState = uiState.copy( + checkSeedPhraseState = uiState.checkSeedPhraseState.copy( + 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, + ), + ), + ) + + private fun updateMnemonicComponents( + uiState: OnboardingSeedPhraseState, + mnemonicGridItems: PersistentList, ): OnboardingSeedPhraseState = uiState.copy( yourSeedPhraseState = uiState.yourSeedPhraseState.copy( mnemonicGridItems = mnemonicGridItems, 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 719abb7761..ed20db4319 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 @@ -13,18 +13,19 @@ import com.tangem.data.common.locale.LocaleProvider import com.tangem.feature.onboarding.data.model.CreateWalletResponse import com.tangem.feature.onboarding.domain.SeedPhraseError import com.tangem.feature.onboarding.domain.SeedPhraseInteractor +import com.tangem.feature.onboarding.domain.models.MnemonicType import com.tangem.feature.onboarding.presentation.wallet2.analytics.CreateWalletEvents import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseEvents import com.tangem.feature.onboarding.presentation.wallet2.analytics.SeedPhraseSource import com.tangem.feature.onboarding.presentation.wallet2.model.* +import com.tangem.feature.onboarding.presentation.wallet2.model.SegmentSeedType.Companion.fromMnemonicType 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 import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import javax.inject.Inject @@ -43,7 +44,8 @@ class SeedPhraseViewModel @Inject constructor( private var uiBuilder = StateBuilder(uiActions = createUiActions()) - var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init()) + private var selectedMnemonicType = MnemonicType.Mnemonic12 + var uiState: OnboardingSeedPhraseState by mutableStateOf(uiBuilder.init(fromMnemonicType(selectedMnemonicType))) private set private lateinit var router: SeedPhraseRouter @@ -70,7 +72,7 @@ class SeedPhraseViewModel @Inject constructor( private val textFieldsDebouncers = mutableMapOf() - private var generatedMnemonicComponents: List? = null + private var generatedMnemonicComponents: Map> = emptyMap() private var importedMnemonicComponents: List? = null override fun onCleared() { @@ -135,10 +137,13 @@ class SeedPhraseViewModel @Inject constructor( ), yourSeedPhraseActions = YourSeedPhraseUiAction( buttonContinueClick = ::buttonContinueClick, + onSelectType = ::onSelectType, ), checkSeedPhraseActions = CheckSeedPhraseUiAction( buttonCreateWalletClick = { - buttonImportWalletClick(generatedMnemonicComponents, SeedPhraseSource.GENERATED) + val mnemonicComponents = + generatedMnemonicComponents[selectedMnemonicType] ?: return@CheckSeedPhraseUiAction + buttonImportWalletClick(mnemonicComponents, SeedPhraseSource.GENERATED) }, secondTextFieldAction = TextFieldUiAction( onTextFieldChanged = { value -> onTextFieldChanged(SeedPhraseField.Second, value) }, @@ -179,11 +184,12 @@ class SeedPhraseViewModel @Inject constructor( } createOrGetDebouncer(field.name).debounce(viewModelScope, context = dispatchers.io) { - val hasError = !interactor.isWordMatch(generatedMnemonicComponents, field, textFieldValue.text) + val mnemonicComponents = generatedMnemonicComponents[selectedMnemonicType] ?: return@debounce + val hasError = !interactor.isWordMatch(mnemonicComponents, field, textFieldValue.text) if (fieldState.isError != hasError) { updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) } } - val isCreateWalletButtonEnabled = SeedPhraseField.values() + val isCreateWalletButtonEnabled = SeedPhraseField.entries .map { field -> field.getState(uiState) } .all { fieldState -> fieldState.textFieldValue.text.isNotEmpty() && !fieldState.isError } @@ -324,50 +330,35 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonGenerateSeedPhraseClick() { + val mnemonicTypes = listOf(MnemonicType.Mnemonic12, MnemonicType.Mnemonic24) analyticsEventHandler.send(SeedPhraseEvents.ButtonGenerateSeedPhrase) launchSingle { updateUi { uiBuilder.generateMnemonicComponents(uiState) } delay(DELAY_GENERATE_SEED_PHRASE) - interactor.generateMnemonic() - .onSuccess { mnemonic -> - generatedMnemonicComponents = mnemonic.mnemonicComponents - val mnemonicGridItems = generateMnemonicGridList(mnemonic.mnemonicComponents) + interactor.generateMnemonics(mnemonicTypes) + .onSuccess { mnemonics -> + generatedMnemonicComponents = mnemonics.mapValues { it.value.mnemonicComponents } + val defaultMnemonic = mnemonics[selectedMnemonicType]?.mnemonicComponents ?: return@launchSingle + val mnemonicGridItems = toMnemonicGridList(defaultMnemonic) updateUi { router.openScreen(SeedPhraseScreen.YourSeedPhrase) - uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toImmutableList()) + uiBuilder.mnemonicGenerated(uiState, mnemonicGridItems.toPersistentList()) } } .onFailure { - generatedMnemonicComponents = null + generatedMnemonicComponents = emptyMap() // TODO: show error } } } - private 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) { - val item = MnemonicGridItem( - index = index + 1, - mnemonic = leftColumn[index], - ) - mnemonicGridItems.add(item) - } - if (index < rightColumn.size) { - val item = MnemonicGridItem( - index = index + splitIndex + 1, - mnemonic = rightColumn[index], - ) - mnemonicGridItems.add(item) - } - } - return mnemonicGridItems.toImmutableList() + private fun toMnemonicGridList(mnemonicComponents: List): PersistentList { + return mnemonicComponents.mapIndexed { index, word -> + MnemonicGridItem( + index = index.inc(), + mnemonic = word, + ) + }.toPersistentList() } private fun buttonImportSeedPhraseClick() { @@ -376,7 +367,26 @@ class SeedPhraseViewModel @Inject constructor( } private fun buttonContinueClick() { - router.openScreen(SeedPhraseScreen.CheckSeedPhrase) + launchSingle { + updateUi { + uiBuilder.clearCheckSeedPhraseState(uiState) + } + router.openScreen(SeedPhraseScreen.CheckSeedPhrase) + } + } + + private fun onSelectType(selectedSeedType: SegmentSeedType) { + launchSingle { + selectedMnemonicType = selectedSeedType.toMnemonicType() + val selectedMnemonic = generatedMnemonicComponents[selectedMnemonicType] ?: return@launchSingle + updateUi { + uiBuilder.selectSeedType( + uiState = uiState, + mnemonicGridItems = toMnemonicGridList(selectedMnemonic), + selectedSeedType = selectedSeedType, + ) + } + } } private fun buttonSuggestedPhraseClick(suggestionIndex: Int) { @@ -433,9 +443,9 @@ class SeedPhraseViewModel @Inject constructor( } // endregion Utils - companion object { - private const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer" - private const val MNEMONIC_DEBOUNCE_DELAY = 700L - private const val DELAY_GENERATE_SEED_PHRASE = 300L + private companion object { + const val MNEMONIC_DEBOUNCER = "MnemonicDebouncer" + const val MNEMONIC_DEBOUNCE_DELAY = 700L + const val DELAY_GENERATE_SEED_PHRASE = 300L } } \ No newline at end of file