Updated on 2026-08-14

This commit is contained in:
Tangem 2023-10-08 21:10:03 +03:00
parent a730af0f1f
commit 98915e2507
21 changed files with 448 additions and 45 deletions

View file

@ -27,6 +27,7 @@ internal sealed class WalletMultiCurrencyState : WalletState.ContentState() {
override val tokensListState: WalletTokensListState,
override val event: StateEvent<WalletEvent> = consumedEvent(),
override val isBalanceHidden: Boolean,
val isManageTokensAvailable: Boolean = true,
val tokenActionsBottomSheet: ActionsBottomSheetConfig?,
val onManageTokensClick: () -> Unit,
) : WalletMultiCurrencyState()

View file

@ -0,0 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.state.factory
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.models.UserWallet
data class TokenListWithWallet(
val tokenList: TokenList,
val wallet: UserWallet,
)

View file

@ -31,7 +31,7 @@ internal class WalletLoadedTokensListConverter(
appCurrencyProvider: Provider<AppCurrency>,
currentWalletProvider: Provider<UserWallet>,
clickIntents: WalletClickIntents,
) : Converter<Either<TokenListError, TokenList>, WalletState> {
) : Converter<Either<TokenListError, TokenListWithWallet>, WalletState> {
private val tokenListStateConverter = TokenListToWalletStateConverter(
currentStateProvider = currentStateProvider,
@ -40,7 +40,7 @@ internal class WalletLoadedTokensListConverter(
clickIntents = clickIntents,
)
override fun convert(value: Either<TokenListError, TokenList>): WalletState {
override fun convert(value: Either<TokenListError, TokenListWithWallet>): WalletState {
return value.fold(
ifLeft = tokenListErrorConverter::convert,
ifRight = tokenListStateConverter::convert,

View file

@ -38,7 +38,9 @@ internal class WalletSkeletonStateConverter(
override fun convert(value: SkeletonModel): WalletState.ContentState {
val selectedWallet = value.wallets[value.selectedWalletIndex]
return if (selectedWallet.isMultiCurrency) {
val isSingleWalletWithToken = !selectedWallet.isMultiCurrency &&
selectedWallet.scanResponse.walletData?.token != null
return if (selectedWallet.isMultiCurrency || isSingleWalletWithToken) {
createMultiCurrencyState(value = value)
} else {
createSingleCurrencyState(value = value, currencyName = selectedWallet.getPrimaryCurrencyName())

View file

@ -13,7 +13,6 @@ import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
@ -160,8 +159,8 @@ internal class WalletStateFactory(
)
}
fun getStateByTokensList(maybeTokenList: Either<TokenListError, TokenList>): WalletState {
return loadedTokensListConverter.convert(maybeTokenList)
fun getStateByTokensList(maybeTokenListWithWallet: Either<TokenListError, TokenListWithWallet>): WalletState {
return loadedTokensListConverter.convert(maybeTokenListWithWallet)
}
fun getStateByTokenListError(error: TokenListError): WalletState {

View file

@ -217,7 +217,7 @@ private fun BaseScaffold(
topBar = { WalletTopBar(config = state.topBarConfig) },
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
floatingActionButton = {
if (state is WalletMultiCurrencyState.Content) {
if (state is WalletMultiCurrencyState.Content && state.isManageTokensAvailable) {
ManageTokensButton(onManageTokensClick = state.onManageTokensClick)
}
},

View file

@ -9,6 +9,7 @@ import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.OrganizeTokensButtonState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState
import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
@ -18,23 +19,25 @@ import kotlinx.collections.immutable.persistentListOf
internal class TokenListToContentItemsConverter(
appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: WalletClickIntents,
) : Converter<TokenList, WalletTokensListState> {
) : Converter<TokenListWithWallet, WalletTokensListState> {
private val tokenStatusConverter = CryptoCurrencyStatusToTokenItemConverter(
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)
override fun convert(value: TokenList): WalletTokensListState {
return when (value) {
override fun convert(value: TokenListWithWallet): WalletTokensListState {
val isSingleCurrencyWalletWithToken = !value.wallet.isMultiCurrency &&
value.wallet.scanResponse.walletData?.token != null
return when (val tokenList = value.tokenList) {
is TokenList.Empty -> WalletTokensListState.Empty
is TokenList.GroupedByNetwork -> WalletTokensListState.Content(
items = value.mapToMultiCurrencyItems(),
organizeTokensButton = value.mapToOrganizeTokensButtonState(),
items = tokenList.mapToMultiCurrencyItems(),
organizeTokensButton = tokenList.mapToOrganizeTokensButtonState(isSingleCurrencyWalletWithToken),
)
is TokenList.Ungrouped -> WalletTokensListState.Content(
items = value.mapToMultiCurrencyItems(),
organizeTokensButton = value.mapToOrganizeTokensButtonState(),
items = tokenList.mapToMultiCurrencyItems(),
organizeTokensButton = tokenList.mapToOrganizeTokensButtonState(isSingleCurrencyWalletWithToken),
)
}
}
@ -51,17 +54,23 @@ internal class TokenListToContentItemsConverter(
}
}
private fun TokenList.GroupedByNetwork.mapToOrganizeTokensButtonState(): OrganizeTokensButtonState {
private fun TokenList.GroupedByNetwork.mapToOrganizeTokensButtonState(
isSingleCurrencyWithTokenWallet: Boolean,
): OrganizeTokensButtonState {
return getOrganizeTokensButtonState(
isLoading = totalFiatBalance is TokenList.FiatBalance.Loading,
currenciesSize = groups.flatMap(NetworkGroup::currencies).size,
isSingleCurrencyWithTokenWallet = isSingleCurrencyWithTokenWallet,
)
}
private fun TokenList.Ungrouped.mapToOrganizeTokensButtonState(): OrganizeTokensButtonState {
private fun TokenList.Ungrouped.mapToOrganizeTokensButtonState(
isSingleCurrencyWithTokenWallet: Boolean,
): OrganizeTokensButtonState {
return getOrganizeTokensButtonState(
isLoading = totalFiatBalance is TokenList.FiatBalance.Loading,
currenciesSize = currencies.size,
isSingleCurrencyWithTokenWallet = isSingleCurrencyWithTokenWallet,
)
}
@ -88,8 +97,12 @@ internal class TokenListToContentItemsConverter(
return this
}
private fun getOrganizeTokensButtonState(isLoading: Boolean, currenciesSize: Int): OrganizeTokensButtonState {
return if (currenciesSize > 1) {
private fun getOrganizeTokensButtonState(
isLoading: Boolean,
currenciesSize: Int,
isSingleCurrencyWithTokenWallet: Boolean,
): OrganizeTokensButtonState {
return if (currenciesSize > 1 && !isSingleCurrencyWithTokenWallet) {
OrganizeTokensButtonState.Visible(
isEnabled = !isLoading,
onClick = clickIntents::onOrganizeTokensClick,

View file

@ -8,6 +8,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencySt
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.state.factory.TokenListWithWallet
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
@ -18,19 +19,23 @@ internal class TokenListToWalletStateConverter(
private val currentWalletProvider: Provider<UserWallet>,
private val appCurrencyProvider: Provider<AppCurrency>,
clickIntents: WalletClickIntents,
) : Converter<TokenList, WalletState> {
) : Converter<TokenListWithWallet, WalletState> {
private val tokenListToContentConverter = TokenListToContentItemsConverter(
appCurrencyProvider = appCurrencyProvider,
clickIntents = clickIntents,
)
override fun convert(value: TokenList): WalletState {
override fun convert(value: TokenListWithWallet): WalletState {
val tokenList = value.tokenList
val isSingleCurrencyWalletWithToken = !value.wallet.isMultiCurrency &&
value.wallet.scanResponse.walletData?.token != null
return when (val state = currentStateProvider()) {
is WalletMultiCurrencyState.Content -> {
state.copy(
walletsListConfig = state.updateSelectedWallet(fiatBalance = value.totalFiatBalance),
walletsListConfig = state.updateSelectedWallet(fiatBalance = tokenList.totalFiatBalance),
tokensListState = tokenListToContentConverter.convert(value = value),
isManageTokensAvailable = !isSingleCurrencyWalletWithToken,
)
}
is WalletMultiCurrencyState.Locked,

View file

@ -62,6 +62,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.PortfolioEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
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.operations.derivation.ExtendedPublicKeysMap
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -92,6 +93,7 @@ internal class WalletViewModel @Inject constructor(
private val updateWalletUseCase: UpdateWalletUseCase,
private val deleteWalletUseCase: DeleteWalletUseCase,
private val getTokenListUseCase: GetTokenListUseCase,
private val getCardTokensListUseCase: GetCardTokensListUseCase,
private val fetchTokenListUseCase: FetchTokenListUseCase,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
@ -592,10 +594,14 @@ internal class WalletViewModel @Inject constructor(
viewModelScope.launch(dispatchers.io) {
val userWallet = getWallet(index = state.walletsListConfig.selectedWalletIndex)
val isSingleWalletWithTokens = !userWallet.isMultiCurrency &&
userWallet.scanResponse.walletData?.token != null
getNetworkCoinStatusUseCase(
userWalletId = userWallet.walletId,
networkId = cryptoCurrencyStatus.currency.network.id,
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
isSingleWalletWithTokens = isSingleWalletWithTokens,
)
.take(count = 1)
.collectLatest {
@ -916,6 +922,9 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getLockedState()
}
wallet.isMultiCurrency -> getMultiCurrencyContent(wallet, index)
isSingleWalletWithTokens(wallet) -> {
getSingleCurrencyWithTokenContent(index)
}
!wallet.isMultiCurrency -> getSingleCurrencyContent(index)
}
}
@ -933,7 +942,7 @@ internal class WalletViewModel @Inject constructor(
tokenListFlow
.distinctUntilChanged()
.onEach { maybeTokenList ->
uiState = stateFactory.getStateByTokensList(maybeTokenList)
uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet))
maybeTokenList.onRight { checkMultiWalletWithFunds(it) }
@ -978,6 +987,10 @@ internal class WalletViewModel @Inject constructor(
.saveIn(updateWcJobHolder)
}
private fun isSingleWalletWithTokens(userWallet: UserWallet): Boolean {
return userWallet.scanResponse.walletData?.token != null && !userWallet.isMultiCurrency
}
private fun List<CryptoCurrencyStatus>.isAllCurrenciesLoaded(): Boolean {
return !this.any { it.value is CryptoCurrencyStatus.Loading }
}
@ -1008,6 +1021,14 @@ internal class WalletViewModel @Inject constructor(
}
}
private fun Either<TokenListError, TokenList>.getTokenListWithWallet(
userWallet: UserWallet,
): Either<TokenListError, TokenListWithWallet> {
return this.map {
TokenListWithWallet(it, userWallet)
}
}
private fun getSingleCurrencyContent(index: Int) {
val wallet = getWallet(index)
getPrimaryCurrencyStatusUpdatesUseCase(wallet.walletId)
@ -1032,6 +1053,30 @@ internal class WalletViewModel @Inject constructor(
.saveIn(marketPriceJobHolder)
}
private fun getSingleCurrencyWithTokenContent(walletIndex: Int) {
val state = requireNotNull(uiState as? WalletMultiCurrencyState) {
"Impossible to get a token list updates if state isn't WalletMultiCurrencyState"
}
val wallet = getWallet(walletIndex)
getCardTokensListUseCase(userWalletId = state.walletsListConfig.wallets[walletIndex].id)
.distinctUntilChanged()
.onEach { maybeTokenList ->
uiState = stateFactory.getStateByTokensList(maybeTokenList.getTokenListWithWallet(wallet))
maybeTokenList.onRight { checkMultiWalletWithFunds(it) }
updateNotifications(
index = walletIndex,
tokenList = maybeTokenList.fold(ifLeft = { null }, ifRight = { it }),
)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
.saveIn(tokensJobHolder)
}
private fun updateTxHistory(userWalletId: UserWalletId, currency: CryptoCurrency, refresh: Boolean) {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
@ -1095,10 +1140,15 @@ internal class WalletViewModel @Inject constructor(
val wallet = getWallet(walletIndex)
viewModelScope.launch(dispatchers.io) {
val result = fetchTokenListUseCase(wallet.walletId, refresh = true)
if (isSingleWalletWithTokens(wallet)) {
// TODO add refresh for nodl cards ([REDACTED_JIRA])
delay(timeMillis = 1000)
} else {
val result = fetchTokenListUseCase(wallet.walletId, refresh = true)
uiState = result.fold(stateFactory::getStateByTokenListError) { uiState }
}
uiState = stateFactory.getRefreshedState()
uiState = result.fold(stateFactory::getStateByTokenListError) { uiState }
}.saveIn(refreshContentJobHolder)
}