From 9ad097686596db3dd82f0d27c2d8192d65651aa0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 7 Jul 2025 18:16:10 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../phrase/ManualBackupPhraseComponent.kt | 37 ++++ .../phrase/entity/ManualBackupPhraseUM.kt | 14 ++ .../phrase/model/ManualBackupPhraseModel.kt | 32 ++++ .../phrase/ui/ManualBackupPhraseContent.kt | 181 ++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/entity/ManualBackupPhraseUM.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt create mode 100644 features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ui/ManualBackupPhraseContent.kt diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt new file mode 100644 index 0000000000..1dd8b360fc --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ManualBackupPhraseComponent.kt @@ -0,0 +1,37 @@ +package com.tangem.features.hotwallet.manualbackup.phrase + +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.core.ui.decompose.ComposableContentComponent +import com.tangem.features.hotwallet.manualbackup.phrase.model.ManualBackupPhraseModel +import com.tangem.features.hotwallet.manualbackup.phrase.ui.ManualBackupPhraseContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +internal class ManualBackupPhraseComponent @AssistedInject constructor( + @Assisted private val context: AppComponentContext, + @Assisted private val params: Params, +) : ComposableContentComponent, AppComponentContext by context { + private val model: ManualBackupPhraseModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + ManualBackupPhraseContent( + state = state, + modifier = modifier, + ) + } + + interface ModelCallbacks { + fun onContinueClick() + } + + data class Params( + val callbacks: ModelCallbacks, + ) +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/entity/ManualBackupPhraseUM.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/entity/ManualBackupPhraseUM.kt new file mode 100644 index 0000000000..3a799f4f31 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/entity/ManualBackupPhraseUM.kt @@ -0,0 +1,14 @@ +package com.tangem.features.hotwallet.manualbackup.phrase.entity + +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal data class ManualBackupPhraseUM( + val onContinueClick: () -> Unit, + val words: ImmutableList = persistentListOf(), +) { + data class MnemonicGridItem( + val index: Int, + val mnemonic: String, + ) +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt new file mode 100644 index 0000000000..4320a86026 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/model/ManualBackupPhraseModel.kt @@ -0,0 +1,32 @@ +package com.tangem.features.hotwallet.manualbackup.phrase.model + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.features.hotwallet.manualbackup.phrase.ManualBackupPhraseComponent +import com.tangem.features.hotwallet.manualbackup.phrase.entity.ManualBackupPhraseUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Stable +@ModelScoped +internal class ManualBackupPhraseModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val params = paramsContainer.require() + private val callbacks = params.callbacks + + internal val uiState: StateFlow + field = MutableStateFlow(getInitialUIState()) + + private fun getInitialUIState(): ManualBackupPhraseUM { + return ManualBackupPhraseUM( + onContinueClick = callbacks::onContinueClick, + ) + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ui/ManualBackupPhraseContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ui/ManualBackupPhraseContent.kt new file mode 100644 index 0000000000..3bba1be463 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/manualbackup/phrase/ui/ManualBackupPhraseContent.kt @@ -0,0 +1,181 @@ +package com.tangem.features.hotwallet.manualbackup.phrase.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +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.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.extensions.stringResourceSafe +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.hotwallet.impl.R +import com.tangem.features.hotwallet.manualbackup.phrase.entity.ManualBackupPhraseUM +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList + +@Composable +internal fun ManualBackupPhraseContent(state: ManualBackupPhraseUM, modifier: Modifier = Modifier) { + Column( + modifier + .background(TangemTheme.colors.background.primary) + .fillMaxSize(), + ) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .imePadding() + .weight(1f), + ) { + TitleBlock( + state = state, + modifier = Modifier.padding(top = 20.dp), + ) + + SeedPhraseGridBlock( + mnemonicGridItems = state.words, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp, bottom = 32.dp), + ) + } + + Text( + text = stringResourceSafe(R.string.backup_seed_responsibility), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), + text = stringResourceSafe(id = R.string.common_continue), + onClick = state.onContinueClick, + ) + } +} + +@Composable +private fun TitleBlock(state: ManualBackupPhraseUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .padding(horizontal = TangemTheme.dimens.size36) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text( + text = stringResourceSafe(R.string.backup_seed_title), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + Text( + text = stringResourceSafe( + R.string.backup_seed_description, + state.words.size, + ), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + } +} + +@Composable +private fun SeedPhraseGridBlock( + mnemonicGridItems: ImmutableList, + modifier: Modifier = Modifier, +) { + VerticalGrid( + modifier = modifier, + 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 VerticalGrid( + items: ImmutableList, + modifier: Modifier = Modifier, + crossinline content: @Composable (T) -> Unit, +) { + val columnLength = items.size / 2 + Row( + modifier = modifier, + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + repeat(2) { index -> + Column { + for (i in 0 until columnLength) { + val item = items[index * columnLength + i] + content(item) + } + } + } + } +} + +@Preview(showBackground = true, widthDp = 360, heightDp = 640) +@Preview(showBackground = true, widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview() { + TangemThemePreview { + ManualBackupPhraseContent( + state = ManualBackupPhraseUM( + onContinueClick = {}, + words = List(12) { + ManualBackupPhraseUM.MnemonicGridItem( + index = it + 1, + mnemonic = "word${it + 1}", + ) + }.toImmutableList(), + ), + ) + } +} \ No newline at end of file