Updated on 2026-08-14
This commit is contained in:
parent
4b3d59fa0f
commit
e6c8647f03
14 changed files with 1132 additions and 27 deletions
|
|
@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.arkivanov.decompose.FaultyDecomposeApi
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||||
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.fade
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||||
|
|
@ -22,6 +24,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChild
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.MultiWalletBackupComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.chooseoption.Wallet1ChooseOptionComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.MultiWalletCreateWalletComponent
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.MultiWalletSeedPhraseComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.Step.*
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState.Step.*
|
||||||
|
|
@ -30,6 +33,7 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.WalletArtworksState
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor(
|
internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor(
|
||||||
|
|
@ -44,7 +48,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
when (model.state.value.currentStep) {
|
when (model.state.value.currentStep) {
|
||||||
CreateWallet -> WalletArtworksState.Folded
|
CreateWallet -> WalletArtworksState.Folded
|
||||||
ChooseBackupOption -> WalletArtworksState.Fan
|
ChooseBackupOption -> WalletArtworksState.Fan
|
||||||
SeedPhrase -> WalletArtworksState.Hidden
|
SeedPhrase -> WalletArtworksState.Folded
|
||||||
AddBackupDevice -> WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First)
|
AddBackupDevice -> WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First)
|
||||||
FinishBackup -> WalletArtworksState.Stack()
|
FinishBackup -> WalletArtworksState.Stack()
|
||||||
Done -> error("Done state should not be used as starting state")
|
Done -> error("Done state should not be used as starting state")
|
||||||
|
|
@ -95,7 +99,15 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
context = childContext,
|
context = childContext,
|
||||||
onNextStep = ::handleNavigationEvent,
|
onNextStep = ::handleNavigationEvent,
|
||||||
)
|
)
|
||||||
SeedPhrase -> TODO()
|
SeedPhrase -> MultiWalletSeedPhraseComponent(
|
||||||
|
context = childContext,
|
||||||
|
params = MultiWalletChildParams(
|
||||||
|
multiWalletState = model.state,
|
||||||
|
parentParams = params,
|
||||||
|
),
|
||||||
|
onNextStep = ::handleNavigationEvent,
|
||||||
|
backButtonClickFlow = MutableSharedFlow(),
|
||||||
|
)
|
||||||
AddBackupDevice -> MultiWalletBackupComponent(
|
AddBackupDevice -> MultiWalletBackupComponent(
|
||||||
context = childContext,
|
context = childContext,
|
||||||
params = MultiWalletChildParams(
|
params = MultiWalletChildParams(
|
||||||
|
|
@ -115,7 +127,6 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
artworksState.value = WalletArtworksState.Fan
|
artworksState.value = WalletArtworksState.Fan
|
||||||
}
|
}
|
||||||
SeedPhrase -> {
|
SeedPhrase -> {
|
||||||
artworksState.value = WalletArtworksState.Hidden
|
|
||||||
}
|
}
|
||||||
AddBackupDevice -> {
|
AddBackupDevice -> {
|
||||||
artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First)
|
artworksState.value = WalletArtworksState.Unfold(WalletArtworksState.Unfold.Step.First)
|
||||||
|
|
@ -144,6 +155,7 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(FaultyDecomposeApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val stackState by childStack.subscribeAsState()
|
val stackState by childStack.subscribeAsState()
|
||||||
|
|
@ -154,10 +166,19 @@ internal class DefaultOnboardingMultiWalletComponent @AssistedInject constructor
|
||||||
state = state,
|
state = state,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
artworksState = artworksState,
|
artworksState = artworksState,
|
||||||
|
isSeedPhraseState = stackState.active.configuration == SeedPhrase,
|
||||||
childContent = { mdfr ->
|
childContent = { mdfr ->
|
||||||
Children(
|
Children(
|
||||||
stack = stackState,
|
stack = stackState,
|
||||||
animation = stackAnimation(slide()),
|
animation = stackAnimation(
|
||||||
|
selector = { one, two, direction ->
|
||||||
|
if (one.configuration == SeedPhrase || two.configuration == SeedPhrase) {
|
||||||
|
fade()
|
||||||
|
} else {
|
||||||
|
slide()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
it.instance.Content(mdfr)
|
it.instance.Content(mdfr)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildComponent
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.MultiWalletChildParams
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.MultiWalletSeedPhrase
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletState
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Suppress("UnusedPrivateMember")
|
||||||
|
class MultiWalletSeedPhraseComponent(
|
||||||
|
context: AppComponentContext,
|
||||||
|
params: MultiWalletChildParams,
|
||||||
|
onNextStep: (OnboardingMultiWalletState.Step) -> Unit,
|
||||||
|
backButtonClickFlow: SharedFlow<Unit>,
|
||||||
|
) : AppComponentContext by context, MultiWalletChildComponent {
|
||||||
|
|
||||||
|
private val model: MultiWalletSeedPhraseModel = getOrCreateModel()
|
||||||
|
|
||||||
|
init {
|
||||||
|
componentScope.launch {
|
||||||
|
backButtonClickFlow.collect {
|
||||||
|
model.onBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content(modifier: Modifier) {
|
||||||
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
MultiWalletSeedPhrase(
|
||||||
|
modifier = modifier,
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
|
||||||
|
|
||||||
|
import com.tangem.core.decompose.di.ComponentScoped
|
||||||
|
import com.tangem.core.decompose.model.Model
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ComponentScoped
|
||||||
|
class MultiWalletSeedPhraseModel @Inject constructor(
|
||||||
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
) : Model() {
|
||||||
|
|
||||||
|
private val state = MutableStateFlow(SeedPhraseState())
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow<MultiWalletSeedPhraseUM>(
|
||||||
|
MultiWalletSeedPhraseUM.Start(
|
||||||
|
onImportSeedPhraseClicked = {},
|
||||||
|
onGenerateSeedPhraseClicked = {
|
||||||
|
openGeneratedSeedPhrase()
|
||||||
|
},
|
||||||
|
onLearnMoreClicked = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val uiState = _uiState.asStateFlow()
|
||||||
|
|
||||||
|
fun onBack() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openGeneratedSeedPhrase() {
|
||||||
|
// TODO state.value.generatedWords
|
||||||
|
val words = listOf("word, word")
|
||||||
|
|
||||||
|
_uiState.value = MultiWalletSeedPhraseUM.GenerateSeedPhrase(
|
||||||
|
words24OptionSelected = false,
|
||||||
|
words = words.mapIndexed { index, s ->
|
||||||
|
MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem(index, s)
|
||||||
|
}.toImmutableList(),
|
||||||
|
onWords24OptionSwitch = {
|
||||||
|
_uiState.update {
|
||||||
|
if (it !is MultiWalletSeedPhraseUM.GenerateSeedPhrase) return@update it
|
||||||
|
it.copy(words24OptionSelected = it.words24OptionSelected.not())
|
||||||
|
}
|
||||||
|
state.update { it.copy(words24Option = it.words24Option.not()) }
|
||||||
|
},
|
||||||
|
onContinueClick = {
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model
|
||||||
|
|
||||||
|
data class SeedPhraseState(
|
||||||
|
val generatedWords: List<String>? = null,
|
||||||
|
val words24Option: Boolean = false,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedContentTransitionScope
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiWalletSeedPhrase(state: MultiWalletSeedPhraseUM, modifier: Modifier = Modifier) {
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = modifier.navigationBarsPadding(),
|
||||||
|
targetState = state,
|
||||||
|
transitionSpec = {
|
||||||
|
val direction = if (initialState.order < targetState.order) {
|
||||||
|
AnimatedContentTransitionScope.SlideDirection.Start
|
||||||
|
} else {
|
||||||
|
AnimatedContentTransitionScope.SlideDirection.End
|
||||||
|
}
|
||||||
|
|
||||||
|
slideIntoContainer(towards = direction, animationSpec = tween())
|
||||||
|
.togetherWith(slideOutOfContainer(towards = direction, animationSpec = tween()))
|
||||||
|
},
|
||||||
|
contentKey = { st -> st::class.java },
|
||||||
|
label = "animatedContent",
|
||||||
|
) { st ->
|
||||||
|
when (st) {
|
||||||
|
is MultiWalletSeedPhraseUM.Start -> MultiWalletSeedPhraseStart(state = st)
|
||||||
|
is MultiWalletSeedPhraseUM.GenerateSeedPhrase -> MultiWalletSeedPhraseWords(state = st)
|
||||||
|
is MultiWalletSeedPhraseUM.GeneratedWordsCheck -> MultiWalletSeedPhraseWordsCheck(state = st)
|
||||||
|
is MultiWalletSeedPhraseUM.Import -> MultiWalletSeedPhraseImport(state = st)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,201 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.*
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.*
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils.InvalidWordsColorTransformation
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiWalletSeedPhraseImport(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
|
||||||
|
Box(modifier.fillMaxSize()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_import_message),
|
||||||
|
style = TangemTheme.typography.body1,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 36.dp, end = 36.dp, top = 24.dp, bottom = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
PhraseBlock(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlineTextFieldWithIcon(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
value = state.passPhrase,
|
||||||
|
onValueChange = state.passPhraseChange,
|
||||||
|
iconResId = R.drawable.ic_information_24,
|
||||||
|
iconColor = TangemTheme.colors.icon.informative,
|
||||||
|
label = stringResource(id = R.string.common_passphrase),
|
||||||
|
placeholder = stringResource(id = R.string.send_optional_field),
|
||||||
|
onIconClick = state.onPassphraseInfoClick,
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(Modifier.weight(1f)) {
|
||||||
|
PrimaryButtonIconEnd(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.common_import),
|
||||||
|
iconResId = R.drawable.ic_tangem_24,
|
||||||
|
enabled = state.createWalletEnabled,
|
||||||
|
showProgress = state.createWalletProgress,
|
||||||
|
onClick = state.createWalletClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val keyboard by keyboardAsState()
|
||||||
|
if (keyboard is Keyboard.Opened) {
|
||||||
|
SuggestionsBlock(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.imePadding(),
|
||||||
|
suggestionsList = state.suggestionsList,
|
||||||
|
onClick = { index -> state.onSuggestionClick(state.suggestionsList[index]) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PassphraseInfoBottomSheet(state.infoBottomSheetConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PhraseBlock(state: MultiWalletSeedPhraseUM.Import, modifier: Modifier = Modifier) {
|
||||||
|
val warningColor = TangemTheme.colors.text.warning
|
||||||
|
val invalidWordsColorTransformation = remember(state.invalidWords, warningColor) {
|
||||||
|
InvalidWordsColorTransformation(
|
||||||
|
wordsToBrush = state.invalidWords,
|
||||||
|
style = SpanStyle(color = warningColor),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(TangemTheme.dimens.size142),
|
||||||
|
value = state.passPhrase,
|
||||||
|
onValueChange = state.passPhraseChange,
|
||||||
|
textStyle = TangemTheme.typography.body1,
|
||||||
|
singleLine = false,
|
||||||
|
visualTransformation = invalidWordsColorTransformation,
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.height(TangemTheme.dimens.size32),
|
||||||
|
) {
|
||||||
|
if (state.wordsErrorText != null) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
text = state.wordsErrorText.resolveReference(),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.warning,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SuggestionsBlock(
|
||||||
|
suggestionsList: ImmutableList<String>,
|
||||||
|
onClick: (Int) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
modifier = modifier,
|
||||||
|
enter = fadeIn() + slideIn(initialOffset = { IntOffset(x = 200, y = 0) }),
|
||||||
|
exit = slideOut(targetOffset = { IntOffset(x = -200, y = 0) }) + fadeOut(),
|
||||||
|
visible = suggestionsList.isNotEmpty(),
|
||||||
|
) {
|
||||||
|
LazyRow(
|
||||||
|
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.size16),
|
||||||
|
) {
|
||||||
|
items(suggestionsList.size) { index ->
|
||||||
|
SuggestionButton(
|
||||||
|
modifier = Modifier.rowPadding(
|
||||||
|
index = index,
|
||||||
|
rowSize = suggestionsList.size,
|
||||||
|
outSide = TangemTheme.dimens.size0,
|
||||||
|
inSide = TangemTheme.dimens.size4,
|
||||||
|
),
|
||||||
|
text = suggestionsList[index],
|
||||||
|
onClick = { onClick(index) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SuggestionButton(text: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||||
|
Box(modifier = modifier.clickable(onClick = onClick)) {
|
||||||
|
Notifier(
|
||||||
|
text = text,
|
||||||
|
backgroundColor = TangemTheme.colors.icon.primary1,
|
||||||
|
textColor = TangemTheme.colors.text.primary2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Modifier.rowPadding(index: Int, rowSize: Int, outSide: Dp, inSide: Dp): Modifier = when (index) {
|
||||||
|
0 -> this.padding(start = outSide, end = inSide)
|
||||||
|
rowSize - 1 -> this.padding(start = inSide, end = outSide)
|
||||||
|
else -> this.padding(horizontal = inSide)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview(
|
||||||
|
alwaysShowBottomSheets = false,
|
||||||
|
) {
|
||||||
|
MultiWalletSeedPhraseImport(
|
||||||
|
state = MultiWalletSeedPhraseUM.Import(
|
||||||
|
wordsErrorText = stringReference("Error"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.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.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.style.LineBreak
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.SecondaryButton
|
||||||
|
import com.tangem.core.ui.components.SpacerH32
|
||||||
|
import com.tangem.core.ui.components.SpacerW8
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun MultiWalletSeedPhraseStart(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier.padding(top = 48.dp),
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(28.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(56.dp),
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_onboarding_text_edit_56),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = TangemTheme.colors.icon.warning.copy(alpha = 0.12f),
|
||||||
|
shape = RoundedCornerShape(TangemTheme.dimens.radius8),
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BodyContent(state)
|
||||||
|
}
|
||||||
|
SpacerH32()
|
||||||
|
Buttons(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BodyContent(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_intro_title),
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_intro_message),
|
||||||
|
style = TangemTheme.typography.body1
|
||||||
|
.copy(lineBreak = LineBreak.Paragraph),
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 32.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
ReadMoreBlock(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReadMoreBlock(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp),
|
||||||
|
) {
|
||||||
|
val shape = TangemTheme.shapes.roundedCornersLarge
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.clip(shape)
|
||||||
|
.border(
|
||||||
|
width = 1.dp,
|
||||||
|
color = TangemTheme.colors.stroke.primary,
|
||||||
|
shape = shape,
|
||||||
|
)
|
||||||
|
.clickable(onClick = state.onLearnMoreClicked)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = R.drawable.ic_arrow_top_right_24),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = TangemTheme.colors.icon.primary1,
|
||||||
|
)
|
||||||
|
SpacerW8()
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.CenterVertically),
|
||||||
|
style = TangemTheme.typography.button,
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_button_read_more),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Buttons(state: MultiWalletSeedPhraseUM.Start, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||||
|
.imePadding(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
SecondaryButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_intro_button_generate),
|
||||||
|
onClick = state.onGenerateSeedPhraseClicked,
|
||||||
|
)
|
||||||
|
SecondaryButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_intro_button_import),
|
||||||
|
onClick = state.onImportSeedPhraseClicked,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
MultiWalletSeedPhraseStart(
|
||||||
|
state = MultiWalletSeedPhraseUM.Start(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.components.PrimaryButton
|
||||||
|
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM.GenerateSeedPhrase.MnemonicGridItem
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiWalletSeedPhraseWords(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) {
|
||||||
|
Column(modifier.fillMaxSize()) {
|
||||||
|
SegmentSeedBlock(
|
||||||
|
modifier = Modifier.padding(vertical = 20.dp),
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
|
||||||
|
TitleBlock(state)
|
||||||
|
|
||||||
|
SeedPhraseGridBlock(
|
||||||
|
mnemonicGridItems = state.words,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 20.dp, bottom = 92.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(Modifier.weight(1f)) {
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||||
|
text = stringResource(id = R.string.common_continue),
|
||||||
|
onClick = state.onContinueClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ComplexCondition", "MagicNumber")
|
||||||
|
@Composable
|
||||||
|
private fun SegmentSeedBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) {
|
||||||
|
SegmentedButtons(
|
||||||
|
modifier = modifier.padding(horizontal = TangemTheme.dimens.spacing76),
|
||||||
|
config = persistentListOf(0, 1),
|
||||||
|
initialSelectedItem = if (state.words24OptionSelected.not()) 0 else 1,
|
||||||
|
onClick = {
|
||||||
|
if (state.words24OptionSelected && it == 1 || state.words24OptionSelected.not() && it == 0) {
|
||||||
|
state.onWords24OptionSwitch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(
|
||||||
|
id = R.plurals.onboarding_seed_generate_words_count,
|
||||||
|
count = if (it == 0) 12 else 24,
|
||||||
|
if (it == 0) 12 else 24,
|
||||||
|
),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = TangemTheme.dimens.spacing10)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
style = TangemTheme.typography.subtitle2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
@Composable
|
||||||
|
private fun TitleBlock(state: MultiWalletSeedPhraseUM.GenerateSeedPhrase, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.padding(horizontal = TangemTheme.dimens.size36)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.onboarding_seed_generate_title),
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = pluralStringResource(
|
||||||
|
id = R.plurals.onboarding_seed_generate_message_words_count,
|
||||||
|
count = if (state.words24OptionSelected.not()) 12 else 24,
|
||||||
|
if (state.words24OptionSelected.not()) 12 else 24,
|
||||||
|
),
|
||||||
|
style = TangemTheme.typography.body1,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SeedPhraseGridBlock(mnemonicGridItems: ImmutableList<MnemonicGridItem>, modifier: Modifier = Modifier) {
|
||||||
|
VerticalGrid(
|
||||||
|
modifier = modifier
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
items = mnemonicGridItems,
|
||||||
|
) { item ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(all = TangemTheme.dimens.size8),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (LocalLayoutDirection.current == LayoutDirection.Ltr) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.width(TangemTheme.dimens.size40),
|
||||||
|
text = "${item.index}.",
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = item.mnemonic,
|
||||||
|
style = TangemTheme.typography.button,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = item.mnemonic,
|
||||||
|
style = TangemTheme.typography.button,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.width(TangemTheme.dimens.size40),
|
||||||
|
text = "${item.index}.",
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private inline fun <T> VerticalGrid(
|
||||||
|
items: ImmutableList<T>,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
crossinline content: @Composable (T) -> Unit,
|
||||||
|
) {
|
||||||
|
val columnLength = items.size / 2
|
||||||
|
Row(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
for (index in 0 until 2) {
|
||||||
|
Column {
|
||||||
|
for (i in 0 until columnLength) {
|
||||||
|
val item = items[index * columnLength + i]
|
||||||
|
content(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
MultiWalletSeedPhraseWords(
|
||||||
|
state = MultiWalletSeedPhraseUM.GenerateSeedPhrase(
|
||||||
|
words = persistentListOf(
|
||||||
|
MnemonicGridItem(
|
||||||
|
index = 0,
|
||||||
|
mnemonic = "word1",
|
||||||
|
),
|
||||||
|
MnemonicGridItem(
|
||||||
|
index = 1,
|
||||||
|
mnemonic = "word1",
|
||||||
|
),
|
||||||
|
MnemonicGridItem(
|
||||||
|
index = 2,
|
||||||
|
mnemonic = "word1",
|
||||||
|
),
|
||||||
|
MnemonicGridItem(
|
||||||
|
index = 3,
|
||||||
|
mnemonic = "word1",
|
||||||
|
),
|
||||||
|
MnemonicGridItem(
|
||||||
|
index = 4,
|
||||||
|
mnemonic = "word1",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.OutlineTextField
|
||||||
|
import com.tangem.core.ui.components.PrimaryButtonIconEnd
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state.MultiWalletSeedPhraseUM
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MultiWalletSeedPhraseWordsCheck(
|
||||||
|
state: MultiWalletSeedPhraseUM.GeneratedWordsCheck,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.onboarding_seed_user_validation_title),
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = 48.dp, start = 36.dp, end = 36.dp, bottom = 16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.onboarding_seed_user_validation_message),
|
||||||
|
style = TangemTheme.typography.body1,
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 36.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
|
||||||
|
Fields(
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 30.dp, horizontal = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier.weight(1f),
|
||||||
|
) {
|
||||||
|
PrimaryButtonIconEnd(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomStart)
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.onboarding_create_wallet_button_create_wallet),
|
||||||
|
iconResId = R.drawable.ic_tangem_24,
|
||||||
|
enabled = state.createWalletButtonEnabled,
|
||||||
|
showProgress = state.createWalletButtonProgress,
|
||||||
|
onClick = state.onCreateWalletButtonClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Fields(state: MultiWalletSeedPhraseUM.GeneratedWordsCheck, modifier: Modifier = Modifier) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
state.wordFields.forEach { field ->
|
||||||
|
OutlineTextField(
|
||||||
|
value = field.word,
|
||||||
|
onValueChange = field.onChange,
|
||||||
|
label = field.index.toString(),
|
||||||
|
isError = field.error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
private fun Preview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
MultiWalletSeedPhraseWordsCheck(
|
||||||
|
state = MultiWalletSeedPhraseUM.GeneratedWordsCheck(
|
||||||
|
wordFields = persistentListOf(
|
||||||
|
MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField(
|
||||||
|
index = 2,
|
||||||
|
word = TextFieldValue(text = "word"),
|
||||||
|
onChange = {},
|
||||||
|
error = false,
|
||||||
|
),
|
||||||
|
MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField(
|
||||||
|
index = 7,
|
||||||
|
word = TextFieldValue(text = "wor"),
|
||||||
|
onChange = {},
|
||||||
|
error = true,
|
||||||
|
),
|
||||||
|
MultiWalletSeedPhraseUM.GeneratedWordsCheck.WordField(
|
||||||
|
index = 11,
|
||||||
|
word = TextFieldValue(text = "word"),
|
||||||
|
onChange = {},
|
||||||
|
error = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.tangem.core.ui.components.PrimaryButton
|
||||||
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||||
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||||
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
|
import com.tangem.features.onboarding.v2.impl.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PassphraseInfoBottomSheet(config: TangemBottomSheetConfig) {
|
||||||
|
TangemBottomSheet(
|
||||||
|
config = config,
|
||||||
|
containerColor = TangemTheme.colors.background.primary,
|
||||||
|
) { _: TangemBottomSheetConfigContent.Empty ->
|
||||||
|
PassphraseInfoBottomSheetContent(config.onDismissRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PassphraseInfoBottomSheetContent(onDismiss: () -> Unit) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = TangemTheme.colors.background.primary)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.padding(top = TangemTheme.dimens.size40)
|
||||||
|
.size(TangemTheme.dimens.size48),
|
||||||
|
painter = painterResource(id = R.drawable.ic_information_24),
|
||||||
|
tint = TangemTheme.colors.icon.accent,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.common_passphrase),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = TangemTheme.dimens.size40)
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
color = TangemTheme.colors.text.primary1,
|
||||||
|
style = TangemTheme.typography.h2,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.onboarding_bottom_sheet_passphrase_description),
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(top = TangemTheme.dimens.size16)
|
||||||
|
.padding(horizontal = TangemTheme.dimens.size24)
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
color = TangemTheme.colors.text.secondary,
|
||||||
|
style = TangemTheme.typography.body2,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
|
||||||
|
PrimaryButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = TangemTheme.dimens.size16)
|
||||||
|
.padding(top = TangemTheme.dimens.size40)
|
||||||
|
.padding(bottom = TangemTheme.dimens.size32)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
text = stringResource(id = R.string.common_ok),
|
||||||
|
onClick = onDismiss,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PassphraseInfoBottomSheetContentPreview() {
|
||||||
|
TangemThemePreview {
|
||||||
|
PassphraseInfoBottomSheetContent({ })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.state
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
sealed class MultiWalletSeedPhraseUM(
|
||||||
|
val order: Int,
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class Start(
|
||||||
|
val onLearnMoreClicked: () -> Unit = {},
|
||||||
|
val onGenerateSeedPhraseClicked: () -> Unit = {},
|
||||||
|
val onImportSeedPhraseClicked: () -> Unit = {},
|
||||||
|
) : MultiWalletSeedPhraseUM(order = 0)
|
||||||
|
|
||||||
|
data class GenerateSeedPhrase(
|
||||||
|
val words24OptionSelected: Boolean = false,
|
||||||
|
val words: ImmutableList<MnemonicGridItem> = persistentListOf(),
|
||||||
|
val onWords24OptionSwitch: () -> Unit = {},
|
||||||
|
val onContinueClick: () -> Unit = {},
|
||||||
|
) : MultiWalletSeedPhraseUM(order = 1) {
|
||||||
|
data class MnemonicGridItem(
|
||||||
|
val index: Int,
|
||||||
|
val mnemonic: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GeneratedWordsCheck(
|
||||||
|
val wordFields: ImmutableList<WordField> = persistentListOf(),
|
||||||
|
val createWalletButtonEnabled: Boolean = false,
|
||||||
|
val createWalletButtonProgress: Boolean = false,
|
||||||
|
val onCreateWalletButtonClick: () -> Unit = {},
|
||||||
|
) : MultiWalletSeedPhraseUM(order = 2) {
|
||||||
|
data class WordField(
|
||||||
|
val index: Int,
|
||||||
|
val word: TextFieldValue,
|
||||||
|
val error: Boolean,
|
||||||
|
val onChange: (TextFieldValue) -> Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Import(
|
||||||
|
val words: TextFieldValue = TextFieldValue(""),
|
||||||
|
val wordsChange: (TextFieldValue) -> Unit = {},
|
||||||
|
val passPhrase: TextFieldValue = TextFieldValue(""),
|
||||||
|
val passPhraseChange: (TextFieldValue) -> Unit = {},
|
||||||
|
val onPassphraseInfoClick: () -> Unit = {},
|
||||||
|
val wordsErrorText: TextReference? = null,
|
||||||
|
val invalidWords: ImmutableList<String> = persistentListOf(),
|
||||||
|
val createWalletEnabled: Boolean = false,
|
||||||
|
val createWalletProgress: Boolean = false,
|
||||||
|
val createWalletClick: () -> Unit = {},
|
||||||
|
val suggestionsList: ImmutableList<String> = persistentListOf(),
|
||||||
|
val onSuggestionClick: (String) -> Unit = {},
|
||||||
|
val infoBottomSheetConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty,
|
||||||
|
) : MultiWalletSeedPhraseUM(order = 1)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.ui.utils
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.input.OffsetMapping
|
||||||
|
import androidx.compose.ui.text.input.TransformedText
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import com.tangem.utils.extensions.isSingleItem
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
class InvalidWordsColorTransformation(
|
||||||
|
private val wordsToBrush: ImmutableList<String>,
|
||||||
|
private val style: SpanStyle,
|
||||||
|
) : VisualTransformation {
|
||||||
|
|
||||||
|
override fun filter(text: AnnotatedString): TransformedText {
|
||||||
|
val plainText = text.text
|
||||||
|
if (wordsToBrush.isSingleItem() && wordsToBrush.first() == plainText) {
|
||||||
|
return plainText.annotate().toTransformedText()
|
||||||
|
}
|
||||||
|
|
||||||
|
val wordList = makeSeparatedWordlist(plainText)
|
||||||
|
val annotatedString = makeAnnotatedString(wordList)
|
||||||
|
|
||||||
|
return annotatedString.toTransformedText()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeSeparatedWordlist(plainText: String): List<String> {
|
||||||
|
val plainTextCharArray = plainText.toCharArray()
|
||||||
|
val wordlist = mutableListOf<String>()
|
||||||
|
var wordBuilder = StringBuilder()
|
||||||
|
|
||||||
|
for (index in plainTextCharArray.indices) {
|
||||||
|
val char = plainTextCharArray[index]
|
||||||
|
if (char.isWhitespace()) {
|
||||||
|
if (wordBuilder.isEmpty()) {
|
||||||
|
wordlist.add(WORD_SEPARATOR)
|
||||||
|
} else {
|
||||||
|
wordlist.add(wordBuilder.toString())
|
||||||
|
wordBuilder = StringBuilder()
|
||||||
|
wordlist.add(WORD_SEPARATOR)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (index == plainTextCharArray.lastIndex) {
|
||||||
|
wordBuilder.append(char)
|
||||||
|
wordlist.add(wordBuilder.toString())
|
||||||
|
wordBuilder = StringBuilder()
|
||||||
|
} else {
|
||||||
|
wordBuilder.append(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wordlist.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makeAnnotatedString(wordList: List<String>): AnnotatedString = buildAnnotatedString {
|
||||||
|
wordList.forEach {
|
||||||
|
if (it == WORD_SEPARATOR) {
|
||||||
|
append(WHITE_SPACE)
|
||||||
|
} else if (wordsToBrush.contains(it)) {
|
||||||
|
append(it.annotate())
|
||||||
|
} else {
|
||||||
|
append(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.annotate(): AnnotatedString = AnnotatedString(this, style)
|
||||||
|
|
||||||
|
private fun AnnotatedString.toTransformedText(): TransformedText = TransformedText(this, OffsetMapping.Identity)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val WHITE_SPACE = " "
|
||||||
|
private const val WORD_SEPARATOR = "_$@-*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.features.onboarding.v2.multiwallet.api.OnboardingMultiWalletCo
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent
|
import com.tangem.features.onboarding.v2.multiwallet.impl.DefaultOnboardingMultiWalletComponent
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.backup.model.MultiWalletBackupModel
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.createwallet.model.MultiWalletCreateWalletModel
|
||||||
|
import com.tangem.features.onboarding.v2.multiwallet.impl.child.seedphrase.model.MultiWalletSeedPhraseModel
|
||||||
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
import com.tangem.features.onboarding.v2.multiwallet.impl.model.OnboardingMultiWalletModel
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -42,4 +43,9 @@ internal interface ModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(MultiWalletCreateWalletModel::class)
|
@ClassKey(MultiWalletCreateWalletModel::class)
|
||||||
fun provideModel3(model: MultiWalletCreateWalletModel): Model
|
fun provideModel3(model: MultiWalletCreateWalletModel): Model
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(MultiWalletSeedPhraseModel::class)
|
||||||
|
fun provideModel4(model: MultiWalletSeedPhraseModel): Model
|
||||||
}
|
}
|
||||||
|
|
@ -2,8 +2,6 @@ package com.tangem.features.onboarding.v2.multiwallet.impl.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -18,33 +16,32 @@ import com.tangem.features.onboarding.v2.multiwallet.impl.ui.state.OnboardingMul
|
||||||
internal fun OnboardingMultiWallet(
|
internal fun OnboardingMultiWallet(
|
||||||
state: OnboardingMultiWalletUM,
|
state: OnboardingMultiWalletUM,
|
||||||
artworksState: WalletArtworksState,
|
artworksState: WalletArtworksState,
|
||||||
|
isSeedPhraseState: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
childContent: @Composable (Modifier) -> Unit = {},
|
childContent: @Composable (Modifier) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
|
||||||
.verticalScroll(rememberScrollState()),
|
|
||||||
) {
|
) {
|
||||||
WalletArtworks(
|
if (isSeedPhraseState.not()) {
|
||||||
url = state.artworkUrl,
|
WalletArtworks(
|
||||||
modifier = Modifier
|
url = state.artworkUrl,
|
||||||
.weight(.56f)
|
modifier = Modifier
|
||||||
.padding(horizontal = 34.dp)
|
.weight(.56f)
|
||||||
.padding(top = 20.dp)
|
.padding(horizontal = 34.dp)
|
||||||
.fillMaxWidth(),
|
.padding(top = 20.dp)
|
||||||
state = artworksState,
|
.fillMaxWidth(),
|
||||||
)
|
state = artworksState,
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = if (artworksState == WalletArtworksState.Hidden) {
|
modifier = Modifier.weight(.44f),
|
||||||
Modifier.fillMaxSize()
|
contentAlignment = Alignment.BottomStart,
|
||||||
} else {
|
) {
|
||||||
Modifier.weight(.44f)
|
childContent(Modifier)
|
||||||
},
|
}
|
||||||
contentAlignment = Alignment.BottomStart,
|
} else {
|
||||||
) {
|
childContent(Modifier.fillMaxSize())
|
||||||
childContent(Modifier)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,6 +53,7 @@ private fun Preview() {
|
||||||
OnboardingMultiWallet(
|
OnboardingMultiWallet(
|
||||||
state = OnboardingMultiWalletUM(artworkUrl = null),
|
state = OnboardingMultiWalletUM(artworkUrl = null),
|
||||||
artworksState = WalletArtworksState.Folded,
|
artworksState = WalletArtworksState.Folded,
|
||||||
|
isSeedPhraseState = false,
|
||||||
childContent = { md ->
|
childContent = { md ->
|
||||||
Box(
|
Box(
|
||||||
modifier = md
|
modifier = md
|
||||||
|
|
@ -66,4 +64,23 @@ private fun Preview() {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, widthDp = 360, heightDp = 640)
|
||||||
|
@Composable
|
||||||
|
private fun Preview2() {
|
||||||
|
TangemThemePreview {
|
||||||
|
OnboardingMultiWallet(
|
||||||
|
state = OnboardingMultiWalletUM(artworkUrl = null),
|
||||||
|
artworksState = WalletArtworksState.Folded,
|
||||||
|
isSeedPhraseState = true,
|
||||||
|
childContent = { md ->
|
||||||
|
Box(
|
||||||
|
modifier = md
|
||||||
|
.background(Color.DarkGray)
|
||||||
|
.fillMaxSize(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue