Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-09 18:08:04 +05:00
parent 4edbc8d918
commit 27cf6b6532
4 changed files with 309 additions and 0 deletions

View file

@ -0,0 +1,39 @@
package com.tangem.features.hotwallet.manualbackup.check
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.crypto.bip39.Mnemonic
import com.tangem.features.hotwallet.manualbackup.check.model.ManualBackupCheckModel
import com.tangem.features.hotwallet.manualbackup.check.ui.ManualBackupCheckContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
internal class ManualBackupCheckComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: Params,
) : ComposableContentComponent, AppComponentContext by context {
private val model: ManualBackupCheckModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
ManualBackupCheckContent(
state = state,
modifier = modifier,
)
}
interface ModelCallbacks {
fun onCompleteClick()
}
data class Params(
val generatedWords: Mnemonic,
val callbacks: ModelCallbacks,
)
}

View file

@ -0,0 +1,18 @@
package com.tangem.features.hotwallet.manualbackup.check.entity
import androidx.compose.ui.text.input.TextFieldValue
import kotlinx.collections.immutable.ImmutableList
internal data class ManualBackupCheckUM(
val onCompleteButtonClick: () -> Unit,
val wordFields: ImmutableList<WordField>,
val completeButtonEnabled: Boolean,
val completeButtonProgress: Boolean,
) {
data class WordField(
val index: Int,
val word: TextFieldValue,
val error: Boolean,
val onChange: (TextFieldValue) -> Unit,
)
}

View file

@ -0,0 +1,105 @@
package com.tangem.features.hotwallet.manualbackup.check.model
import androidx.compose.runtime.Stable
import androidx.compose.ui.text.input.TextFieldValue
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.check.ManualBackupCheckComponent
import com.tangem.features.hotwallet.manualbackup.check.entity.ManualBackupCheckUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import javax.inject.Inject
import kotlin.Boolean
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.all
import kotlin.collections.map
import kotlin.error
@Stable
@ModelScoped
internal class ManualBackupCheckModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params = paramsContainer.require<ManualBackupCheckComponent.Params>()
private val callbacks = params.callbacks
internal val uiState: StateFlow<ManualBackupCheckUM>
field = MutableStateFlow(getInitialUIState())
@Suppress("MagicNumber")
private fun getInitialUIState(): ManualBackupCheckUM {
val wordFields = List(WORD_FIELD_INDICES.size) { index ->
val shownIndex = WORD_FIELD_INDICES[index]
ManualBackupCheckUM.WordField(
index = shownIndex,
word = TextFieldValue(""),
error = false,
onChange = { textFieldValue ->
updateWordField(shownIndex, textFieldValue)
},
)
}.toImmutableList()
return ManualBackupCheckUM(
wordFields = wordFields,
completeButtonEnabled = false,
completeButtonProgress = false,
onCompleteButtonClick = {
val currentUIState = uiState.value
if (currentUIState.completeButtonEnabled) {
callbacks.onCompleteClick()
}
},
)
}
private fun updateWordField(shownIndex: Int, newText: TextFieldValue) {
uiState.update { currentState ->
val updatedFields = currentState.wordFields.map { wordField ->
if (wordField.index == shownIndex) {
val isCorrect = checkWordField(newText.text, shownIndex)
wordField.copy(
word = newText,
error = !isCorrect,
)
} else {
wordField
}
}.toImmutableList()
val allFieldsCorrect = updatedFields.all { field ->
checkWordField(field.word.text, field.index)
}
currentState.copy(
wordFields = updatedFields,
completeButtonEnabled = allFieldsCorrect,
)
}
}
private fun checkWordField(word: String, shownIndex: Int): Boolean {
val generatedWords = params.generatedWords
val wordList = generatedWords.mnemonicComponents
return if (shownIndex <= wordList.size) {
wordList[shownIndex - 1] == word
} else {
false
}
}
companion object {
private val WORD_FIELD_INDICES = listOf(2, 7, 11)
}
}

View file

@ -0,0 +1,147 @@
package com.tangem.features.hotwallet.manualbackup.check.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.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
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 androidx.compose.ui.util.fastForEachIndexed
import com.tangem.core.ui.components.OutlineTextField
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.fields.contextmenu.DisableContextMenu
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.check.entity.ManualBackupCheckUM
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun ManualBackupCheckContent(state: ManualBackupCheckUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.background(TangemTheme.colors.background.primary)
.fillMaxSize()
.imePadding(),
) {
Column(
Modifier
.verticalScroll(rememberScrollState())
.weight(1f),
) {
Text(
text = stringResourceSafe(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 = stringResourceSafe(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),
)
}
PrimaryButton(
modifier = Modifier
.padding(16.dp)
.imePadding()
.fillMaxWidth(),
text = stringResourceSafe(id = R.string.common_continue),
enabled = state.completeButtonEnabled,
showProgress = state.completeButtonProgress,
onClick = state.onCompleteButtonClick,
)
}
}
@Composable
private fun Fields(state: ManualBackupCheckUM, modifier: Modifier = Modifier) {
val focusManager = LocalFocusManager.current
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
DisableContextMenu {
state.wordFields.fastForEachIndexed { index, field ->
OutlineTextField(
value = field.word,
onValueChange = field.onChange,
label = field.index.toString(),
isError = field.error,
keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false,
keyboardType = KeyboardType.Password,
imeAction = if (index == state.wordFields.lastIndex) ImeAction.Done else ImeAction.Next,
),
keyboardActions = KeyboardActions(
onDone = { focusManager.clearFocus() },
onNext = { focusManager.moveFocus(focusDirection = FocusDirection.Down) },
),
)
}
}
}
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview() {
TangemThemePreview {
ManualBackupCheckContent(
state = ManualBackupCheckUM(
onCompleteButtonClick = {},
wordFields = persistentListOf(
ManualBackupCheckUM.WordField(
index = 2,
word = TextFieldValue(text = "word"),
onChange = {},
error = false,
),
ManualBackupCheckUM.WordField(
index = 7,
word = TextFieldValue(text = "wor"),
onChange = {},
error = true,
),
ManualBackupCheckUM.WordField(
index = 11,
word = TextFieldValue(text = "word"),
onChange = {},
error = false,
),
),
completeButtonEnabled = false,
completeButtonProgress = false,
),
)
}
}