Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-27 15:56:35 +08:00
parent f89ba800e9
commit 236b977e46
10 changed files with 670 additions and 18 deletions

View file

@ -20,6 +20,8 @@ import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.TokenWithBlockchain
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.walletconnect.WalletConnectActions
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.tap.*
@ -90,13 +92,17 @@ object TokensMiddleware {
if (scanResponse.supportsHdWallet()) {
deriveMissingCoins(scanResponse = scanResponse, currencyList = currencyList) {
submitNewAdd(
userWalletId = action.userWallet.walletId,
userWallet = action.userWallet,
updatedScanResponse = it,
currencyList = currencyList,
)
}
} else {
submitNewAdd(userWalletId = action.userWallet.walletId, scanResponse, currencyList = currencyList)
submitNewAdd(
userWallet = action.userWallet,
updatedScanResponse = scanResponse,
currencyList = currencyList,
)
}
}
}
@ -375,16 +381,18 @@ object TokensMiddleware {
}
private fun submitNewAdd(
userWalletId: UserWalletId,
userWallet: UserWallet,
updatedScanResponse: ScanResponse,
currencyList: List<CryptoCurrency>,
) {
scope.launch {
userWalletsListManager.update(
userWalletId = userWalletId,
userWalletId = userWallet.walletId,
update = { it.copy(scanResponse = updatedScanResponse) },
).doOnSuccess {
addCryptoCurrenciesUseCase(userWalletId, currencyList)
addCryptoCurrenciesUseCase(userWallet.walletId, currencyList).onRight {
store.dispatch(action = WalletConnectActions.New.SetupUserChains(userWallet = userWallet))
}
}
}
store.dispatchOnMain(NavigationAction.PopBackTo())

View file

@ -34,5 +34,9 @@
{
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
"version": "undefined"
},
{
"name": "WALLETS_SCROLLING_PREVIEW_ENABLED",
"version": "5.4.0"
}
]

View file

@ -3,6 +3,7 @@ package com.tangem.core.ui.event
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.NonRestartableComposable
import kotlinx.coroutines.launch
/**
* A Composable function that reacts to a given [StateEvent], executing the provided action only once when the event
@ -17,8 +18,8 @@ import androidx.compose.runtime.NonRestartableComposable
fun <A> EventEffect(event: StateEvent<A>, onTrigger: suspend (data: A) -> Unit) {
LaunchedEffect(event) {
if (event is StateEvent.Triggered<A>) {
onTrigger(event.data)
event.onConsume()
launch { onTrigger(event.data) }
.invokeOnCompletion { event.onConsume() }
}
}
}

View file

@ -9,4 +9,6 @@ interface WalletFeatureToggles {
/** Availability of redesigned screen */
val isRedesignedScreenEnabled: Boolean
val isWalletsScrollingPreviewEnabled: Boolean
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.di
import com.tangem.core.navigation.ReduxNavController
import com.tangem.feature.wallet.presentation.router.DefaultWalletRouter
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.features.wallet.navigation.WalletRouter
import dagger.Module
import dagger.Provides
@ -15,7 +16,10 @@ internal object WalletRouterModule {
@Provides
@ActivityScoped
fun provideWalletRouter(reduxNavController: ReduxNavController): WalletRouter {
return DefaultWalletRouter(reduxNavController = reduxNavController)
fun provideWalletRouter(
reduxNavController: ReduxNavController,
walletFeatureToggles: WalletFeatureToggles,
): WalletRouter {
return DefaultWalletRouter(reduxNavController = reduxNavController, walletFeatureToggles = walletFeatureToggles)
}
}

View file

@ -16,4 +16,7 @@ internal class DefaultWalletFeatureToggles(
override val isRedesignedScreenEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "REDESIGNED_WALLET_SCREEN_ENABLED")
override val isWalletsScrollingPreviewEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "WALLETS_SCROLLING_PREVIEW_ENABLED")
}

View file

