Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-19 19:10:30 +05:00
parent f368e6a2f5
commit 51ae08211f
5 changed files with 95 additions and 57 deletions

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.essenty.lifecycle.doOnResume
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -21,6 +22,12 @@ internal class AccessCodeComponent @AssistedInject constructor(
private val model: AccessCodeModel = getOrCreateModel(params)
init {
lifecycle.doOnResume {
model.onResume()
}
}
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()

View file

@ -27,6 +27,7 @@ import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.UnlockHotWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -36,6 +37,9 @@ import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.resume
private const val SUCCESS_DISPLAY_DURATION_MS = 200L
private const val ERROR_DISPLAY_DURATION_MS = 500L
@Suppress("LongParameterList")
@Stable
@ModelScoped
@ -59,49 +63,93 @@ internal class AccessCodeModel @Inject constructor(
internal val uiState: StateFlow<AccessCodeUM>
field = MutableStateFlow(getInitialState())
internal fun onResume() {
uiState.update { currentState ->
currentState.copy(
onAccessCodeChange = ::onAccessCodeChange,
)
}
}
private fun getInitialState() = AccessCodeUM(
accessCode = "",
accessCodeColor = PinTextColor.Primary,
onAccessCodeChange = ::onAccessCodeChange,
isConfirmMode = params.accessCodeToConfirm != null,
buttonEnabled = false,
buttonInProgress = false,
onButtonClick = ::onButtonClick,
)
private fun onAccessCodeChange(value: String) {
uiState.update {
it.copy(
val isConfirmMode = uiState.value.isConfirmMode
val accessCodeLength = uiState.value.accessCodeLength
if (value.length > accessCodeLength) return
uiState.update { currentState ->
currentState.copy(
accessCode = value,
buttonEnabled = if (params.accessCodeToConfirm != null) {
value == params.accessCodeToConfirm
} else {
value.length == uiState.value.accessCodeLength
},
accessCodeColor = when {
params.accessCodeToConfirm == null -> PinTextColor.Primary
value.length != uiState.value.accessCodeLength -> PinTextColor.Primary
value == params.accessCodeToConfirm -> PinTextColor.Primary
else -> PinTextColor.WrongCode
},
accessCodeColor = PinTextColor.Primary,
)
}
if (value.length == accessCodeLength) {
uiState.update { currentState ->
currentState.copy(
onAccessCodeChange = {},
)
}
if (isConfirmMode) {
modelScope.launch {
if (value == params.accessCodeToConfirm) {
showSuccessAndProceed(params.accessCodeToConfirm)
} else {
showErrorAndReset()
}
}
} else {
onNewCodeSet()
}
}
}
private fun onButtonClick() {
if (params.accessCodeToConfirm == null) {
params.callbacks.onNewAccessCodeInput(params.userWalletId, uiState.value.accessCode)
} else {
setCode(params.userWalletId, params.accessCodeToConfirm)
private fun onNewCodeSet() {
val accessCode = uiState.value.accessCode
uiState.update { currentState ->
currentState.copy(
accessCode = "",
)
}
params.callbacks.onNewAccessCodeInput(params.userWalletId, accessCode)
}
private suspend fun showSuccessAndProceed(accessCodeToConfirm: String) {
uiState.update { currentState ->
currentState.copy(
accessCodeColor = PinTextColor.Success,
)
}
delay(timeMillis = SUCCESS_DISPLAY_DURATION_MS)
setCode(params.userWalletId, accessCodeToConfirm)
}
private suspend fun showErrorAndReset() {
uiState.update { currentState ->
currentState.copy(
accessCodeColor = PinTextColor.WrongCode,
)
}
delay(timeMillis = ERROR_DISPLAY_DURATION_MS)
uiState.update { currentState ->
currentState.copy(
accessCode = "",
accessCodeColor = PinTextColor.Primary,
onAccessCodeChange = ::onAccessCodeChange,
)
}
}
private fun setCode(userWalletId: UserWalletId, accessCode: String) {
modelScope.launch {
uiState.update {
it.copy(buttonInProgress = true)
}
runCatching {
val userWallet = getUserWalletUseCase(userWalletId)
.getOrElse { error("User wallet with id $userWalletId not found") }
@ -150,10 +198,6 @@ internal class AccessCodeModel @Inject constructor(
params.callbacks.onAccessCodeUpdated(params.userWalletId)
}.onFailure {
Timber.e(it)
uiState.update {
it.copy(buttonInProgress = false)
}
}
}
}

View file

@ -8,9 +8,6 @@ internal data class AccessCodeUM(
val accessCodeColor: PinTextColor,
val onAccessCodeChange: (String) -> Unit,
val isConfirmMode: Boolean,
val buttonEnabled: Boolean,
val buttonInProgress: Boolean,
val onButtonClick: () -> Unit,
) {
val accessCodeLength: Int = ACCESS_CODE_LENGTH
}

View file

@ -11,7 +11,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.res.R
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.fields.PinTextColor
import com.tangem.core.ui.components.fields.PinTextField
import com.tangem.core.ui.extensions.stringResourceSafe
@ -25,6 +24,7 @@ internal fun AccessCode(state: AccessCodeUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.navigationBarsPadding(),
) {
Column(
@ -32,7 +32,6 @@ internal fun AccessCode(state: AccessCodeUM, modifier: Modifier = Modifier) {
.padding(top = 16.dp)
.weight(1f)
.fillMaxSize()
.background(TangemTheme.colors.background.primary)
.padding(horizontal = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
@ -81,23 +80,6 @@ internal fun AccessCode(state: AccessCodeUM, modifier: Modifier = Modifier) {
)
}
}
PrimaryButton(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.imePadding(),
text = stringResourceSafe(
if (state.isConfirmMode) {
R.string.common_confirm
} else {
R.string.common_continue
},
),
onClick = state.onButtonClick,
enabled = state.buttonEnabled,
showProgress = state.buttonInProgress,
)
}
}
@ -112,9 +94,6 @@ private fun PreviewSet() {
accessCodeColor = PinTextColor.Primary,
onAccessCodeChange = {},
isConfirmMode = false,
buttonEnabled = false,
buttonInProgress = false,
onButtonClick = {},
),
)
}
@ -131,9 +110,6 @@ private fun PreviewConfirm() {
accessCodeColor = PinTextColor.Success,
onAccessCodeChange = {},
isConfirmMode = true,
buttonEnabled = true,
buttonInProgress = false,
onButtonClick = {},
),
)
}

View file

@ -6,6 +6,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.value.ObserveLifecycleMode
import com.arkivanov.decompose.value.subscribe
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
@ -14,6 +16,7 @@ import com.tangem.features.hotwallet.updateaccesscode.routing.UpdateAccessCodeCh
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
internal class DefaultUpdateAccessCodeComponent @AssistedInject constructor(
@Assisted private val appComponentContext: AppComponentContext,
@ -38,6 +41,17 @@ internal class DefaultUpdateAccessCodeComponent @AssistedInject constructor(
},
)
init {
innerStack.subscribe(
lifecycle = lifecycle,
mode = ObserveLifecycleMode.CREATE_DESTROY,
) { stack ->
componentScope.launch {
model.currentRoute.emit(stack.active.configuration)
}
}
}
@Composable
override fun Content(modifier: Modifier) {
val stackState by innerStack.subscribeAsState()