Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-29 13:59:01 +05:00
parent c761f40891
commit 95c984d9e3
18 changed files with 107 additions and 249 deletions

View file

@ -1,3 +0,0 @@
package com.tangem.features.hotwallet.accesscode
const val ACCESS_CODE_LENGTH = 6

View file

@ -1,52 +0,0 @@
package com.tangem.features.hotwallet.accesscode.confirm
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.accesscode.confirm.ui.SetAccessCodeConfirmContent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class ConfirmAccessCodeComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: Params,
) : ComposableContentComponent, AppComponentContext by context {
private val model: ConfirmAccessCodeModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
DisableScreenshotsDisposableEffect()
SetAccessCodeConfirmContent(
accessCode = state.accessCode,
onAccessCodeChange = state.onAccessCodeChange,
accessCodeLength = state.accessCodeLength,
onConfirm = state.onConfirm,
buttonEnabled = state.buttonEnabled,
modifier = modifier,
)
}
interface ModelCallbacks {
fun onAccessCodeConfirmed()
}
data class Params(
val accessCodeToConfirm: String,
val callbacks: ModelCallbacks,
)
@AssistedFactory
interface Factory {
fun create(context: AppComponentContext, params: Params): ConfirmAccessCodeComponent
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.features.hotwallet.accesscode.confirm.entity
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
internal data class ConfirmAccessCodeUM(
val accessCode: String,
val onAccessCodeChange: (String) -> Unit,
val buttonEnabled: Boolean,
val onConfirm: () -> Unit,
) {
val accessCodeLength: Int = ACCESS_CODE_LENGTH
}

View file

@ -1,26 +0,0 @@
package com.tangem.features.hotwallet.accesscode.di
import com.tangem.core.decompose.model.Model
import com.tangem.features.hotwallet.accesscode.confirm.ConfirmAccessCodeModel
import com.tangem.features.hotwallet.accesscode.set.SetAccessCodeModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AccessCodeModule {
@Binds
@IntoMap
@ClassKey(SetAccessCodeModel::class)
fun bindSetAccessCodeModel(model: SetAccessCodeModel): Model
@Binds
@IntoMap
@ClassKey(ConfirmAccessCodeModel::class)
fun bindConfirmAccessCodeModel(model: ConfirmAccessCodeModel): Model
}

View file

@ -1,45 +0,0 @@
package com.tangem.features.hotwallet.accesscode.set
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.accesscode.set.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())
private fun getInitialState() = SetAccessCodeUM(
accessCode = "",
onAccessCodeChange = ::onAccessCodeChange,
buttonEnabled = false,
onContinue = ::onContinue,
)
private fun onAccessCodeChange(value: String) {
uiState.update {
it.copy(
accessCode = value,
buttonEnabled = value.length == uiState.value.accessCodeLength,
)
}
}
private fun onContinue() {
params.callbacks.onAccessCodeSet(uiState.value.accessCode)
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.features.hotwallet.accesscode.set.entity
import com.tangem.features.hotwallet.accesscode.ACCESS_CODE_LENGTH
internal data class SetAccessCodeUM(
val accessCode: String,
val onAccessCodeChange: (String) -> Unit,
val buttonEnabled: Boolean,
val onContinue: () -> Unit,
) {
val accessCodeLength: Int = ACCESS_CODE_LENGTH
}

View file

@ -1,46 +0,0 @@
package com.tangem.features.hotwallet.accesscode.set.ui
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.features.hotwallet.accesscode.ui.AccessCodeEnter
import com.tangem.core.res.R
@Composable
internal fun SetAccessCodeContent(
accessCode: String,
onAccessCodeChange: (String) -> Unit,
accessCodeLength: Int,
onContinue: () -> Unit,
buttonEnabled: Boolean,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxSize()
.navigationBarsPadding(),
) {
AccessCodeEnter(
modifier = Modifier
.padding(top = 16.dp)
.weight(1f),
accessCode = accessCode,
onAccessCodeChange = onAccessCodeChange,
accessCodeLength = accessCodeLength,
reEnterAccessCodeState = false,
)
PrimaryButton(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.imePadding(),
text = stringResourceSafe(R.string.common_continue),
onClick = onContinue,
enabled = buttonEnabled,
)
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.features.hotwallet.accesscoderequest
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
import com.tangem.features.hotwallet.setaccesscode.ACCESS_CODE_LENGTH
import com.tangem.features.hotwallet.accesscoderequest.entity.HotAccessCodeRequestUM
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -82,8 +83,4 @@ internal class HotAccessCodeRequestModel @Inject constructor(
it.copy(isShown = false)
}
}
private companion object {
const val ACCESS_CODE_LENGTH = 6
}
}

