Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-28 18:26:31 +03:00
parent e72bf276da
commit 41dea25009
17 changed files with 348 additions and 23 deletions

View file

@ -1,9 +1,16 @@
package com.tangem.tap.features.saveWallet.redux
import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
import org.rekotlin.Action
internal sealed interface SaveWalletAction : Action {
data class ProvideAdditionalInfo(
val scanResponse: ScanResponse,
val accessCode: String?,
val backupCardsIds: Set<String>?,
) : SaveWalletAction
object Save : SaveWalletAction {
object Success : SaveWalletAction
data class Error(val error: TangemError) : SaveWalletAction
@ -12,6 +19,10 @@ internal sealed interface SaveWalletAction : Action {
object Dismiss : SaveWalletAction
object CloseError : SaveWalletAction
object EnrollBiometrics : SaveWalletAction {
object Enroll : SaveWalletAction
object Cancel : SaveWalletAction
}
object SaveWalletWasShown : SaveWalletAction
}

View file

@ -0,0 +1,124 @@
package com.tangem.tap.features.saveWallet.redux
import com.tangem.common.CompletionResult
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.common.flatMap
import com.tangem.common.map
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.model.UserWallet
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import com.tangem.tap.userWalletsListManager
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
internal class SaveWalletMiddleware {
val middleware: Middleware<AppState> = { _, stateProvider ->
{ next ->
{ action ->
val state = stateProvider()
if (action is SaveWalletAction && state != null) {
handleAction(action, state.saveWalletState)
}
next(action)
}
}
}
private fun handleAction(action: SaveWalletAction, state: SaveWalletState) {
when (action) {
is SaveWalletAction.Save -> saveWalletIfBiometricsEnrolled(state)
is SaveWalletAction.Save.Success -> popBack()
is SaveWalletAction.EnrollBiometrics.Enroll -> enrollBiometrics()
is SaveWalletAction.SaveWalletWasShown -> saveWalletWasShown()
is SaveWalletAction.ProvideAdditionalInfo,
is SaveWalletAction.Dismiss,
is SaveWalletAction.CloseError,
is SaveWalletAction.Save.Error,
is SaveWalletAction.EnrollBiometrics,
is SaveWalletAction.EnrollBiometrics.Cancel,
-> Unit
}
}
private fun enrollBiometrics() {
store.dispatchOnMain(NavigationAction.OpenBiometricsSettings)
}
private fun saveWalletIfBiometricsEnrolled(state: SaveWalletState) {
if (tangemSdkManager.canEnrollBiometrics) {
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics)
} else {
saveWallet(state)
}
}
private fun saveWallet(state: SaveWalletState) {
val scanResponse = state.additionalInfo?.scanResponse
?: store.state.globalState.scanResponse
?: return
scope.launch {
val userWallet = UserWallet(
scanResponse = scanResponse,
backupCardsIds = state.additionalInfo?.backupCardsIds,
)
userWalletsListManager.save(userWallet)
.flatMap {
trySaveAccessCode(
userWallet = userWallet,
additionalInfo = state.additionalInfo,
)
}
.doOnFailure { error ->
store.dispatchOnMain(SaveWalletAction.Save.Error(error))
}
.doOnSuccess {
preferencesStorage.shouldSaveUserWallets = true
preferencesStorage.shouldSaveAccessCodes = true
store.dispatchOnMain(SaveWalletAction.Save.Success)
store.onUserWalletSelected(userWallet)
}
}
}
private fun popBack() {
store.dispatchOnMain(NavigationAction.PopBackTo())
}
private fun saveWalletWasShown() {
preferencesStorage.shouldShowSaveWallet = false
}
private suspend fun trySaveAccessCode(
userWallet: UserWallet,
additionalInfo: SaveWalletState.WalletAdditionalInfo?,
): CompletionResult<Unit> {
return when {
additionalInfo?.accessCode != null -> {
tangemSdkManager.saveAccessCode(
accessCode = additionalInfo.accessCode,
cardsIds = userWallet.cardsInWallet,
)
}
userWallet.hasAccessCode -> {
tangemSdkManager
.scanCard(
cardId = userWallet.cardId,
useBiometricsForAccessCode = true,
)
.map { /* no-op */ }
}
else -> {
CompletionResult.Success(Unit)
}
}
}
}

View file

@ -0,0 +1,51 @@
package com.tangem.tap.features.saveWallet.redux
import com.tangem.tap.common.redux.AppState
import org.rekotlin.Action
internal object SaveWalletReducer {
fun reduce(action: Action, state: AppState): SaveWalletState {
return if (action is SaveWalletAction) {
internalReduce(action, state.saveWalletState)
} else state.saveWalletState
}
private fun internalReduce(action: SaveWalletAction, state: SaveWalletState): SaveWalletState {
return when (action) {
is SaveWalletAction.ProvideAdditionalInfo -> state.copy(
additionalInfo = SaveWalletState.WalletAdditionalInfo(
scanResponse = action.scanResponse,
accessCode = action.accessCode,
backupCardsIds = action.backupCardsIds,
),
)
is SaveWalletAction.Save -> state.copy(
isSaveInProgress = true,
)
is SaveWalletAction.Save.Error -> state.copy(
error = action.error,
isSaveInProgress = false,
)
is SaveWalletAction.Save.Success -> state.copy(
additionalInfo = null,
isSaveInProgress = false,
)
is SaveWalletAction.Dismiss -> state.copy(
additionalInfo = null,
)
is SaveWalletAction.CloseError -> state.copy(
error = null,
)
is SaveWalletAction.EnrollBiometrics -> state.copy(
needEnrollBiometrics = true,
)
is SaveWalletAction.EnrollBiometrics.Enroll,
is SaveWalletAction.EnrollBiometrics.Cancel,
-> state.copy(
needEnrollBiometrics = false,
isSaveInProgress = false,
)
is SaveWalletAction.SaveWalletWasShown -> state
}
}
}

View file

@ -1,5 +1,18 @@
package com.tangem.tap.features.saveWallet.redux
import com.tangem.common.core.TangemError
import com.tangem.domain.common.ScanResponse
import org.rekotlin.StateType
class SaveWalletState : StateType
data class SaveWalletState(
val additionalInfo: WalletAdditionalInfo? = null,
val isSaveInProgress: Boolean = false,
val needEnrollBiometrics: Boolean = false,
val error: TangemError? = null,
) : StateType {
data class WalletAdditionalInfo(
val scanResponse: ScanResponse,
val accessCode: String?,
val backupCardsIds: Set<String>?,
)
}

View file

@ -20,6 +20,7 @@ import com.tangem.core.ui.components.dialogs.EnrollBiometricsDialogContent
import com.tangem.core.ui.fragments.ComposeBottomSheetFragment
import com.tangem.core.ui.models.EnrollBiometricsDialog
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.cardsettings.resolveReference
import com.tangem.tap.features.saveWallet.ui.components.SaveWalletScreenContent
internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWalletScreenState>() {
@ -43,7 +44,7 @@ internal class SaveWalletBottomSheetFragment : ComposeBottomSheetFragment<SaveWa
state: SaveWalletScreenState,
) {
val snackbarHostState = remember { SnackbarHostState() }
val errorMessage by rememberUpdatedState(newValue = state.error?.customMessage)
val errorMessage by rememberUpdatedState(newValue = state.error?.resolveReference())
val enrollBiometricsDialog by rememberUpdatedState(newValue = state.enrollBiometricsDialog)
Box(modifier = modifier) {

View file

@ -1,12 +1,12 @@
package com.tangem.tap.features.saveWallet.ui
import androidx.compose.runtime.Immutable
import com.tangem.common.core.TangemError
import com.tangem.core.ui.models.EnrollBiometricsDialog
import com.tangem.tap.features.details.ui.cardsettings.TextReference
@Immutable
internal data class SaveWalletScreenState(
val showProgress: Boolean = false,
val enrollBiometricsDialog: EnrollBiometricsDialog? = null,
val error: TangemError? = null,
val error: TextReference? = null,
)

View file

@ -1,7 +1,9 @@
package com.tangem.tap.features.saveWallet.ui
import androidx.lifecycle.ViewModel
import com.tangem.core.ui.models.EnrollBiometricsDialog
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.features.details.ui.cardsettings.TextReference
import com.tangem.tap.features.saveWallet.redux.SaveWalletAction
import com.tangem.tap.features.saveWallet.redux.SaveWalletState
import com.tangem.tap.store
@ -33,8 +35,16 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber<SaveWalletStat
override fun newState(state: SaveWalletState) {
stateInternal.update { prevState ->
// TODO: Update screen state
prevState
prevState.copy(
showProgress = state.isSaveInProgress,
enrollBiometricsDialog = if (state.needEnrollBiometrics) createEnrollBiometricsDialog() else null,
error = state.error
?.takeUnless { it.silent }
?.let { error ->
error.messageResId?.let { TextReference.Res(it) }
?: TextReference.Str(error.customMessage)
},
)
}
}
@ -48,4 +58,13 @@ internal class SaveWalletViewModel : ViewModel(), StoreSubscriber<SaveWalletStat
.select { it.saveWalletState }
}
}
private fun createEnrollBiometricsDialog() = EnrollBiometricsDialog(
onEnroll = {
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics.Enroll)
},
onCancel = {
store.dispatchOnMain(SaveWalletAction.EnrollBiometrics.Cancel)
},
)
}