@ -26,12 +26,18 @@ import com.tangem.feature.wallet.presentation.WalletFragment
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreenV2
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModelV2
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */
internal class DefaultWalletRouter(private val reduxNavController: ReduxNavController) : InnerWalletRouter {
internal class DefaultWalletRouter(
private val reduxNavController: ReduxNavController,
private val walletFeatureToggles: WalletFeatureToggles,
) : InnerWalletRouter {
private var navController: NavHostController by Delegates.notNull()
private var onFinish: () -> Unit = {}
@ -47,10 +53,19 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
startDestination = WalletRoute.Wallet.route,
) {
composable(WalletRoute.Wallet.route) {
val viewModel = hiltViewModel<WalletViewModel>().apply { router = this@DefaultWalletRouter }
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
if (walletFeatureToggles.isWalletsScrollingPreviewEnabled) {
val viewModel = hiltViewModel<WalletViewModel>().apply {
router = this@DefaultWalletRouter
}
WalletScreen(state = viewModel.uiState)
WalletScreen(state = viewModel.uiState)
} else {
val viewModel = hiltViewModel<WalletViewModelV2>().apply {
setWalletRouter(router = this@DefaultWalletRouter)
}
WalletScreenV2(state = viewModel.uiState.collectAsStateWithLifecycle().value)
}
}
composable(

View file

@ -68,6 +68,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.*
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletStateFactory
import com.tangem.feature.wallet.presentation.wallet.subscribers.MaybeTokenListFlow
import com.tangem.operations.derivation.ExtendedPublicKeysMap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
@ -129,10 +130,10 @@ internal class WalletViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler,
hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
isNeedToBackupUseCase: IsNeedToBackupUseCase,
getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
// endregion Parameters
) : ViewModel(), DefaultLifecycleObserver, WalletClickIntents {
@ -1352,7 +1353,7 @@ internal class WalletViewModel @Inject constructor(
}
}
private suspend fun updateButtons(userWallet: UserWallet, currencyStatus: CryptoCurrencyStatus) {
private fun updateButtons(userWallet: UserWallet, currencyStatus: CryptoCurrencyStatus) {
getCryptoCurrencyActionsUseCase(
userWallet = userWallet,
cryptoCurrencyStatus = currencyStatus,
@ -1416,6 +1417,4 @@ internal class WalletViewModel @Inject constructor(
}
private fun getCardTypeResolver(index: Int): CardTypesResolver = getWallet(index).scanResponse.cardTypesResolver
}
typealias MaybeTokenListFlow = Flow<Either<TokenListError, TokenList>>
}

View file

@ -0,0 +1,311 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.CanUseBiometryUseCase
import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled
import com.tangem.domain.settings.ShouldShowSaveWalletScreenUseCase
import com.tangem.domain.walletconnect.WalletConnectActions
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent.DemonstrateWalletsScrollPreview.Direction
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateHolderV2
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.*
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList")
@HiltViewModel
internal class WalletViewModelV2 @Inject constructor(
private val stateHolder: WalletStateHolderV2,
private val clickIntents: WalletClickIntentsV2,
private val walletEventSender: WalletEventSender,
private val walletsUpdateActionResolver: WalletsUpdateActionResolverV2,
private val walletScreenContentLoader: WalletScreenContentLoader,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val shouldShowSaveWalletScreenUseCase: ShouldShowSaveWalletScreenUseCase,
private val canUseBiometryUseCase: CanUseBiometryUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
analyticsEventsHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: ReduxStateHolder,
) : ViewModel() {
val uiState: StateFlow<WalletScreenState> = stateHolder.uiState
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
private var router: InnerWalletRouter by Delegates.notNull()
private var walletsUpdateJobHolder: JobHolder = JobHolder()
init {
analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened)
suggestToEnableBiometrics()
subscribeOnWalletsUpdateFlow()
subscribeOnBalanceHiding()
subscribeOnSelectedWalletFlow()
}
fun setWalletRouter(router: InnerWalletRouter) {
this.router = router
clickIntents.initialize(router, viewModelScope)
}
private fun suggestToEnableBiometrics() {
viewModelScope.launch(dispatchers.main) {
withContext(dispatchers.io) { delay(timeMillis = 1_800) }
if (isShowSaveWalletScreenEnabled()) router.openSaveUserWalletScreen()
}
}
private suspend fun isShowSaveWalletScreenEnabled(): Boolean {
return router.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase()
}
private fun subscribeOnWalletsUpdateFlow() {
viewModelScope.launch(dispatchers.main) {
shouldSaveUserWalletsUseCase()
.conflate()
.distinctUntilChanged()
.collectLatest { shouldSaveUserWallet ->
getWalletsUseCase()
.distinctUntilChanged()
.conflate()
.map {
walletsUpdateActionResolver.resolve(
wallets = it,
currentState = stateHolder.value,
canSaveWallets = shouldSaveUserWallet,
)
}
.onEach(::updateWallets)
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
.saveIn(walletsUpdateJobHolder)
}
}
}
private fun subscribeOnBalanceHiding() {
getBalanceHidingSettingsUseCase()
.conflate()
.distinctUntilChanged()
.onEach {
stateHolder.update(transformer = UpdateBalanceHidingModeTransformer(it.isBalanceHidden))
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
}
private fun subscribeOnSelectedWalletFlow() {
getSelectedWalletUseCase().onRight {
it
.conflate()
.distinctUntilChanged()
.onEach { selectedWallet ->
if (selectedWallet.isMultiCurrency) {
reduxStateHolder.dispatch(
action = WalletConnectActions.New.Initialize(userWallet = selectedWallet),
)
}
}
.flowOn(dispatchers.main)
.launchIn(viewModelScope)
}
}
private fun updateWallets(action: WalletsUpdateActionResolverV2.Action) {
when (action) {
is WalletsUpdateActionResolverV2.Action.InitializeWallets -> initializeWallets(action)
is WalletsUpdateActionResolverV2.Action.ReinitializeWallets -> {
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
isRefresh = true,
)
}
is WalletsUpdateActionResolverV2.Action.ReinitializeWallet -> reinitializeWallet(action)
is WalletsUpdateActionResolverV2.Action.AddWallet -> addWallet(action)
is WalletsUpdateActionResolverV2.Action.DeleteWallet -> deleteWallet(action)
is WalletsUpdateActionResolverV2.Action.UnlockWallet -> unlockWallet(action)
is WalletsUpdateActionResolverV2.Action.UpdateWalletCardCount -> {
stateHolder.update(transformer = UpdateWalletCardsCountTransformer(action.selectedWallet))
}
is WalletsUpdateActionResolverV2.Action.UpdateWalletName -> {
stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name))
}
is WalletsUpdateActionResolverV2.Action.Unknown -> Unit
}
}
private fun initializeWallets(action: WalletsUpdateActionResolverV2.Action.InitializeWallets) {
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
)
stateHolder.update(
transformer = InitializeWalletsTransformer(
selectedWalletIndex = action.selectedWalletIndex,
selectedWallet = action.selectedWallet,
wallets = action.wallets,
clickIntents = clickIntents,
),
)
viewModelScope.launch(dispatchers.main) {
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
withContext(dispatchers.io) {
delay(timeMillis = 1_800)
}
walletEventSender.send(
event = WalletEvent.DemonstrateWalletsScrollPreview(
direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
Direction.RIGHT
} else {
Direction.LEFT
},
),
)
}
}
}
private fun reinitializeWallet(action: WalletsUpdateActionResolverV2.Action.ReinitializeWallet) {
viewModelScope.launch(dispatchers.main) {
walletScreenContentLoader.cancel(action.prevWalletId)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
)
stateHolder.update(
ReinitializeWalletTransformer(userWallet = action.selectedWallet, clickIntents = clickIntents),
)
}
}
private fun addWallet(action: WalletsUpdateActionResolverV2.Action.AddWallet) {
viewModelScope.launch(dispatchers.main) {
stateHolder.update(
AddWalletTransformer(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
),
)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
)
withContext(dispatchers.io) { delay(timeMillis = 700) }
scrollToWallet(index = action.selectedWalletIndex)
}
}
private fun deleteWallet(action: WalletsUpdateActionResolverV2.Action.DeleteWallet) {
viewModelScope.launch(dispatchers.main) {
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
)
scrollToWallet(index = action.selectedWalletIndex)
withContext(dispatchers.io) { delay(timeMillis = 700) }
stateHolder.update(
DeleteWalletTransformer(
selectedWalletIndex = action.selectedWalletIndex,
deletedWalletId = action.deletedWalletId,
),
)
}
}
private fun unlockWallet(action: WalletsUpdateActionResolverV2.Action.UnlockWallet) {
viewModelScope.launch(dispatchers.main) {
withContext(dispatchers.io) { delay(timeMillis = 700) }
stateHolder.update(
transformer = UnlockWalletTransformer(
unlockedWallets = action.unlockedWallets,
clickIntents = clickIntents,
),
)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
appCurrency = selectedAppCurrencyFlow.value,
clickIntents = clickIntents,
coroutineScope = viewModelScope,
)
}
}
private fun scrollToWallet(index: Int) {
stateHolder.update(
ScrollToWalletTransformer(
index = index,
currentStateProvider = Provider(action = stateHolder::value),
stateUpdater = { newState -> stateHolder.update { newState } },
),
)
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
}

View file

@ -0,0 +1,305 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state2.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state2.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state2.WalletState
import dagger.hilt.android.scopes.ViewModelScoped
import timber.log.Timber
import javax.inject.Inject
/**
* Resolver that determines which update action will be performed
*
* @property getSelectedWalletSyncUseCase use case that returns selected wallet
*/
@ViewModelScoped
internal class WalletsUpdateActionResolverV2 @Inject constructor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
) {
private var isInitialized: Boolean = false
private var canSaveWallets: Boolean = false
fun resolve(wallets: List<UserWallet>, currentState: WalletScreenState, canSaveWallets: Boolean): Action {
val selectedWallet = wallets.getSelectedWallet() ?: return Action.Unknown
val action = when {
isFirstInitialization(currentState) -> {
createInitializeWalletsAction(wallets, selectedWallet, canSaveWallets)
}
isReinitialization(canSaveWallets) -> {
this.canSaveWallets = canSaveWallets
Action.ReinitializeWallets(selectedWallet = selectedWallet)
}
else -> getUpdateContentAction(currentState, wallets, selectedWallet)
}
Timber.d("Resolved action: $action")
return action
}
private fun List<UserWallet>.getSelectedWallet(): UserWallet? {
return when {
isEmpty() -> null
size == 1 -> first()
else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it })
}
}
private fun isFirstInitialization(state: WalletScreenState): Boolean {
return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX
}
private fun createInitializeWalletsAction(
wallets: List<UserWallet>,
selectedWallet: UserWallet,
canSaveWallets: Boolean,
): Action {
this.isInitialized = true
this.canSaveWallets = canSaveWallets
return Action.InitializeWallets(
selectedWalletIndex = wallets.indexOfWallet(selectedWallet.walletId),
selectedWallet = selectedWallet,
wallets = wallets,
)
}
private fun isReinitialization(canSaveWallets: Boolean): Boolean {
return isInitialized && this.canSaveWallets != canSaveWallets
}
private fun getUpdateContentAction(
state: WalletScreenState,
wallets: List<UserWallet>,
selectedWallet: UserWallet,
): Action {
return when {
isWalletsCountChanged(state, wallets) -> {
getChangeWalletsListAction(state, wallets, selectedWallet)
}
isAnotherWalletSelected(state, selectedWallet) -> {
Action.ReinitializeWallet(
prevWalletId = state.getPrevSelectedWallet().id,
selectedWallet = selectedWallet,
)
}
else -> getUpdateSelectedWalletAction(state, wallets, selectedWallet)
}
}
private fun isWalletsCountChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
val prevWalletsSize = state.wallets.size
val walletsSize = wallets.size
return prevWalletsSize != walletsSize
}
private fun getChangeWalletsListAction(
state: WalletScreenState,
wallets: List<UserWallet>,
selectedWallet: UserWallet,
): Action {
val prevWalletsSize = state.wallets.size
return when {
prevWalletsSize > wallets.size -> {
Action.DeleteWallet(
selectedWallet = selectedWallet,
selectedWalletIndex = wallets.indexOfWallet(id = selectedWallet.walletId),
deletedWalletId = state.wallets.getDeletedWalletId(wallets),
)
}
prevWalletsSize < wallets.size -> {
val newUserWallet = state.wallets.getAddedWallet(wallets)
Action.AddWallet(
selectedWalletIndex = wallets.indexOfWallet(id = newUserWallet.walletId),
selectedWallet = newUserWallet,
)
}
else -> error("Wallets list is not changed")
}
}
private fun List<WalletState>.getDeletedWalletId(wallets: List<UserWallet>): UserWalletId {
return this
.map { it.walletCardState.id }
.firstOrNull { !wallets.map(UserWallet::walletId).contains(it) }
?: error("Deleted wallet id is not found. Wallets contains all previous wallets ids")
}
private fun List<WalletState>.getAddedWallet(wallets: List<UserWallet>): UserWallet {
return wallets
.firstOrNull { wallet -> !this.map { it.walletCardState.id }.contains(wallet.walletId) }
?: error("Added wallet id is not found. Wallets contains all previous wallets ids")
}
private fun isAnotherWalletSelected(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
return state.getPrevSelectedWallet().id != selectedWallet.walletId
}
private fun getUpdateSelectedWalletAction(
state: WalletScreenState,
wallets: List<UserWallet>,
selectedWallet: UserWallet,
): Action {
return when {
isSelectedWalletNameChanged(state, selectedWallet) -> {
Action.UpdateWalletName(selectedWalletId = selectedWallet.walletId, name = selectedWallet.name)
}
isSelectedWalletUnlocked(state, selectedWallet) -> {
Action.UnlockWallet(
selectedWallet = selectedWallet,
unlockedWallets = wallets.filterNot(UserWallet::isLocked),
)
}
isSelectedWalletCardsCountChanged(state, selectedWallet) -> Action.UpdateWalletCardCount(selectedWallet)
else -> Action.Unknown
}
}
private fun isSelectedWalletNameChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
return state.getPrevSelectedWallet().title != selectedWallet.name
}
private fun isSelectedWalletUnlocked(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
return state.isSelectedWalletLocked() && !selectedWallet.isLocked
}
private fun isSelectedWalletCardsCountChanged(state: WalletScreenState, selectedWallet: UserWallet): Boolean {
val prevSelectedWallet = state.getPrevSelectedWallet()
return prevSelectedWallet is WalletCardState.Content &&
prevSelectedWallet.cardCount != selectedWallet.getCardsCount()
}
private fun WalletScreenState.isSelectedWalletLocked(): Boolean {
val selectedWalletState = wallets.getOrNull(selectedWalletIndex) ?: error("Selected wallet is not found")
return selectedWalletState is WalletState.MultiCurrency.Locked ||
selectedWalletState is WalletState.SingleCurrency.Locked
}
private fun WalletScreenState.getPrevSelectedWallet(): WalletCardState {
return wallets
.map(WalletState::walletCardState)
.getOrNull(selectedWalletIndex)
?: error("Previous selected wallet is not found")
}
private fun List<UserWallet>.indexOfWallet(id: UserWalletId): Int {
val selectedIndex = indexOfFirst { it.walletId == id }
return if (selectedIndex == -1) {
error("Wallets don't contain a wallet with id: $id")
} else {
selectedIndex
}
}
sealed class Action {
data class InitializeWallets(
val selectedWalletIndex: Int,
val selectedWallet: UserWallet,
val wallets: List<UserWallet>,
) : Action() {
override fun toString(): String {
return """
Initialize(
selectedWalletIndex=$selectedWalletIndex,
selectedWallet=${selectedWallet.walletId},
wallets=${wallets.joinToString { it.walletId.toString() }}
)
""".trimIndent()
}
}
/**
* Reinitialize wallets. Example, if user turned on wallets saving
*
* @property selectedWallet selected wallet
*/
data class ReinitializeWallets(val selectedWallet: UserWallet) : Action() {
override fun toString(): String {
return "Reinitialize(selectedWallet=${selectedWallet.walletId})"
}
}
/**
* Reinitialize selected wallet. Example, scanning a new card if wallets saving is turned off
*
* @property prevWalletId previous selected wallet id
* @property selectedWallet selected wallet
*/
data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() {
override fun toString(): String {
return "ReinitializeWallet(prevWalletId=$prevWalletId, selectedWallet=${selectedWallet.walletId})"
}
}
data class UpdateWalletName(val selectedWalletId: UserWalletId, val name: String) : Action() {
override fun toString(): String {
return "UpdateWalletName(selectedWalletId=$selectedWalletId, name=$name)"
}
}
data class UnlockWallet(val selectedWallet: UserWallet, val unlockedWallets: List<UserWallet>) : Action() {
override fun toString(): String {
return """
UnlockWallet(
selectedWallet=${selectedWallet.walletId},
unlockedWallets=${unlockedWallets.joinToString { it.walletId.toString() }}
)
""".trimIndent()
}
}
data class DeleteWallet(
val selectedWallet: UserWallet,
val selectedWalletIndex: Int,
val deletedWalletId: UserWalletId,
) : Action() {
override fun toString(): String {
return """
DeleteWallet(
selectedWallet=${selectedWallet.walletId},
selectedWalletIndex=$selectedWalletIndex,
deletedWalletId=$deletedWalletId
)
""".trimIndent()
}
}
data class AddWallet(val selectedWalletIndex: Int, val selectedWallet: UserWallet) : Action() {
override fun toString(): String {
return "AddWallet(selectedWalletIndex=$selectedWalletIndex, selectedWallet=${selectedWallet.walletId})"
}
}
/**
* Update wallet card count. Example, if user backed up wallet
*
* @property selectedWallet selected wallet
*/
data class UpdateWalletCardCount(val selectedWallet: UserWallet) : Action() {
override fun toString(): String {
return "UpdateWalletCardCount(selectedWallet=${selectedWallet.walletId})"
}
}
object Unknown : Action()
}
}