View file

@ -14,8 +14,7 @@ import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWallet
import com.tangem.features.hotwallet.addexistingwallet.entry.routing.AddExistingWalletRoute
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
import com.tangem.features.hotwallet.manualbackup.completed.ManualBackupCompletedComponent
import com.tangem.features.hotwallet.accesscode.confirm.ConfirmAccessCodeComponent
import com.tangem.features.hotwallet.accesscode.set.SetAccessCodeComponent
import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent
import com.tangem.features.hotwallet.setupfinished.MobileWalletSetupFinishedComponent
import com.tangem.features.pushnotifications.api.PushNotificationsComponent
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
@ -34,8 +33,7 @@ internal class AddExistingWalletModel @Inject constructor(
val addExistingWalletImportModelCallbacks = AddExistingWalletImportModelCallbacks()
val manualBackupCompletedComponentModelCallbacks = ManualBackupCompletedComponentModelCallbacks()
val pushNotificationsComponentModelCallbacks = PushNotificationsComponentModelCallbacks()
val setAccessCodeModelCallbacks = SetAccessCodeModelCallbacks()
val confirmAccessCodeModelCallbacks = ConfirmAccessCodeModelCallbacks()
val accessCodeModelCallbacks = AccessCodeModelCallbacks()
val mobileWalletSetupFinishedComponentModelCallbacks = MobileWalletSetupFinishedComponentModelCallbacks()
val stackNavigation = StackNavigation<AddExistingWalletRoute>()
@ -91,13 +89,11 @@ internal class AddExistingWalletModel @Inject constructor(
}
}
inner class SetAccessCodeModelCallbacks : SetAccessCodeComponent.ModelCallbacks {
inner class AccessCodeModelCallbacks : AccessCodeComponent.ModelCallbacks {
override fun onAccessCodeSet(accessCode: String) {
stackNavigation.push(AddExistingWalletRoute.ConfirmAccessCode(accessCode))
}
}
inner class ConfirmAccessCodeModelCallbacks : ConfirmAccessCodeComponent.ModelCallbacks {
override fun onAccessCodeConfirmed() {
navigateToPushNotificationsOrNext()
}

View file

@ -2,8 +2,7 @@ package com.tangem.features.hotwallet.addexistingwallet.entry.routing
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.hotwallet.accesscode.set.SetAccessCodeComponent
import com.tangem.features.hotwallet.accesscode.confirm.ConfirmAccessCodeComponent
import com.tangem.features.hotwallet.setaccesscode.AccessCodeComponent
import com.tangem.features.hotwallet.addexistingwallet.entry.AddExistingWalletModel
import com.tangem.features.hotwallet.addexistingwallet.start.AddExistingWalletStartComponent
import com.tangem.features.hotwallet.addexistingwallet.im.port.AddExistingWalletImportComponent
@ -14,8 +13,7 @@ import javax.inject.Inject
internal class AddExistingWalletChildFactory @Inject constructor(
private val pushNotificationsComponent: PushNotificationsComponent.Factory,
private val setAccessCodeSetComponentFactory: SetAccessCodeComponent.Factory,
private val confirmAccessCodeComponentFactory: ConfirmAccessCodeComponent.Factory,
private val accessCodeComponentFactory: AccessCodeComponent.Factory,
) {
fun createChild(
@ -42,17 +40,19 @@ internal class AddExistingWalletChildFactory @Inject constructor(
callbacks = model.manualBackupCompletedComponentModelCallbacks,
),
)
is AddExistingWalletRoute.SetAccessCode -> setAccessCodeSetComponentFactory.create(
is AddExistingWalletRoute.SetAccessCode -> accessCodeComponentFactory.create(
context = childContext,
params = SetAccessCodeComponent.Params(
callbacks = model.setAccessCodeModelCallbacks,
params = AccessCodeComponent.Params(
isConfirmMode = false,
callbacks = model.accessCodeModelCallbacks,
),
)
is AddExistingWalletRoute.ConfirmAccessCode -> confirmAccessCodeComponentFactory.create(
is AddExistingWalletRoute.ConfirmAccessCode -> accessCodeComponentFactory.create(
context = childContext,
params = ConfirmAccessCodeComponent.Params(
params = AccessCodeComponent.Params(
isConfirmMode = true,
accessCodeToConfirm = route.accessCode,
callbacks = model.confirmAccessCodeModelCallbacks,
callbacks = model.accessCodeModelCallbacks,
),
)
is AddExistingWalletRoute.PushNotifications -> pushNotificationsComponent.create(

View file

@ -1,4 +1,4 @@
package com.tangem.features.hotwallet.accesscode.set
package com.tangem.features.hotwallet.setaccesscode
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -8,17 +8,19 @@ 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.accesscode.set.ui.SetAccessCodeContent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.features.hotwallet.setaccesscode.ui.AccessCodeLayout
import com.tangem.core.res.R
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
internal class SetAccessCodeComponent @AssistedInject constructor(
internal class AccessCodeComponent @AssistedInject constructor(
@Assisted private val context: AppComponentContext,
@Assisted private val params: Params,
) : ComposableContentComponent, AppComponentContext by context {
private val model: SetAccessCodeModel = getOrCreateModel(params)
private val model: AccessCodeModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
@ -26,26 +28,37 @@ internal class SetAccessCodeComponent @AssistedInject constructor(
DisableScreenshotsDisposableEffect()
SetAccessCodeContent(
AccessCodeLayout(
modifier = modifier,
accessCode = state.accessCode,
onAccessCodeChange = state.onAccessCodeChange,
accessCodeLength = state.accessCodeLength,
onContinue = state.onContinue,
reEnterAccessCodeState = params.isConfirmMode,
buttonText = stringResourceSafe(
if (params.isConfirmMode) {
R.string.common_confirm
} else {
R.string.common_continue
},
),
onButtonClick = state.onButtonClick,
buttonEnabled = state.buttonEnabled,
modifier = modifier,
)
}
interface ModelCallbacks {
fun onAccessCodeSet(accessCode: String)
fun onAccessCodeConfirmed()
}
data class Params(
val isConfirmMode: Boolean,
val accessCodeToConfirm: String? = null,
val callbacks: ModelCallbacks,
)
@AssistedFactory
interface Factory {
fun create(context: AppComponentContext, params: Params): SetAccessCodeComponent
fun create(context: AppComponentContext, params: Params): AccessCodeComponent
}
}

View file

@ -1,10 +1,10 @@
package com.tangem.features.hotwallet.accesscode.confirm
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.accesscode.confirm.entity.ConfirmAccessCodeUM
import com.tangem.features.hotwallet.setaccesscode.entity.AccessCodeUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -13,33 +13,41 @@ import javax.inject.Inject
@Stable
@ModelScoped
internal class ConfirmAccessCodeModel @Inject constructor(
internal class AccessCodeModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
private val params = paramsContainer.require<ConfirmAccessCodeComponent.Params>()
private val params = paramsContainer.require<AccessCodeComponent.Params>()
internal val uiState: StateFlow<ConfirmAccessCodeUM>
internal val uiState: StateFlow<AccessCodeUM>
field = MutableStateFlow(getInitialState())
private fun getInitialState() = ConfirmAccessCodeUM(
private fun getInitialState() = AccessCodeUM(
accessCode = "",
onAccessCodeChange = ::onAccessCodeChange,
buttonEnabled = false,
onConfirm = ::onConfirm,
onButtonClick = ::onButtonClick,
)
private fun onAccessCodeChange(value: String) {
uiState.update {
it.copy(
accessCode = value,
buttonEnabled = value == params.accessCodeToConfirm,
buttonEnabled = if (params.isConfirmMode) {
value == params.accessCodeToConfirm
} else {
value.length == uiState.value.accessCodeLength
},
)
}
}
private fun onConfirm() {
params.callbacks.onAccessCodeConfirmed()
private fun onButtonClick() {
if (params.isConfirmMode) {
params.callbacks.onAccessCodeConfirmed()
} else {
params.callbacks.onAccessCodeSet(uiState.value.accessCode)
}
}
}

View file

@ -0,0 +1,3 @@
package com.tangem.features.hotwallet.setaccesscode
const val ACCESS_CODE_LENGTH = 6

View file

@ -0,0 +1,20 @@
package com.tangem.features.hotwallet.setaccesscode.di
import com.tangem.core.decompose.model.Model
import com.tangem.features.hotwallet.setaccesscode.AccessCodeModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(SingletonComponent::class)
internal interface AccessCodeModule {
@Binds
@IntoMap
@ClassKey(AccessCodeModel::class)
fun bindAccessCodeModel(model: AccessCodeModel): Model
}

View file

@ -0,0 +1,12 @@
package com.tangem.features.hotwallet.setaccesscode.entity
import com.tangem.features.hotwallet.setaccesscode.ACCESS_CODE_LENGTH
internal data class AccessCodeUM(
val accessCode: String,
val onAccessCodeChange: (String) -> Unit,
val buttonEnabled: Boolean,
val onButtonClick: () -> Unit,
) {
val accessCodeLength: Int = ACCESS_CODE_LENGTH
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.hotwallet.accesscode.ui
package com.tangem.features.hotwallet.setaccesscode.ui
import android.content.res.Configuration
import androidx.compose.foundation.background

View file

@ -1,20 +1,25 @@
package com.tangem.features.hotwallet.accesscode.confirm.ui
package com.tangem.features.hotwallet.setaccesscode.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
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.features.hotwallet.accesscode.ui.AccessCodeEnter
import com.tangem.core.res.R
@Suppress("LongParameterList")
@Composable
internal fun SetAccessCodeConfirmContent(
internal fun AccessCodeLayout(
accessCode: String,
onAccessCodeChange: (String) -> Unit,
accessCodeLength: Int,
onConfirm: () -> Unit,
reEnterAccessCodeState: Boolean,
buttonText: String,
onButtonClick: () -> Unit,
buttonEnabled: Boolean,
modifier: Modifier = Modifier,
) {
@ -30,7 +35,7 @@ internal fun SetAccessCodeConfirmContent(
accessCode = accessCode,
onAccessCodeChange = onAccessCodeChange,
accessCodeLength = accessCodeLength,
reEnterAccessCodeState = true,
reEnterAccessCodeState = reEnterAccessCodeState,
)
PrimaryButton(
@ -38,8 +43,8 @@ internal fun SetAccessCodeConfirmContent(
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
.imePadding(),
text = stringResourceSafe(R.string.common_confirm),
onClick = onConfirm,
text = buttonText,
onClick = onButtonClick,
enabled = buttonEnabled,
)
}

View file

@ -43,18 +43,18 @@ internal fun HotWalletStepper(
) {
TangemTopAppBar(
startButton = if (state.showBackButton) {
TopAppBarButtonUM.Back { onBackClick() }
TopAppBarButtonUM.Back(onBackClick)
} else {
null
},
endButton = when {
state.showSkipButton -> TopAppBarButtonUM.Text(
text = resourceReference(R.string.common_skip),
onClicked = { onSkipClick() },
onClicked = onSkipClick,
)
state.showFeedbackButton -> TopAppBarButtonUM.Icon(
iconRes = R.drawable.ic_chat_24,
onClicked = { onFeedbackClick() },
onClicked = onFeedbackClick,
)
else -> null
},