diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt index 7c7285b37d..a06106b354 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/PinTextField.kt @@ -107,7 +107,7 @@ private fun CellDecoration( Box( modifier = Modifier .background( - color = TangemTheme.colors.background.tertiary, + color = TangemTheme.colors.field.primary, shape = RoundedCornerShape(8.dp), ), contentAlignment = Alignment.Center, diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/AddExistingWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/AddExistingWalletModel.kt index f0d230ef97..da289198f7 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/AddExistingWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/AddExistingWalletModel.kt @@ -6,6 +6,7 @@ import com.arkivanov.decompose.router.stack.push import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.navigation.Router +import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent import com.tangem.features.hotwallet.addexistingwallet.root.routing.AddExistingWalletRoute @@ -22,6 +23,7 @@ internal class AddExistingWalletModel @Inject constructor( val addExistingWalletStartModelCallbacks = AddExistingWalletStartModelCallbacks() val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks() val pushNotificationsComponentModelCallbacks = PushNotificationsComponentModelCallbacks() + val accessCodeModelCallbacks = AccessCodeModelCallbacks() val stackNavigation = StackNavigation() @@ -46,4 +48,14 @@ internal class AddExistingWalletModel @Inject constructor( // TODO [REDACTED_TASK_KEY] } } + + inner class AccessCodeModelCallbacks : SetAccessCodeComponent.ModelCallbacks { + override fun onBackClick() { + // TODO [REDACTED_TASK_KEY] + } + + override fun onAccessCodeSet() { + // TODO [REDACTED_TASK_KEY] + } + } } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/di/AddExistingWalletModule.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/di/AddExistingWalletModule.kt index d2019f9f33..eebd8bf8cb 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/di/AddExistingWalletModule.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/di/AddExistingWalletModule.kt @@ -2,6 +2,7 @@ package com.tangem.features.hotwallet.addexistingwallet.root.di import com.tangem.core.decompose.model.Model import com.tangem.features.hotwallet.AddExistingWalletComponent +import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeModel import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel import com.tangem.features.hotwallet.addexistingwallet.root.DefaultAddExistingWalletComponent import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartModel @@ -42,4 +43,9 @@ internal interface AddExistingWalletModuleBinds { @IntoMap @ClassKey(AddExistingWalletImportModel::class) fun bindAddExistingWalletImportModel(model: AddExistingWalletImportModel): Model + + @Binds + @IntoMap + @ClassKey(SetAccessCodeModel::class) + fun bindAccessCodeModel(model: SetAccessCodeModel): Model } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt index 9f8e3716a6..301d9caff4 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletChildFactory.kt @@ -2,6 +2,7 @@ package com.tangem.features.hotwallet.addexistingwallet.root.routing import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.features.hotwallet.setaccesscode.SetAccessCodeComponent import com.tangem.features.hotwallet.addexistingwallet.root.AddExistingWalletModel import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent @@ -36,6 +37,12 @@ internal class AddExistingWalletChildFactory @Inject constructor( callbacks = model.pushNotificationsComponentModelCallbacks, ), ) + is AddExistingWalletRoute.AccessCode -> SetAccessCodeComponent( + context = childContext, + params = SetAccessCodeComponent.Params( + callbacks = model.accessCodeModelCallbacks, + ), + ) } } } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletRoute.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletRoute.kt index 2b26cc1e69..e38bdfdc60 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletRoute.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/addexistingwallet/root/routing/AddExistingWalletRoute.kt @@ -13,4 +13,7 @@ internal sealed class AddExistingWalletRoute : Route { @Serializable object PushNotifications : AddExistingWalletRoute() + + @Serializable + object AccessCode : AddExistingWalletRoute() } \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/SetAccessCodeComponent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/SetAccessCodeComponent.kt new file mode 100644 index 0000000000..b1f774aa93 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/SetAccessCodeComponent.kt @@ -0,0 +1,43 @@ +package com.tangem.features.hotwallet.setaccesscode + +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.core.ui.security.DisableScreenshotsDisposableEffect +import com.tangem.features.hotwallet.setaccesscode.ui.SetAccessCodeContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject + +internal class SetAccessCodeComponent @AssistedInject constructor( + @Assisted private val context: AppComponentContext, + @Assisted private val params: Params, +) : ComposableContentComponent, AppComponentContext by context { + + private val model: SetAccessCodeModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + SetAccessCodeContent( + state = state, + onBack = { model.onBack() }, + modifier = modifier, + ) + + DisableScreenshotsDisposableEffect() + } + + interface ModelCallbacks { + fun onBackClick() + fun onAccessCodeSet() + } + + 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/setaccesscode/SetAccessCodeModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/SetAccessCodeModel.kt new file mode 100644 index 0000000000..945ff9ad20 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/SetAccessCodeModel.kt @@ -0,0 +1,84 @@ +package com.tangem.features.hotwallet.setaccesscode + +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.setaccesscode.entity.SetAccessCodeUM +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import javax.inject.Inject + +@Stable +@ModelScoped +internal class SetAccessCodeModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, +) : Model() { + + private val params = paramsContainer.require() + + internal val uiState: StateFlow + field = MutableStateFlow(getInitialState()) + + fun onBack() { + when (uiState.value.step) { + SetAccessCodeUM.Step.AccessCode -> { + params.callbacks.onBackClick() + } + SetAccessCodeUM.Step.ConfirmAccessCode -> { + uiState.update { + it.copy( + step = SetAccessCodeUM.Step.AccessCode, + accessCodeSecond = "", + ) + } + } + } + } + + private fun getInitialState() = SetAccessCodeUM( + step = SetAccessCodeUM.Step.AccessCode, + accessCodeFirst = "", + accessCodeSecond = "", + onAccessCodeFirstChange = ::onAccessCodeFirstChange, + onAccessCodeSecondChange = ::onAccessCodeSecondChange, + buttonEnabled = false, + onContinue = ::onContinue, + ) + + private fun onAccessCodeFirstChange(value: String) { + uiState.update { + it.copy( + accessCodeFirst = value, + buttonEnabled = value.length == uiState.value.accessCodeLength, + ) + } + } + + private fun onAccessCodeSecondChange(value: String) { + uiState.update { + it.copy( + accessCodeSecond = value, + buttonEnabled = uiState.value.accessCodeFirst == uiState.value.accessCodeSecond, + ) + } + } + + private fun onContinue() { + when (uiState.value.step) { + SetAccessCodeUM.Step.AccessCode -> { + uiState.update { + it.copy( + step = SetAccessCodeUM.Step.ConfirmAccessCode, + ) + } + } + SetAccessCodeUM.Step.ConfirmAccessCode -> { + params.callbacks.onAccessCodeSet() + } + } + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/entity/SetAccessCodeUM.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/entity/SetAccessCodeUM.kt new file mode 100644 index 0000000000..a5e98ca830 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/entity/SetAccessCodeUM.kt @@ -0,0 +1,22 @@ +package com.tangem.features.hotwallet.setaccesscode.entity + +internal data class SetAccessCodeUM( + val step: Step, + val accessCodeFirst: String, + val accessCodeSecond: String, + val onAccessCodeFirstChange: (String) -> Unit, + val onAccessCodeSecondChange: (String) -> Unit, + val buttonEnabled: Boolean, + val onContinue: () -> Unit, +) { + val accessCodeLength: Int = ACCESS_CODE_LENGTH + + enum class Step { + AccessCode, + ConfirmAccessCode, + } + + companion object { + private const val ACCESS_CODE_LENGTH = 6 + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeContent.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeContent.kt new file mode 100644 index 0000000000..64449365ae --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeContent.kt @@ -0,0 +1,59 @@ +package com.tangem.features.hotwallet.setaccesscode.ui + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +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.TangemAnimations +import com.tangem.features.hotwallet.setaccesscode.entity.SetAccessCodeUM +import com.tangem.features.hotwallet.setaccesscode.entity.SetAccessCodeUM.Step.* +import com.tangem.core.res.R + +@Composable +internal fun SetAccessCodeContent(state: SetAccessCodeUM, onBack: () -> Unit, modifier: Modifier = Modifier) { + BackHandler(onBack = onBack) + + Column( + modifier = modifier + .fillMaxSize() + .navigationBarsPadding(), + ) { + AnimatedContent( + modifier = Modifier.weight(1f), + targetState = state.step, + transitionSpec = TangemAnimations.AnimatedContent + .slide { initial, target -> target.ordinal > initial.ordinal }, + label = "AnimatedContent", + ) { step -> + when (step) { + AccessCode -> SetAccessCodeEnter( + modifier = Modifier.padding(top = 16.dp), + state = state, + reEnterAccessCodeState = false, + ) + ConfirmAccessCode -> SetAccessCodeEnter( + modifier = Modifier.padding(top = 16.dp), + state = state, + reEnterAccessCodeState = true, + ) + } + } + + PrimaryButton( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .imePadding(), + text = if (state.step == ConfirmAccessCode) { + stringResourceSafe(R.string.common_confirm) + } else { + stringResourceSafe(R.string.common_continue) + }, + onClick = state.onContinue, + ) + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeEnter.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeEnter.kt new file mode 100644 index 0000000000..bd787c1822 --- /dev/null +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/setaccesscode/ui/SetAccessCodeEnter.kt @@ -0,0 +1,125 @@ +package com.tangem.features.hotwallet.setaccesscode.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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.fields.PinTextField +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.setaccesscode.entity.SetAccessCodeUM + +@Composable +internal fun SetAccessCodeEnter( + state: SetAccessCodeUM, + reEnterAccessCodeState: Boolean, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier + .fillMaxSize() + .background(TangemTheme.colors.background.primary) + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier + .padding(top = 56.dp) + .align(Alignment.CenterHorizontally), + text = if (reEnterAccessCodeState) { + stringResourceSafe(R.string.access_code_confirm_title) + } else { + stringResourceSafe(R.string.access_code_create_title) + }, + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + ) + Text( + modifier = Modifier + .padding(16.dp) + .align(Alignment.CenterHorizontally), + text = if (reEnterAccessCodeState) { + stringResourceSafe(R.string.access_code_confirm_description) + } else { + stringResourceSafe( + R.string.access_code_create_description, + state.accessCodeLength, + ) + }, + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + contentAlignment = Alignment.Center, + ) { + PinTextField( + length = state.accessCodeLength, + isPasswordVisual = true, + value = if (reEnterAccessCodeState) { + state.accessCodeSecond + } else { + state.accessCodeFirst + }, + onValueChange = if (reEnterAccessCodeState) { + state.onAccessCodeSecondChange + } else { + state.onAccessCodeFirstChange + }, + ) + } + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview() { + TangemThemePreview { + SetAccessCodeEnter( + reEnterAccessCodeState = false, + state = SetAccessCodeUM( + step = SetAccessCodeUM.Step.AccessCode, + accessCodeFirst = "", + accessCodeSecond = "", + onAccessCodeFirstChange = {}, + onAccessCodeSecondChange = {}, + buttonEnabled = false, + onContinue = {}, + ), + ) + } +} + +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview2() { + TangemThemePreview { + SetAccessCodeEnter( + reEnterAccessCodeState = true, + state = SetAccessCodeUM( + step = SetAccessCodeUM.Step.ConfirmAccessCode, + accessCodeFirst = "", + accessCodeSecond = "", + onAccessCodeFirstChange = {}, + onAccessCodeSecondChange = {}, + buttonEnabled = false, + onContinue = {}, + ), + ) + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt index bc0d8551bd..6c8cb1fc1b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/accesscode/ui/MultiWalletAccessCodeEnter.kt @@ -71,13 +71,11 @@ internal fun MultiWalletAccessCodeEnter( state.onAccessCodeFirstChange }, label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third), - isError = state.codesNotMatchError || state.atLeast4CharError, + isError = state.codesNotMatchError, visualTransformation = PasswordVisualTransformation(), caption = when { state.codesNotMatchError && reEnterAccessCodeState -> stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) - state.atLeast4CharError && !reEnterAccessCodeState -> - stringResourceSafe(R.string.onboarding_access_code_too_short) else -> null }, )