Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-07 18:16:10 +05:00
parent a9abc33107
commit 9ad0976865
4 changed files with 264 additions and 0 deletions

View file

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

View file

@ -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<MnemonicGridItem> = persistentListOf(),
) {
data class MnemonicGridItem(
val index: Int,
val mnemonic: String,
)
}

View file

@ -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<ManualBackupPhraseComponent.Params>()
private val callbacks = params.callbacks
internal val uiState: StateFlow<ManualBackupPhraseUM>
field = MutableStateFlow(getInitialUIState())
private fun getInitialUIState(): ManualBackupPhraseUM {
return ManualBackupPhraseUM(
onContinueClick = callbacks::onContinueClick,
)
}
}

View file

@ -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<ManualBackupPhraseUM.MnemonicGridItem>,
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 <T> VerticalGrid(
items: ImmutableList<T>,
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(),
),
)
}
}