Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-15 14:25:11 +03:00
commit 5ee5b4f61b
563 changed files with 13917 additions and 4692 deletions

View file

@ -361,10 +361,10 @@ internal class WalletModel @Inject constructor(
val info = tangemPayMainScreenCustomerInfoUseCase()
if (info != null) {
stateHolder.update(
transformer = TangemPayStateTransformer(
transformer = TangemPayInitialStateTransformer(
value = info,
onIssueOrderClick = ::issueOrder,
onContinueKycClick = innerWalletRouter::openTangemPayOnboarding,
onClickIssue = ::issueOrder,
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
openDetails = innerWalletRouter::openTangemPayDetails,
),
)
@ -373,9 +373,9 @@ internal class WalletModel @Inject constructor(
private fun issueOrder() {
modelScope.launch {
stateHolder.update(TangemPayStateTransformer(issueProgressState = true))
stateHolder.update(TangemPayIssueProgressStateTransformer())
tangemPayIssueOrderUseCase().onLeft {
stateHolder.update(TangemPayStateTransformer(issueState = true, onIssueOrderClick = ::issueOrder))
stateHolder.update(TangemPayIssueAvailableStateTransformer(onClickIssue = ::issueOrder))
}
}
}

View file

@ -4,17 +4,15 @@ import arrow.core.getOrElse
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.nft.analytics.NFTAnalyticsEvent
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.TokensAction
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -39,15 +37,13 @@ internal interface WalletContentClickIntents {
fun onDetailsClick()
fun onManageTokensClick()
fun onOrganizeTokensClick()
fun onDismissMarketsOnboarding()
fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onAccountExpandClick(account: Account)
@ -81,7 +77,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val shouldShowMarketsTooltipUseCase: ShouldShowMarketsTooltipUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: ReduxStateHolder,
private val walletEventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
@ -122,11 +117,6 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
}
override fun onManageTokensClick() {
reduxStateHolder.dispatch(action = TokensAction.SetArgs.ManageAccess)
router.openManageTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
}
override fun onOrganizeTokensClick() {
router.openOrganizeTokensScreen(userWalletId = stateHolder.getSelectedWalletId())
}
@ -138,13 +128,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
}
override fun onTokenItemClick(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
router.openTokenDetails(portfolioId, currencyStatus)
override fun onTokenItemClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
router.openTokenDetails(userWalletId, currencyStatus)
}
override fun onTokenItemLongClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.main) {
val userWalletId = portfolioId.userWalletId
val userWallet = getUserWalletUseCase(userWalletId).getOrElse {
Timber.e(
"""
@ -160,7 +149,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
getCryptoCurrencyActionsUseCase(userWallet = userWallet, cryptoCurrencyStatus = cryptoCurrencyStatus)
.take(count = 1)
.collectLatest {
showActionsBottomSheet(it, userWallet, portfolioId)
showActionsBottomSheet(it, userWallet)
}
}
}
@ -175,17 +164,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
accountDependencies.expandedAccountsHolder.collapseAccount(userWalletId, account.accountId)
}
private fun showActionsBottomSheet(
tokenActionsState: TokenActionsState,
userWallet: UserWallet,
portfolioId: PortfolioId,
) {
private fun showActionsBottomSheet(tokenActionsState: TokenActionsState, userWallet: UserWallet) {
stateHolder.showBottomSheet(
ActionsBottomSheetConfig(
actions = MultiWalletCurrencyActionsConverter(
userWallet = userWallet,
clickIntents = currencyActionsClickIntents,
portfolioId = portfolioId,
).convert(tokenActionsState),
),
userWallet.walletId,

View file

@ -22,6 +22,9 @@ import com.tangem.core.ui.extensions.WrappedList
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.core.lce.Lce
@ -29,8 +32,6 @@ import com.tangem.domain.core.utils.lceError
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.ReceiveAddressModel
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -43,12 +44,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.GetViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.NeedShowYieldSupplyDepositedWarningUseCase
import com.tangem.domain.tokens.SaveViewedTokenReceiveWarningUseCase
import com.tangem.domain.tokens.SaveViewedYieldSupplyWarningUseCase
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent
@ -57,7 +53,8 @@ import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.AVAILABLE
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
import com.tangem.domain.transaction.usecase.GetEnsNameUseCase
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
@ -70,7 +67,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.feature.wallet.presentation.wallet.utils.WalletFeatureUseCasesFacade
import com.tangem.features.tokenreceive.TokenReceiveFeatureToggle
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -78,13 +74,14 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
interface WalletCurrencyActionsClickIntents {
fun onSendClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
)
@ -92,32 +89,32 @@ interface WalletCurrencyActionsClickIntents {
fun onSellClick(cryptoCurrencyStatus: CryptoCurrencyStatus, unavailabilityReason: ScenarioUnavailabilityReason)
fun onBuyClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
portfolioId: PortfolioId,
userWalletId: UserWalletId,
unavailabilityReason: ScenarioUnavailabilityReason,
)
fun onReceiveClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
event: AnalyticsEvent? = null,
)
fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
fun onExploreClick()
@ -139,7 +136,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val walletManagersFacade: WalletManagersFacade,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val useCasesFacade: WalletFeatureUseCasesFacade,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
@ -152,16 +148,20 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val tokenReceiveFeatureToggle: TokenReceiveFeatureToggle,
private val getViewedTokenReceiveWarningUseCase: GetViewedTokenReceiveWarningUseCase,
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
private val getEnsNameUseCase: GetEnsNameUseCase,
private val receiveAddressesFactory: ReceiveAddressesFactory,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
@ -185,27 +185,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch {
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
stateHolder.hideBottomSheet()
navigateToSend(cryptoCurrencyStatus, portfolioId)
navigateToSend(cryptoCurrencyStatus, userWalletId)
}
},
)
} else {
navigateToSend(cryptoCurrencyStatus, portfolioId)
navigateToSend(cryptoCurrencyStatus, userWalletId)
}
}
}
override fun onReceiveClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
event: AnalyticsEvent?,
) {
val userWalletId = portfolioId.userWalletId
if (portfolioId is PortfolioId.Account) {
// todo account find address
TODO("account")
}
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonWithParams.ButtonReceive(
token = cryptoCurrencyStatus.currency.symbol,
@ -278,12 +272,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
}
override fun onCopyAddressClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
val userWalletId = portfolioId.userWalletId
if (portfolioId is PortfolioId.Account) {
// todo account find address
TODO("account")
}
override fun onCopyAddressClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = TokenReceiveNewAnalyticsEvent.ButtonCopyAddress(
token = cryptoCurrencyStatus.currency.symbol,
@ -305,7 +294,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
override fun onHideTokensClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onHideTokensClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
analyticsEventHandler.send(
event = TokenScreenAnalyticsEvent.ButtonRemoveToken(cryptoCurrencyStatus.currency.symbol),
)
@ -313,19 +302,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch(dispatchers.main) {
walletEventSender.send(
event = WalletEvent.ShowAlert(
state = getHideTokeAlertConfig(portfolioId, cryptoCurrencyStatus),
state = getHideTokeAlertConfig(userWalletId, cryptoCurrencyStatus),
),
)
}
}
private suspend fun getHideTokeAlertConfig(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
): WalletAlertState.DefaultAlert {
val currency = cryptoCurrencyStatus.currency
val isCryptoCurrencyCoinCouldHide = currency is CryptoCurrency.Coin &&
!useCasesFacade.isCryptoCurrencyCoinCouldHide(portfolioId = portfolioId, cryptoCurrencyCoin = currency)
!isCryptoCurrencyCoinCouldHide(userWalletId = userWalletId, cryptoCurrencyCoin = currency)
return if (isCryptoCurrencyCoinCouldHide) {
WalletAlertState.DefaultAlert(
title = resourceReference(
@ -351,14 +340,30 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
formatArgs = WrappedList(listOf(cryptoCurrencyStatus.currency.name)),
),
message = resourceReference(R.string.token_details_hide_alert_message),
onConfirmClick = { onPerformHideToken(portfolioId, cryptoCurrencyStatus) },
onConfirmClick = { onPerformHideToken(userWalletId, cryptoCurrencyStatus) },
)
}
}
override fun onPerformHideToken(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.io) {
useCasesFacade.removeCurrencyUseCase(portfolioId, cryptoCurrencyStatus.currency)
if (accountsFeatureToggles.isFeatureEnabled) {
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = cryptoCurrencyStatus.currency,
)
.map { it.account.accountId }
.getOrNull()
if (accountId == null) {
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrencyStatus.currency.id}")
return@launch
}
saveCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
} else {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
}
.fold(
ifLeft = {
walletEventSender.send(
@ -366,7 +371,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
},
ifRight = {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
},
)
}
@ -399,7 +404,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
override fun onBuyClick(
portfolioId: PortfolioId,
userWalletId: UserWalletId,
cryptoCurrencyStatus: CryptoCurrencyStatus,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
@ -415,7 +420,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
appRouter.push(
AppRoute.Onramp(
portfolioId = portfolioId,
userWalletId = userWalletId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
@ -424,7 +429,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onSwapClick(
cryptoCurrencyStatus: CryptoCurrencyStatus,
portfolioId: PortfolioId,
userWalletId: UserWalletId,
unavailabilityReason: ScenarioUnavailabilityReason,
) {
analyticsEventHandler.send(
@ -447,12 +452,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
modelScope.launch {
saveViewedYieldSupplyWarningUseCase(cryptoCurrencyStatus.currency.name)
stateHolder.hideBottomSheet()
navigateToSwap(cryptoCurrencyStatus, portfolioId)
navigateToSwap(cryptoCurrencyStatus, userWalletId)
}
},
)
} else {
navigateToSwap(cryptoCurrencyStatus, portfolioId)
navigateToSwap(cryptoCurrencyStatus, userWalletId)
}
}
}
@ -493,15 +498,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
}
override fun onStakeClick(portfolioId: PortfolioId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
override fun onStakeClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
modelScope.launch {
val cryptoCurrency = cryptoCurrencyStatus.currency
appRouter.push(
AppRoute.Staking(
portfolioId = portfolioId,
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrency.id,
yieldId = yield?.id ?: return@launch,
),
@ -519,15 +524,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
val tokenListState = selectedWallet.tokensListState as? WalletTokensListState.ContentState.Content ?: return
val tokenListState = selectedWallet.tokensListState
if (tokenListState.items.count { it is TokensListItemUM.Token } < 2) {
handleError(
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
when (tokenListState) {
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
)
return
is WalletTokensListState.ContentState.PortfolioContent -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.sumOf { it.tokens.count { it is TokensListItemUM.Token } },
)
WalletTokensListState.ContentState.Loading,
WalletTokensListState.ContentState.Locked,
WalletTokensListState.Empty,
-> return
}
modelScope.launch {
@ -720,44 +729,10 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
private suspend fun configureReceiveAddresses(cryptoCurrencyStatus: CryptoCurrencyStatus): TokenReceiveConfig? {
val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return null
val userWalletId = stateHolder.getSelectedWalletId()
val ensName = getEnsNameUseCase.invoke(
return receiveAddressesFactory.create(
status = cryptoCurrencyStatus,
userWalletId = userWalletId,
network = cryptoCurrencyStatus.currency.network,
address = networkAddress.defaultAddress.value,
)
val receiveAddresses = buildList {
ensName?.let { ens ->
add(
ReceiveAddressModel(
nameService = ReceiveAddressModel.NameService.Ens,
value = ens,
),
)
}
networkAddress.availableAddresses.map { address ->
add(
ReceiveAddressModel(
nameService = when (address.type) {
NetworkAddress.Address.Type.Primary -> ReceiveAddressModel.NameService.Default
NetworkAddress.Address.Type.Secondary -> ReceiveAddressModel.NameService.Legacy
},
value = address.value,
),
)
}
}
return TokenReceiveConfig(
shouldShowWarning = cryptoCurrencyStatus.currency.name !in getViewedTokenReceiveWarningUseCase(),
cryptoCurrency = cryptoCurrencyStatus.currency,
userWalletId = userWalletId,
showMemoDisclaimer = cryptoCurrencyStatus.currency.network.transactionExtrasType != Network
.TransactionExtrasType.NONE,
receiveAddress = receiveAddresses,
)
}
@ -766,21 +741,21 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
}
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, portfolioId: PortfolioId) {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = portfolioId.userWalletId))
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWalletId))
val route = AppRoute.Send(
currency = cryptoCurrencyStatus.currency,
portfolioId = portfolioId,
userWalletId = userWalletId,
)
appRouter.push(route)
}
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, portfolioId: PortfolioId) {
private fun navigateToSwap(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {
appRouter.push(
AppRoute.Swap(
currencyFrom = cryptoCurrencyStatus.currency,
portfolioId = portfolioId,
userWalletId = userWalletId,
screenSource = AnalyticsParam.ScreensSources.LongTap.value,
),
)
@ -808,4 +783,15 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
)
}
}
private fun checkSwapCryptoAvailability(tokenCount: Int) {
if (tokenCount < 2) {
handleError(
alertState = WalletAlertState.InsufficientTokensCountForSwapping,
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
)
return
}
}
}

View file

@ -361,7 +361,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
userWalletId = userWallet.walletId,
currency = cryptoCurrency,
source = OnrampSource.SEPA_BANNER,
launchSepa = true,
shouldLaunchSepa = true,
),
)
}

View file

@ -19,6 +19,7 @@ import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarCon
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
internal object WalletScreenPreviewData {
private val tokenItemState = TokenItemState.Content(
@ -86,15 +87,17 @@ internal object WalletScreenPreviewData {
private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
items = persistentListOf(
TokensListItemUM.Portfolio(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
isExpanded = false,
state = AccountItemPreviewData.accountItem
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
),
TokensListItemUM.Portfolio(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>(),
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
isExpanded = true,
state = AccountItemPreviewData.accountItem,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem,
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(

View file

@ -3,17 +3,16 @@ package com.tangem.feature.wallet.presentation.router
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.redux.StateDialog
import com.tangem.domain.tokens.model.details.TokenAction
@ -67,12 +66,12 @@ internal class DefaultWalletRouter @Inject constructor(
urlOpener.openUrl(url)
}
override fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus) {
override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
val networkAddress = currencyStatus.value.networkAddress
if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) {
router.push(
AppRoute.CurrencyDetails(
portfolioId = portfolioId,
userWalletId = userWalletId,
currency = currencyStatus.currency,
),
)
@ -87,10 +86,6 @@ internal class DefaultWalletRouter @Inject constructor(
return router.stack.lastOrNull() is AppRoute.Wallet
}
override fun openManageTokensScreen(userWalletId: UserWalletId) {
router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
}
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
}
@ -114,8 +109,8 @@ internal class DefaultWalletRouter @Inject constructor(
router.push(AppRoute.TangemPayOnboarding(AppRoute.TangemPayOnboarding.Mode.ContinueOnboarding))
}
override fun openTangemPayDetails(customerWalletAddress: String, cardNumberEnd: String) {
router.push(AppRoute.TangemPayDetails(customerWalletAddress, cardNumberEnd))
override fun openTangemPayDetails(config: TangemPayDetailsConfig) {
router.push(AppRoute.TangemPayDetails(config))
}
override fun openYieldSupplyBottomSheet(

View file

@ -2,13 +2,13 @@ package com.tangem.feature.wallet.presentation.router
import androidx.compose.runtime.Stable
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.tokens.model.details.TokenAction
import com.tangem.feature.wallet.navigation.WalletRoute
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
@ -42,7 +42,7 @@ internal interface InnerWalletRouter {
fun openUrl(url: String)
/** Open token details screen */
fun openTokenDetails(portfolioId: PortfolioId, currencyStatus: CryptoCurrencyStatus)
fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
/** Open stories screen */
fun openStoriesScreen()
@ -50,9 +50,6 @@ internal interface InnerWalletRouter {
/** Is wallet last screen */
fun isWalletLastScreen(): Boolean
/** Open manage tokens screen */
fun openManageTokensScreen(userWalletId: UserWalletId)
/** Open scan failed dialog */
fun openScanFailedDialog(onTryAgain: () -> Unit)
@ -63,7 +60,7 @@ internal interface InnerWalletRouter {
fun openTangemPayOnboarding()
fun openTangemPayDetails(customerWalletAddress: String, cardNumberEnd: String)
fun openTangemPayDetails(config: TangemPayDetailsConfig)
/** Open BS abput yield supply active and all money deposited in AAVE */
fun openYieldSupplyBottomSheet(

View file

@ -73,6 +73,13 @@ sealed class WalletScreenAnalyticsEvent {
data object ScreenOpened : MainScreen(event = "Screen opened")
class WalletSelected(val isImported: Boolean) : MainScreen(
event = "Wallet Selected",
params = mapOf(
"Wallet Type" to if (isImported) "Seed Phrase" else "Seedless",
),
)
class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen(
event = "Enable Biometric",
params = mapOf("State" to state.value),

View file

@ -29,6 +29,14 @@ internal class SelectedWalletAnalyticsSender @Inject constructor(
* */
private fun getEvent(userWallet: UserWallet): AnalyticsEvent? = when {
userWallet.isLocked -> WalletScreenAnalyticsEvent.MainScreen.WalletUnlock
else -> null
else -> WalletScreenAnalyticsEvent.MainScreen.WalletSelected(userWallet.isImported())
}
private fun UserWallet.isImported(): Boolean {
return when (this) {
is UserWallet.Cold -> isImported
is UserWallet.Hot -> true
}
}
}

View file

@ -14,7 +14,6 @@ import com.tangem.domain.analytics.model.WalletBalanceState
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isMultiCurrency
@ -38,33 +37,41 @@ internal class TokenListAnalyticsSender @Inject constructor(
private val mutex = Mutex()
private val loadingTraces = mutableMapOf<UserWalletId, Trace>()
suspend fun send(displayedUiState: WalletState?, userWallet: UserWallet, tokenList: TokenList) {
suspend fun send(
displayedUiState: WalletState?,
userWallet: UserWallet,
totalFiatBalance: TotalFiatBalance,
flattenCurrencies: List<CryptoCurrencyStatus>,
) {
if (screenLifecycleProvider.isBackgroundState.value) return
if (displayedUiState == null || displayedUiState.pullToRefreshConfig.isRefreshing) return
if (tokenList.totalFiatBalance is TotalFiatBalance.Loading) {
startLoadingTraceIfNeeded(userWallet.walletId, tokenList)
if (totalFiatBalance is TotalFiatBalance.Loading) {
startLoadingTraceIfNeeded(userWallet.walletId, flattenCurrencies)
return
}
if (isTerminalState(tokenList.totalFiatBalance)) {
stopLoadingTraceIfNeeded(userWallet.walletId, tokenList.totalFiatBalance)
if (isTerminalState(totalFiatBalance)) {
stopLoadingTraceIfNeeded(userWallet.walletId, totalFiatBalance)
}
val currenciesStatuses = tokenList.flattenCurrencies()
val currenciesStatuses = flattenCurrencies
sendBalanceLoadedEventIfNeeded(tokenList.totalFiatBalance, currenciesStatuses)
sendToppedUpEventIfNeeded(userWallet, tokenList.totalFiatBalance, currenciesStatuses)
sendBalanceLoadedEventIfNeeded(totalFiatBalance, currenciesStatuses)
sendToppedUpEventIfNeeded(userWallet, totalFiatBalance, currenciesStatuses)
sendUnreachableNetworksEventIfNeeded(currenciesStatuses)
sendTokenBalancesIfNeeded(currenciesStatuses)
}
private suspend fun startLoadingTraceIfNeeded(userWalletId: UserWalletId, tokenList: TokenList) {
private suspend fun startLoadingTraceIfNeeded(
userWalletId: UserWalletId,
flattenCurrencies: List<CryptoCurrencyStatus>,
) {
mutex.withLock {
if (!loadingTraces.containsKey(userWalletId)) {
val trace = FirebasePerformance.getInstance().newTrace(BALANCE_LOADED_TRACE_NAME)
trace.start()
trace.putAttribute(TOKENS_COUNT, tokenList.flattenCurrencies().size.toString())
trace.putAttribute(TOKENS_COUNT, flattenCurrencies.size.toString())
loadingTraces[userWalletId] = trace
}
}

View file

@ -9,17 +9,16 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.components.notifications.NotificationConfig.IconTint
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.onramp.GetOnrampCountryUseCase
@ -45,6 +44,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
@ -70,72 +70,81 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
val tokenListFlow = if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
val accountStatusList by lazy {
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
accountDependencies.singleAccountStatusListSupplier(params)
} else {
tokenListStore.getOrThrow(userWallet.walletId)
.map { it.totalFiatBalance to it.flattenCurrencies() }
.map { Lce.Content(it) }
}
fun tokenListFlow(): LceFlow<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>> {
return if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
accountStatusList
} else {
runCatching { tokenListStore.getOrThrow(userWallet.walletId) }
.map { result -> result.map { lce -> lce.map { it.totalFiatBalance to it.flattenCurrencies() } } }
.getOrNull()
// in case of runtime change ft in tester menu
?: accountStatusList
}
}
// val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
// val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
return combine(
tokenListFlow,
// todo account just use it, after delete accountsFeatureToggles
// accountStatusListFlow,
isReadyToShowRateAppUseCase(),
isNeedToBackupUseCase(userWallet.walletId),
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Referral),
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key),
) { array ->
val totalFiatBalance: Lce<TokenListError, TotalFiatBalance>
val flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>
if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
val accountStatusList = array[0] as AccountStatusList
totalFiatBalance = Lce.Content(accountStatusList.totalFiatBalance)
flattenCurrencies = Lce.Content(accountStatusList.flattenCurrencies())
} else {
val maybeTokenList = array[0] as Lce<TokenListError, TokenList>
totalFiatBalance = maybeTokenList.map { it.totalFiatBalance }
flattenCurrencies = maybeTokenList.map { it.flattenCurrencies() }
) { array -> array }
.combine(tokenListFlow()) { array, any: Any -> arrayOf(any).plus(elements = array) }
.map { array ->
val lceTokens = array[0] as Lce<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>>
val totalFiatBalance = lceTokens.map { it.first }
val flattenCurrencies = lceTokens.map { it.second }
val isReadyToShowRating = array[1] as Boolean
val isNeedToBackup = array[2] as Boolean
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
val shouldShowReferralPromo = array[4] as Boolean
val shouldShowSepaBanner = array[5] as Boolean
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
buildList {
addUsedOutdatedDataNotification(totalFiatBalance)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents)
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
addPushReminderNotification(
clickIntents = clickIntents,
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
)
addYieldSupplyNotifications(flattenCurrencies)
val hasCriticalOrWarning = any { notification ->
notification is WalletNotification.Critical || notification is WalletNotification.Warning
}
if (!hasCriticalOrWarning) {
addRateTheAppNotification(isReadyToShowRating, clickIntents)
}
}.toImmutableList()
}
val isReadyToShowRating = array[1] as Boolean
val isNeedToBackup = array[2] as Boolean
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
val shouldShowReferralPromo = array[4] as Boolean
val shouldShowSepaBanner = array[5] as Boolean
val shouldShowEnablePushesReminderNotification = array[6] as Boolean
buildList {
addUsedOutdatedDataNotification(totalFiatBalance)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addFinishWalletActivationNotification(userWallet, totalFiatBalance, clickIntents)
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
addSepaPromoNotification(userWallet, clickIntents, shouldShowSepaBanner)
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
addPushReminderNotification(
clickIntents = clickIntents,
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
)
addYieldSupplyNotifications(flattenCurrencies)
val hasCriticalOrWarning = any { notification ->
notification is WalletNotification.Critical || notification is WalletNotification.Warning
}
if (!hasCriticalOrWarning) {
addRateTheAppNotification(isReadyToShowRating, clickIntents)
}
}.toImmutableList()
}
}
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(

View file

@ -50,7 +50,7 @@ internal class SetCryptoCurrencyActionsTransformer(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onBuyClick(
portfolioId = portfolioId,
userWalletId = portfolioId.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
unavailabilityReason = action.unavailabilityReason,
)
@ -62,7 +62,10 @@ internal class SetCryptoCurrencyActionsTransformer(
enabled = true,
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus = cryptoCurrencyStatus)
clickIntents.onReceiveClick(
portfolioId.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
)
},
onLongClick = {
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus)
@ -87,7 +90,7 @@ internal class SetCryptoCurrencyActionsTransformer(
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
onClick = {
clickIntents.onSendClick(
portfolioId = portfolioId,
userWalletId = portfolioId.userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
unavailabilityReason = action.unavailabilityReason,
)

View file

@ -9,7 +9,6 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
@ -153,7 +152,7 @@ internal class SetVisaInfoTransformer(
dimContent = false,
onClick = {
clickIntents.onReceiveClick(
portfolioId = PortfolioId(userWalletId), // todo account Visa use Main account?
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
event = MainScreenAnalyticsEvent.ButtonReceive,
)

View file

@ -0,0 +1,68 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus.CANCELED
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createKycInProgressState
import java.util.Currency
/**
* Hardcode Polygon chain id only for F&F.
* Later chain id will be fetched from BFF.
*/
private const val POLYGON_CHAIN_ID = 137
internal class TangemPayInitialStateTransformer(
private val value: MainScreenCustomerInfo? = null,
private val onClickIssue: () -> Unit = {},
private val onClickKyc: () -> Unit = {},
private val openDetails: (config: TangemPayDetailsConfig) -> Unit = {},
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
val tangemPayState = createInitialState()
return prevState.copy(tangemPayState = tangemPayState)
}
private fun createInitialState(): TangemPayState {
val cardInfo = value?.info?.cardInfo
return when {
value == null -> TangemPayState.Empty
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
cardInfo != null -> getCardInfoState(cardInfo)
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
else -> createIssueProgressState()
}
}
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
onClick = {
openDetails(
TangemPayDetailsConfig(
customerWalletAddress = cardInfo.customerWalletAddress,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
depositAddress = cardInfo.depositAddress,
),
)
},
)
private fun getBalanceText(cardInfo: CardInfo): String {
val currency = Currency.getInstance(cardInfo.currencyCode)
return cardInfo.balance.format {
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
}
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
internal class TangemPayIssueAvailableStateTransformer(
private val onClickIssue: () -> Unit = {},
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState =
prevState.copy(tangemPayState = createIssueAvailableState(onClickIssue))
}

View file

@ -0,0 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
internal class TangemPayIssueProgressStateTransformer : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState =
prevState.copy(tangemPayState = createIssueProgressState())
}

View file

@ -1,79 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus.CANCELED
import com.tangem.domain.pay.model.OrderStatus.NOT_ISSUED
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import java.util.Currency
internal class TangemPayStateTransformer(
private val value: MainScreenCustomerInfo? = null,
private val onIssueOrderClick: () -> Unit = {},
private val onContinueKycClick: () -> Unit = {},
private val openDetails: (customerWalletAddress: String, cardNumberEnd: String) -> Unit = { _, _ -> },
private val issueProgressState: Boolean = false,
private val issueState: Boolean = false,
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
val tangemPayState = when {
issueProgressState -> createIssueProgressState()
issueState -> createIssueState()
else -> createInitialState()
}
return prevState.copy(tangemPayState = tangemPayState)
}
private fun createInitialState(): TangemPayState {
val cardInfo = value?.info?.cardInfo
return when {
value == null -> TangemPayState.Empty
!value.info.isKycApproved() -> createKycInProgressState(onContinueKycClick)
cardInfo != null -> getCardInfoState(cardInfo)
value.orderStatus == NOT_ISSUED || value.orderStatus == CANCELED -> createIssueState()
else -> createIssueProgressState()
}
}
private fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
)
private fun createIssueState() = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = onIssueOrderClick,
)
private fun createKycInProgressState(onContinueKycClick: () -> Unit): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = onContinueKycClick,
)
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),
onClick = { openDetails(cardInfo.customerWalletAddress, cardInfo.lastFourDigits) },
)
private fun getBalanceText(cardInfo: CardInfo): String {
val currency = Currency.getInstance(cardInfo.currencyCode)
return cardInfo.balance.format {
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
}
}
}

View file

@ -3,9 +3,9 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.feature.wallet.child.wallet.model.intents.WalletCurrencyActionsClickIntents
@ -18,10 +18,11 @@ import kotlinx.collections.immutable.toImmutableList
internal class MultiWalletCurrencyActionsConverter(
private val userWallet: UserWallet,
private val portfolioId: PortfolioId,
private val clickIntents: WalletCurrencyActionsClickIntents,
) : Converter<TokenActionsState, ImmutableList<TokenActionButtonConfig>> {
private val userWalletId: UserWalletId = userWallet.walletId
override fun convert(value: TokenActionsState): ImmutableList<TokenActionButtonConfig> {
return value.states
.filterIfSingleWithToken()
@ -56,17 +57,17 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.Buy -> {
title = resourceReference(R.string.common_buy)
icon = R.drawable.ic_plus_24
action = { clickIntents.onBuyClick(portfolioId, cryptoCurrencyStatus, noneReason) }
action = { clickIntents.onBuyClick(userWalletId, cryptoCurrencyStatus, noneReason) }
}
is TokenActionsState.ActionState.Receive -> {
title = resourceReference(R.string.common_receive)
icon = R.drawable.ic_arrow_down_24
action = { clickIntents.onReceiveClick(portfolioId, cryptoCurrencyStatus) }
action = { clickIntents.onReceiveClick(userWalletId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Stake -> {
title = resourceReference(R.string.common_stake)
icon = R.drawable.ic_staking_24
action = { clickIntents.onStakeClick(portfolioId, cryptoCurrencyStatus, actionsState.yield) }
action = { clickIntents.onStakeClick(userWalletId, cryptoCurrencyStatus, actionsState.yield) }
}
is TokenActionsState.ActionState.Sell -> {
title = resourceReference(R.string.common_sell)
@ -76,7 +77,7 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.Send -> {
title = resourceReference(R.string.common_send)
icon = R.drawable.ic_arrow_up_24
action = { clickIntents.onSendClick(portfolioId, cryptoCurrencyStatus, noneReason) }
action = { clickIntents.onSendClick(userWalletId, cryptoCurrencyStatus, noneReason) }
}
is TokenActionsState.ActionState.Swap -> {
title = resourceReference(R.string.swapping_swap_action)
@ -84,7 +85,7 @@ internal class MultiWalletCurrencyActionsConverter(
action = {
clickIntents.onSwapClick(
cryptoCurrencyStatus = cryptoCurrencyStatus,
portfolioId = portfolioId,
userWalletId = userWalletId,
unavailabilityReason = noneReason,
)
}
@ -92,12 +93,12 @@ internal class MultiWalletCurrencyActionsConverter(
is TokenActionsState.ActionState.CopyAddress -> {
title = resourceReference(R.string.common_copy_address)
icon = R.drawable.ic_copy_24
action = { clickIntents.onCopyAddressClick(portfolioId, cryptoCurrencyStatus) }
action = { clickIntents.onCopyAddressClick(userWalletId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.HideToken -> {
title = resourceReference(R.string.token_details_hide_token)
icon = R.drawable.ic_hide_24
action = { clickIntents.onHideTokensClick(portfolioId, cryptoCurrencyStatus) }
action = { clickIntents.onHideTokensClick(userWalletId, cryptoCurrencyStatus) }
}
is TokenActionsState.ActionState.Analytics -> {
title = resourceReference(R.string.common_analytics)

View file

@ -2,7 +2,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers.convert
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
import com.tangem.common.ui.tokens.TokenItemStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
@ -14,7 +13,6 @@ import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
@ -36,23 +34,22 @@ internal class TokenListStateConverter(
private val params: TokenConverterParams,
private val selectedWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val apyMap: Map<String, String> = emptyMap(),
private val apyMap: Map<String, String>,
) : Converter<WalletTokensListState, WalletTokensListState> {
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
{ accountId, currencyStatus ->
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
clickIntents.onTokenItemClick(id, currencyStatus)
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
}
private val onTokenLongClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
{ accountId, currencyStatus ->
val id = accountId?.let { PortfolioId(accountId) } ?: PortfolioId(selectedWallet.walletId)
clickIntents.onTokenItemLongClick(id, currencyStatus)
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
}
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
appCurrency = appCurrency,
apyMap = apyMap,
onItemClick = { _, status -> onTokenClick(accountId, status) },
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
)
@ -112,9 +109,10 @@ internal class TokenListStateConverter(
is WalletTokensListState.Empty -> listOf()
}
return TokensListItemUM.Portfolio(
state = accountItem,
tokenItemUM = accountItem,
isExpanded = isExtend,
tokens = items.filterIsInstance<PortfolioTokensListItemUM>(),
isCollapsable = true,
tokens = items.filterIsInstance<PortfolioTokensListItemUM>().toPersistentList(),
)
}
@ -168,55 +166,13 @@ internal class TokenListStateConverter(
tokenConverter: TokenItemStateConverter,
token: CryptoCurrencyStatus,
): List<TokensListItemUM> {
val tokenItemState = tokenConverter.convert(token).withEarnApyBadge(token)
val tokenItemState = tokenConverter.convert(token)
add(TokensListItemUM.Token(tokenItemState))
return this
}
private fun TokenItemState.withEarnApyBadge(cryptoCurrencyStatus: CryptoCurrencyStatus): TokenItemState {
val apy: String? = resolveEarnApy(cryptoCurrencyStatus)
val shouldApply = apy != null && this.titleState is TokenItemState.TitleState.Content
return if (!shouldApply) {
this
} else {
val contentTitle = this.titleState as TokenItemState.TitleState.Content
val newTitle = contentTitle.copy(
earnApy = resourceReference(
R.string.yield_module_earn_badge,
wrappedList(apy),
),
)
when (this) {
is TokenItemState.Content -> this.copy(titleState = newTitle)
is TokenItemState.Unreachable -> this.copy(titleState = newTitle)
is TokenItemState.NoAddress -> this.copy(titleState = newTitle)
is TokenItemState.Loading -> this.copy(titleState = newTitle)
is TokenItemState.Draggable -> this.copy(titleState = newTitle)
is TokenItemState.Locked -> this
}
}
}
private fun resolveEarnApy(cryptoCurrencyStatus: CryptoCurrencyStatus): String? {
if (apyMap.isEmpty()) return null
val isYieldSupplyActive = (cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded)
?.yieldSupplyStatus?.isActive == true
if (isYieldSupplyActive) return null
val contract = (cryptoCurrencyStatus.currency as? CryptoCurrency.Token)
?.contractAddress
?.lowercase() ?: return null
val yieldSupplyKey = "${cryptoCurrencyStatus.currency.network.backendId}_$contract"
return apyMap[yieldSupplyKey]
}
private fun getOrganizeTokensButtonState(tokenList: TokenList): WalletOrganizeTokensButtonConfig? {
val currenciesSize = when (tokenList) {
TokenList.Empty -> return null

View file

@ -0,0 +1,31 @@
package com.tangem.feature.wallet.presentation.wallet.state.util
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
internal object TangemPayStateCreator {
fun createKycInProgressState(onClickKyc: () -> Unit): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = onClickKyc,
)
fun createIssueAvailableState(onClickIssue: () -> Unit) = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = onClickIssue,
)
fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
)
}

View file

@ -8,7 +8,9 @@ import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
@ -63,7 +65,10 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
flow = tokenListFlow(coroutineScope)
.onEach { maybeTokenList ->
coroutineScope.launch {
sendTokenListAnalytics(maybeTokenList)
sendTokenListAnalytics(
flattenCurrencies = maybeTokenList.getOrNull()?.flattenCurrencies(),
totalFiatBalance = maybeTokenList.getOrNull()?.totalFiatBalance,
)
}.saveIn(sendAnalyticsJobHolder)
}
.distinctUntilChanged()
@ -140,12 +145,14 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine(
flow = accountListFlow(coroutineScope)
// todo account analytics for account total balance
/*.onEach { maybeTokenList ->
.onEach { accountStatusList ->
coroutineScope.launch {
sendTokenListAnalytics(maybeTokenList)
sendTokenListAnalytics(
flattenCurrencies = accountStatusList.flattenCurrencies(),
totalFiatBalance = accountStatusList.totalFiatBalance,
)
}.saveIn(sendAnalyticsJobHolder)
}*/
}
.distinctUntilChanged()
.onEach { accountList ->
// todo account see[onAccountListReceived]
@ -207,13 +214,17 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() {
}
}
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
private suspend fun sendTokenListAnalytics(
flattenCurrencies: List<CryptoCurrencyStatus>?,
totalFiatBalance: TotalFiatBalance?,
) {
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)
tokenListAnalyticsSender.send(
displayedUiState = displayedState,
userWallet = userWallet,
tokenList = maybeTokenList.getOrNull() ?: return,
flattenCurrencies = flattenCurrencies ?: return,
totalFiatBalance = totalFiatBalance ?: return,
)
}

View file

@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.tokenlist.PortfolioListItem
import com.tangem.core.ui.components.tokenlist.PortfolioTokensListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
@ -59,6 +60,7 @@ internal fun LazyListScope.portfolioTokensList(
contentType = { _, item -> item::class.java },
itemContent = { tokenIndex, token ->
val indexWithHeader = tokenIndex.inc()
val lastIndex = tokens.lastIndex.inc()
val isPreview = LocalInspectionMode.current
val appear = remember {
MutableTransitionState(isPreview).apply { targetState = true }
@ -69,11 +71,12 @@ internal fun LazyListScope.portfolioTokensList(
.animateItem()
.roundedShapeItemDecoration(
currentIndex = indexWithHeader,
lastIndex = tokens.lastIndex.inc(),
lastIndex = lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
),
visibleState = appear,
) {
val modifier = if (indexWithHeader == lastIndex) Modifier.padding(bottom = 8.dp) else Modifier
PortfolioTokensListItem(
state = token,
isBalanceHidden = isBalanceHidden,
@ -114,10 +117,15 @@ private fun LazyListScope.portfolioItem(
modifier = anchorModifier,
visibleState = appear,
) {
val modifier = if (portfolio.tokens.isEmpty()) {
Modifier.padding(vertical = 8.dp)
} else {
Modifier.padding(top = 8.dp)
}
PortfolioListItem(
state = portfolio,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
modifier = modifier,
)
}
} else {

View file

@ -1,24 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.utils
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase
import com.tangem.domain.tokens.RemoveCurrencyUseCase
import javax.inject.Inject
class WalletFeatureUseCasesFacade @Inject constructor(
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
) {
suspend fun isCryptoCurrencyCoinCouldHide(portfolioId: PortfolioId, cryptoCurrencyCoin: CryptoCurrency.Coin) =
when (portfolioId) {
is PortfolioId.Account -> TODO("account")
is PortfolioId.Wallet -> isCryptoCurrencyCoinCouldHide(portfolioId.userWalletId, cryptoCurrencyCoin)
}
suspend fun removeCurrencyUseCase(portfolioId: PortfolioId, currency: CryptoCurrency) = when (portfolioId) {
is PortfolioId.Account -> TODO("account")
is PortfolioId.Wallet -> removeCurrencyUseCase(portfolioId.userWalletId, currency)
}
}

View file

@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.wallet.utils.UserWalletImageFetcher
import com.tangem.operations.attestation.ArtworkSize
import kotlinx.coroutines.flow.*
@ -17,12 +18,21 @@ import javax.inject.Inject
class DefaultUserWalletImageFetcher @Inject constructor(
private val getCardImageUseCase: GetCardImageUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val artworkUMConverter: ArtworkUMConverter,
) : UserWalletImageFetcher {
private val smallCache = MutableStateFlow(mapOf<String, ArtworkUM>())
private val largeCache = MutableStateFlow(mapOf<String, ArtworkUM>())
override fun allWallets(size: ArtworkSize): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> =
getWalletsUseCase.invoke()
.map { list -> list.map { it.walletId } }
.distinctUntilChanged()
.map { list -> list.map { walletId -> walletImage(walletId, size).map { image -> walletId to image } } }
.flatMapLatest { flows -> combine(flows) { it.toMap() } }
.distinctUntilChanged()
override fun walletImage(wallet: UserWallet, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = when (wallet) {
is UserWallet.Cold -> walletImage(wallet.scanResponse.card, size)
is UserWallet.Hot -> flowOf(UserWalletItemUM.ImageState.MobileWallet)
@ -31,11 +41,8 @@ class DefaultUserWalletImageFetcher @Inject constructor(
override fun walletsImage(
wallets: Collection<UserWallet>,
size: ArtworkSize,
): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> = wallets
.map { userWallet -> walletImage(userWallet, size).map { imageState -> userWallet.walletId to imageState } }
.merge()
.runningFold(mapOf<UserWalletId, UserWalletItemUM.ImageState>()) { map, newState -> map.plus(newState) }
.filter { it.size >= wallets.size } // prevent spam, waiting full map
): Flow<Map<UserWalletId, UserWalletItemUM.ImageState>> = allWallets(size)
.map { allWallets -> allWallets.filter { (walletId, _) -> wallets.any { it.walletId == walletId } } }
.distinctUntilChanged()
override fun walletImage(walletId: UserWalletId, size: ArtworkSize): Flow<UserWalletItemUM.ImageState> = flow {

View file

@ -130,6 +130,11 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
balance = balance,
isBalanceHidden = balanceHidingSettings.isBalanceHidden,
artwork = artworks[userWallet.walletId],
endIcon = if (isAuthMode.not() && userWallet is UserWallet.Hot && !userWallet.backedUp) {
UserWalletItemUM.EndIcon.Warning
} else {
UserWalletItemUM.EndIcon.None
},
isAuthMode = isAuthMode,
)
.convert(userWallet)