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 }