Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-11 15:58:35 +03:00
parent 8fbb94cd99
commit bba40d0d7a
10 changed files with 221 additions and 282 deletions

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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()

View file

@ -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, lets check",
subTitle = "To check wether youve 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
}

View file

@ -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) },
)
}
}
}
}

View file

@ -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",
)
}
}

View file

@ -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),
) {

View file

@ -21,6 +21,7 @@ fun OnboardingActionBlock(
Box(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.size16)
.padding(
top = TangemTheme.dimens.size8,
bottom = TangemTheme.dimens.size32,

View file

@ -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,

View file

@ -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
}