Updated on 2026-08-14
This commit is contained in:
parent
f368e6a2f5
commit
51ae08211f
5 changed files with 95 additions and 57 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue