Updated on 2026-08-14
This commit is contained in:
parent
f89ba800e9
commit
236b977e46
10 changed files with 670 additions and 18 deletions
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -34,5 +34,9 @@
|
|||
{
|
||||
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "WALLETS_SCROLLING_PREVIEW_ENABLED",
|
||||
"version": "5.4.0"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,4 +9,6 @@ interface WalletFeatureToggles {
|
|||
|
||||
/** Availability of redesigned screen */
|
||||
val isRedesignedScreenEnabled: Boolean
|
||||
|
||||
val isWalletsScrollingPreviewEnabled: Boolean
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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>>
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue