Updated on 2026-08-14
This commit is contained in:
parent
5e9771c378
commit
ca03f18d81
11 changed files with 363 additions and 4 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<AddExistingWalletRoute>()
|
||||
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,4 +13,7 @@ internal sealed class AddExistingWalletRoute : Route {
|
|||
|
||||
@Serializable
|
||||
object PushNotifications : AddExistingWalletRoute()
|
||||
|
||||
@Serializable
|
||||
object AccessCode : AddExistingWalletRoute()
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
@ -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<SetAccessCodeComponent.Params>()
|
||||
|
||||
internal val uiState: StateFlow<SetAccessCodeUM>
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue