Updated on 2026-08-14
This commit is contained in:
parent
8fbb94cd99
commit
bba40d0d7a
10 changed files with 221 additions and 282 deletions
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<String> = listOf(),
|
||||
val mnemonicComponents: ImmutableList<String> = persistentListOf(),
|
||||
val buttonContinue: ButtonState,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>) {
|
||||
private fun PhraseGreedBlock(phraseList: ImmutableList<String>) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(2),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ fun OnboardingActionBlock(
|
|||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.size16)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.size8,
|
||||
bottom = TangemTheme.dimens.size32,
|
||||
|
|
|
|||
|
|
@ -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<OnboardingDescription> = 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<OnboardingDescription>) {
|
||||
//TODO: implement
|
||||
fun OnboardingCarouselDescriptionBlock(
|
||||
modifier: Modifier = Modifier,
|
||||
descriptionsList: ImmutableList<DescriptionResource>,
|
||||
) {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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<SeedPhraseField, Debouncer>()
|
||||
|
||||
init {
|
||||
prepareCheckYourSeedPhraseTest()
|
||||
}
|
||||
|
||||
// TODO: delete
|
||||
private fun prepareCheckYourSeedPhraseTest() {
|
||||
uiState.copy(step = OnboardingSeedPhraseStep.AboutSeedPhrase)
|
||||
viewModelScope.launchIo {
|
||||
delay(300)
|
||||
buttonGenerateSeedPhraseClick()
|
||||
}
|
||||
}
|
||||
private val textFieldsDebouncers = mutableMapOf<String, Debouncer>()
|
||||
|
||||
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 <T> withMainContext(block: suspend CoroutineScope.() -> T): T {
|
||||
return withContext(dispatchers.main, block)
|
||||
}
|
||||
|
||||
private suspend fun <T> withIoContext(block: suspend CoroutineScope.() -> T): T {
|
||||
return withContext(dispatchers.io, block)
|
||||
private suspend fun <T> withSingleContext(block: suspend CoroutineScope.() -> T): T {
|
||||
return withContext(dispatchers.single, block)
|
||||
}
|
||||
// endregion Utils
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue