Updated on 2026-08-14
This commit is contained in:
commit
b0ac045d0d
119 changed files with 2794 additions and 607 deletions
|
|
@ -19,7 +19,7 @@ internal class OnrampOffersStateFactory(
|
|||
return when (currentState) {
|
||||
is OnrampV2MainComponentUM.InitialLoading -> currentState
|
||||
is OnrampV2MainComponentUM.Content -> {
|
||||
if (currentState.offersBlockState is OnrampOffersBlockUM.Loading && offers.isEmpty()) {
|
||||
if (currentState.offersBlockState is OnrampOffersBlockUM.Loading) {
|
||||
return currentState
|
||||
}
|
||||
currentState.copy(
|
||||
|
|
|
|||
|
|
@ -64,7 +64,11 @@ internal class OnrampV2AmountStateFactory(
|
|||
currencySymbol = currency.unit,
|
||||
onAmountValueChanged = onrampIntents::onAmountValueChanged,
|
||||
),
|
||||
offersBlockState = OnrampOffersBlockUM.Loading,
|
||||
offersBlockState = if (amountState.amountFieldModel.fiatValue.isEmpty()) {
|
||||
OnrampOffersBlockUM.Empty
|
||||
} else {
|
||||
OnrampOffersBlockUM.Loading
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +102,7 @@ internal class OnrampV2AmountStateFactory(
|
|||
amountBlockState = amountState.copy(secondaryFieldModel = OnrampSecondaryFieldErrorUM.Empty),
|
||||
onrampAmountButtonUMState = OnrampV2AmountButtonUMState.None,
|
||||
errorNotification = null,
|
||||
offersBlockState = currentState.offersBlockState,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -236,8 +236,17 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
maybeOffers.fold(
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { offers ->
|
||||
if (offers.isNotEmpty()) {
|
||||
state.update { onrampOffersStateFactory.getOffersState(offers) }
|
||||
val currentState = state.value
|
||||
if (currentState is OnrampV2MainComponentUM.Content) {
|
||||
if (currentState.amountBlockState.amountFieldModel.fiatValue.isEmpty()) {
|
||||
state.update {
|
||||
currentState.copy(offersBlockState = OnrampOffersBlockUM.Empty)
|
||||
}
|
||||
return@fold
|
||||
}
|
||||
state.update {
|
||||
onrampOffersStateFactory.getOffersState(offers)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -301,7 +310,17 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) }
|
||||
}
|
||||
else -> {
|
||||
state.update { amountStateFactory.getAmountSecondaryFieldResetState() }
|
||||
state.update { prevState ->
|
||||
val resetState = amountStateFactory.getAmountSecondaryFieldResetState()
|
||||
if (prevState is OnrampV2MainComponentUM.Content &&
|
||||
resetState is OnrampV2MainComponentUM.Content &&
|
||||
prevState.offersBlockState is OnrampOffersBlockUM.Loading
|
||||
) {
|
||||
resetState.copy(offersBlockState = OnrampOffersBlockUM.Empty)
|
||||
} else {
|
||||
resetState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1651,8 +1651,7 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun onTangemPaySupportClick(txId: String?) {
|
||||
modelScope.launch {
|
||||
val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId)
|
||||
.getOrElse { error("CardInfo must be not null") }
|
||||
val metaInfo = getWalletMetaInfoUseCase(userWallet.walletId).getOrNull() ?: return@launch
|
||||
val email = FeedbackEmailType.Visa.Withdrawal(
|
||||
walletMetaInfo = metaInfo,
|
||||
providerName = dataState.selectedProvider?.name.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
|
||||
|
||||
init {
|
||||
analytics.send(TangemPayAnalyticsEvents.MainScreenOpened())
|
||||
handleBalanceHiding()
|
||||
fetchAddToWalletBanner()
|
||||
fetchBalance()
|
||||
|
|
@ -212,6 +213,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onClickAddFunds() {
|
||||
analytics.send(TangemPayAnalyticsEvents.AddFundsClicked())
|
||||
val currentBalance = balance
|
||||
val depositAddress = currentBalance?.depositAddress
|
||||
if (currentBalance == null || depositAddress == null) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
|||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDetailsComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUM
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter
|
||||
|
|
@ -21,10 +19,8 @@ import javax.inject.Inject
|
|||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
@Suppress("LongParameterList")
|
||||
internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getUserWalletsUseCase: GetWalletsUseCase,
|
||||
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
|
|
@ -66,12 +62,7 @@ internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
|||
|
||||
private fun dispute() {
|
||||
modelScope.launch {
|
||||
val userWalletId = params.userWalletId
|
||||
val userWallet = getUserWalletsUseCase.invokeSync()
|
||||
.firstOrNull { it.walletId == userWalletId } ?: return@launch
|
||||
val walletMetaInfo = getWalletMetaInfoUseCase.invoke(
|
||||
userWallet.requireColdWallet().scanResponse,
|
||||
).getOrNull() ?: return@launch
|
||||
val walletMetaInfo = getWalletMetaInfoUseCase.invoke(params.userWalletId).getOrNull() ?: return@launch
|
||||
|
||||
sendFeedbackEmailUseCase.invoke(
|
||||
FeedbackEmailType.Visa.DisputeV2(
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@ internal object TangemPayTxHistoryDetailsConverter :
|
|||
)
|
||||
is TangemPayTxHistoryItem.Spend -> persistentListOf(
|
||||
ButtonState(
|
||||
text = resourceReference(R.string.tangem_pay_dispute),
|
||||
text = resourceReference(R.string.tangem_pay_get_help),
|
||||
onClick = this.onDisputeClick,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -182,8 +182,7 @@ private fun TangemPayCardDetailsShownBlock(
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
CardDetailsTextContainer(
|
||||
|
|
@ -203,6 +202,7 @@ private fun TangemPayCardDetailsShownBlock(
|
|||
onCopy = onCopyCvv,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Row {
|
||||
SpacerWMax()
|
||||
TangemPayCardDetailsCustomButton(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
|
|
@ -74,6 +75,7 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM
|
|||
text = state.transactionSubtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
|
|
@ -84,7 +86,7 @@ internal fun TangemPayTxHistoryDetailsContent(state: TangemPayTxHistoryDetailsUM
|
|||
state.localTransactionText?.let { localTransaction ->
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
text = localTransaction,
|
||||
text = localTransaction.orMaskWithStars(state.isBalanceHidden),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
|
@ -207,11 +209,11 @@ private class TangemPayTxHistoryDetailsUMProvider : CollectionPreviewParameterPr
|
|||
dismiss = {},
|
||||
),
|
||||
TangemPayTxHistoryDetailsUM(
|
||||
isBalanceHidden = false,
|
||||
isBalanceHidden = true,
|
||||
title = stringReference("12 June • 12:40"),
|
||||
iconState = ImageReference.Res(R.drawable.ic_category_24),
|
||||
transactionTitle = stringReference("Starbucks"),
|
||||
transactionSubtitle = stringReference("Food and drinks"),
|
||||
transactionSubtitle = stringReference("Food and drinks Food and drinks Food and drinks Food and drinks"),
|
||||
transactionAmount = "-$5.86",
|
||||
transactionAmountColor = themedColor { TangemTheme.colors.text.primary1 },
|
||||
localTransactionText = "€ 5.36",
|
||||
|
|
|
|||
|
|
@ -340,7 +340,9 @@ private fun AddressItem(
|
|||
onClick = onOpenQrCodeClick,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 12.dp, vertical = 32.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.yieldSupply.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ internal object TokenDetailsPreviewData {
|
|||
selectedBalanceType = BalanceType.ALL,
|
||||
onBalanceSelect = {},
|
||||
displayCryptoBalance = "966,96 XLM",
|
||||
displayYeildSupplyCryptoBalance = null,
|
||||
displayYieldSupplyFiatBalance = null,
|
||||
displayFiatBalance = "91,50$",
|
||||
isBalanceSelectorEnabled = true,
|
||||
isBalanceFlickering = false,
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
|
|||
import com.tangem.domain.wallets.usecase.GetExtendedPublicKeyForCurrencyUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetRewardsBalanceUseCase
|
||||
import com.tangem.feature.tokendetails.deeplink.TokenDetailsDeepLinkActionListener
|
||||
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
|
||||
|
|
@ -416,7 +417,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
.saveIn(yieldSupplyBalanceJobHolder)
|
||||
} else {
|
||||
yieldSupplyBalanceJobHolder.cancel()
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(null)
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedYieldSupplyDisplayBalance(
|
||||
YieldSupplyRewardBalance.empty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1261,14 +1264,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleNavigationParam() {
|
||||
when (val action = params.navigationAction) {
|
||||
is NavigationAction.Staking -> openStaking()
|
||||
is NavigationAction.YieldSupply -> if (action.isActive) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
fetchCurrencyStatusUseCase(userWalletId = userWalletId, id = cryptoCurrency.id)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
if (params.navigationAction is NavigationAction.Staking) {
|
||||
openStaking()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ internal sealed class TokenDetailsBalanceBlockState {
|
|||
val isBalanceSelectorEnabled: Boolean,
|
||||
val isBalanceFlickering: Boolean,
|
||||
val yieldSupplyState: TokenDetailsYieldSupplyState = TokenDetailsYieldSupplyState.Empty,
|
||||
val displayYeildSupplyCryptoBalance: String? = null,
|
||||
val displayYieldSupplyFiatBalance: String? = null,
|
||||
val displayYieldSupplyCryptoBalance: String? = null,
|
||||
) : TokenDetailsBalanceBlockState()
|
||||
|
||||
data class Error(
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
stakingCryptoAmount,
|
||||
currentState.selectedBalanceType,
|
||||
),
|
||||
displayYeildSupplyCryptoBalance = (currentState as? TokenDetailsBalanceBlockState.Content)
|
||||
?.displayYeildSupplyCryptoBalance,
|
||||
displayYieldSupplyFiatBalance = (currentState as? TokenDetailsBalanceBlockState.Content)
|
||||
?.displayYieldSupplyFiatBalance,
|
||||
balanceSegmentedButtonConfig = currentState.balanceSegmentedButtonConfig,
|
||||
onBalanceSelect = clickIntents::onBalanceSelect,
|
||||
selectedBalanceType = currentState.selectedBalanceType,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
|||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.yield.supply.models.YieldSupplyRewardBalance
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsAppBarMenuConfig
|
||||
|
|
@ -315,13 +316,18 @@ internal class TokenDetailsStateFactory(
|
|||
return balanceSelectStateConverter.convert(buttonConfig)
|
||||
}
|
||||
|
||||
fun getStateWithUpdatedYieldSupplyDisplayBalance(displayBalance: String?): TokenDetailsState {
|
||||
fun getStateWithUpdatedYieldSupplyDisplayBalance(
|
||||
yieldSupplyRewardBalance: YieldSupplyRewardBalance,
|
||||
): TokenDetailsState {
|
||||
val state = currentStateProvider()
|
||||
val balanceState = state.tokenBalanceBlockState
|
||||
return state.copy(
|
||||
tokenBalanceBlockState = when (balanceState) {
|
||||
is TokenDetailsBalanceBlockState.Content ->
|
||||
balanceState.copy(displayYeildSupplyCryptoBalance = displayBalance)
|
||||
balanceState.copy(
|
||||
displayYieldSupplyFiatBalance = yieldSupplyRewardBalance.fiatBalance,
|
||||
displayYieldSupplyCryptoBalance = yieldSupplyRewardBalance.cryptoBalance,
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Error -> balanceState
|
||||
is TokenDetailsBalanceBlockState.Loading -> balanceState
|
||||
},
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ private fun FiatBalance(
|
|||
height = TangemTheme.dimens.size32,
|
||||
),
|
||||
)
|
||||
is TokenDetailsBalanceBlockState.Content -> if (state.displayYeildSupplyCryptoBalance != null &&
|
||||
is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null &&
|
||||
!isBalanceHidden
|
||||
) {
|
||||
TextAnimatedCounter(
|
||||
modifier = modifier,
|
||||
text = state.displayYeildSupplyCryptoBalance,
|
||||
text = state.displayYieldSupplyFiatBalance,
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.primary1,
|
||||
|
|
@ -136,7 +136,7 @@ private fun FiatBalance(
|
|||
} else {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = (state.displayYeildSupplyCryptoBalance ?: state.displayFiatBalance).orMaskWithStars(
|
||||
text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars(
|
||||
isBalanceHidden,
|
||||
),
|
||||
style = TangemTheme.typography.h2.applyBladeBrush(
|
||||
|
|
@ -184,8 +184,9 @@ private fun CryptoBalance(
|
|||
tint = TangemTheme.colors.icon.inactive,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden),
|
||||
TextAnimatedCounter(
|
||||
text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance)
|
||||
.orMaskWithStars(isBalanceHidden),
|
||||
style = TangemTheme.typography.caption2.applyBladeBrush(
|
||||
isEnabled = state.isBalanceFlickering,
|
||||
textColor = TangemTheme.colors.text.tertiary,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.Approve -> R.drawable.ic_doc_24
|
||||
is TransactionType.YieldSupply.DeployContract,
|
||||
is TransactionType.Approve,
|
||||
-> R.drawable.ic_doc_24
|
||||
is TransactionType.Staking.Stake,
|
||||
is TransactionType.Staking.Vote,
|
||||
is TransactionType.Staking.Restake,
|
||||
|
|
@ -51,11 +53,16 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
is TransactionType.Staking.Unstake,
|
||||
is TransactionType.Staking.Withdraw,
|
||||
-> R.drawable.ic_transaction_history_unstaking_24
|
||||
is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24
|
||||
is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24
|
||||
is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24
|
||||
is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24
|
||||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.YieldSupply,
|
||||
is TransactionType.UnknownOperation,
|
||||
TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
|
@ -66,32 +73,76 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
|
||||
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
|
||||
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
|
||||
is TransactionType.YieldSupply -> when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
TransactionType.YieldSupply.Send -> if (isOutgoing) {
|
||||
resourceReference(R.string.common_transfer)
|
||||
} else {
|
||||
resourceReference(R.string.yield_module_transaction_withdraw)
|
||||
}
|
||||
is TransactionType.YieldSupply.DeployContract -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_deploy_contract,
|
||||
)
|
||||
is TransactionType.YieldSupply.InitializeToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_initialize,
|
||||
)
|
||||
is TransactionType.YieldSupply.ReactivateToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_reactivate,
|
||||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
return when (this.type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Topup,
|
||||
-> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
return when (val type = this.type) {
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) {
|
||||
if (type == TransactionType.YieldSupply.Send) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
resourceReference(
|
||||
R.string.transaction_history_transaction_for_address,
|
||||
wrappedList(type.address?.toBriefAddressFormat().orEmpty()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
TransactionType.YieldSupply.Topup -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
}
|
||||
TransactionType.YieldSupply.Send -> {
|
||||
if (isOutgoing) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
val amount =
|
||||
amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
|
|
@ -133,14 +184,21 @@ internal class TxHistoryItemToTransactionStateConverter(
|
|||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun TxInfo.getAmount(): String {
|
||||
if (type is TransactionType.Staking.Vote ||
|
||||
type == TransactionType.Staking.ClaimRewards ||
|
||||
type == TransactionType.Staking.Withdraw ||
|
||||
type == TransactionType.YieldSupply.Enter ||
|
||||
type == TransactionType.YieldSupply.Exit ||
|
||||
type == TransactionType.YieldSupply.Topup
|
||||
) {
|
||||
return ""
|
||||
when (type) {
|
||||
is TransactionType.Staking.Vote,
|
||||
TransactionType.Staking.ClaimRewards,
|
||||
TransactionType.Staking.Withdraw,
|
||||
-> return ""
|
||||
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Token) {
|
||||
when (type) {
|
||||
TransactionType.YieldSupply.Send -> if (!isOutgoing) {
|
||||
return ""
|
||||
}
|
||||
else -> return ""
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
val prefix = when {
|
||||
status == TxInfo.TransactionStatus.Failed -> ""
|
||||
|
|
|
|||
|
|
@ -537,7 +537,7 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
|
||||
walletScreenContentLoader.cancel(action.prevWalletId)
|
||||
tokenListStore.remove(action.prevWalletId)
|
||||
|
||||
|
|
@ -585,7 +585,7 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
fetchWalletContent(userWallet = action.selectedWallet)
|
||||
|
||||
|
|
@ -725,15 +725,15 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchWalletContent(userWallet: UserWallet) {
|
||||
private fun fetchWalletContent(userWallet: UserWallet) {
|
||||
if (userWallet.isLocked) return
|
||||
|
||||
/*
|
||||
* Updating the balance of the current wallet is an essential part of InitializationWallets,
|
||||
* so the coroutine is launched in the current context
|
||||
*/
|
||||
supervisorScope {
|
||||
launch { walletContentFetcher(userWalletId = userWallet.walletId) }
|
||||
modelScope.launch {
|
||||
walletContentFetcher(userWalletId = userWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,9 @@ import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.requireColdWallet
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -41,7 +39,6 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
private val onboardingRepository: OnboardingRepository,
|
||||
private val produceInitialDataTangemPay: ProduceTangemPayInitialDataUseCase,
|
||||
private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
|
|
@ -68,6 +65,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
val issuingBottomSheet = bottomSheetMessage {
|
||||
infoBlock {
|
||||
icon(com.tangem.core.ui.R.drawable.ic_clock_24) {
|
||||
type = MessageBottomSheetUMV2.Icon.Type.Informative
|
||||
backgroundType = MessageBottomSheetUMV2.Icon.BackgroundType.Informative
|
||||
}
|
||||
title = resourceReference(R.string.tangempay_issuing_your_card)
|
||||
|
|
@ -93,7 +91,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
body = resourceReference(R.string.tangempay_failed_to_issue_card_support_description)
|
||||
}
|
||||
secondaryButton {
|
||||
text = resourceReference(R.string.common_contact_support)
|
||||
text = resourceReference(R.string.tangempay_go_to_support)
|
||||
onClick {
|
||||
onPaySupportClick()
|
||||
closeBs()
|
||||
|
|
@ -106,10 +104,8 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
|
||||
override fun onPaySupportClick() {
|
||||
modelScope.launch {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
val userWallet = getUserWalletUseCase.invoke(userWalletId).getOrNull() ?: return@launch
|
||||
val cardInfo = getWalletMetainfoUseCase.invoke(
|
||||
userWallet.requireColdWallet().scanResponse,
|
||||
userWalletId = stateHolder.getSelectedWalletId(),
|
||||
).getOrNull() ?: return@launch
|
||||
|
||||
sendFeedbackEmailUseCase(
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.tokens.model.details.NavigationAction
|
|||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
|
||||
|
|
@ -58,6 +59,8 @@ internal interface WalletContentClickIntents {
|
|||
apy: String,
|
||||
)
|
||||
|
||||
fun onYieldPromoCloseClick()
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
fun onAccountCollapseClick(account: Account)
|
||||
|
|
@ -95,6 +98,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase,
|
||||
private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase,
|
||||
) : BaseWalletClickIntents(), WalletContentClickIntents {
|
||||
|
||||
override fun onDetailsClick() {
|
||||
|
|
@ -194,6 +198,12 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onYieldPromoCloseClick() {
|
||||
modelScope.launch {
|
||||
yieldSupplySetShouldShowMainPromoUseCase(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccountExpandClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens())
|
||||
|
|
|
|||
|
|
@ -317,6 +317,11 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
program = Program.BlackFriday,
|
||||
action = PromotionBannerClicked.BannerAction.Closed(),
|
||||
)
|
||||
PromoId.OnePlusOne -> PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.OnePlusOne,
|
||||
action = PromotionBannerClicked.BannerAction.Closed(),
|
||||
)
|
||||
},
|
||||
|
||||
)
|
||||
|
|
@ -364,6 +369,16 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
)
|
||||
urlOpener.openUrl(BLACK_FRIDAY_PROMO_LINK)
|
||||
}
|
||||
PromoId.OnePlusOne -> {
|
||||
analyticsEventHandler.send(
|
||||
PromotionBannerClicked(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.OnePlusOne,
|
||||
action = PromotionBannerClicked.BannerAction.Clicked(),
|
||||
),
|
||||
)
|
||||
urlOpener.openUrl(ONE_PLUS_ONE_PROMO_LINK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -581,5 +596,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
|
|||
"&utm_source=tangem-app-banner" +
|
||||
"&utm_medium=banner" +
|
||||
"&utm_campaign=BlackFriday2025"
|
||||
const val ONE_PLUS_ONE_PROMO_LINK = "https://tangem.com/pricing/" +
|
||||
"?cat=family" +
|
||||
"&utm_source=tangem-app-banner" +
|
||||
"&utm_medium=banner" +
|
||||
"&utm_campaign=BOGO50"
|
||||
}
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
|
|||
val event = when {
|
||||
customerInfo.orderStatus == OrderStatus.CANCELED -> return // ignore cancelled state on analytics
|
||||
!customerInfo.info.isKycApproved -> return // ignore kyc not approved state on analytics
|
||||
cardInfo != null && productInstance != null -> TangemPayAnalyticsEvents.MainScreenOpened()
|
||||
cardInfo != null && productInstance != null -> return
|
||||
else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
|
|||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.BlackFriday,
|
||||
)
|
||||
is WalletNotification.OnePlusOnePromo -> NoticePromotionBanner(
|
||||
source = AnalyticsParam.ScreensSources.Main,
|
||||
program = Program.OnePlusOne,
|
||||
)
|
||||
is WalletNotification.ReferralPromo -> MainScreen.ReferralPromo()
|
||||
is WalletNotification.VisaPresalePromo -> VisaWaitlistPromo()
|
||||
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
package com.tangem.feature.wallet.presentation.wallet.domain
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -21,12 +20,9 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.onramp.GetOnrampCountryUseCase
|
||||
import com.tangem.domain.onramp.OnrampSepaAvailableUseCase
|
||||
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
|
||||
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
|
||||
|
|
@ -37,7 +33,6 @@ import com.tangem.feature.wallet.impl.R
|
|||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.extensions.addIf
|
||||
import com.tangem.utils.extensions.isPositive
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -47,7 +42,6 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
|
|
@ -60,9 +54,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
private val backupValidator: BackupValidator,
|
||||
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
|
||||
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
|
||||
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val onrampSepaAvailableUseCase: OnrampSepaAvailableUseCase,
|
||||
private val getOnrampCountryUseCase: GetOnrampCountryUseCase,
|
||||
private val notificationsRepository: NotificationsRepository,
|
||||
private val accountDependencies: AccountDependencies,
|
||||
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
|
||||
|
|
@ -99,11 +90,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
isReadyToShowRateAppUseCase().distinctUntilChanged(),
|
||||
isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
|
||||
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId).distinctUntilChanged(),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.VisaPresale)
|
||||
.distinctUntilChanged(),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.Sepa)
|
||||
.distinctUntilChanged(),
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.BlackFriday)
|
||||
shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.OnePlusOne)
|
||||
.distinctUntilChanged(),
|
||||
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
|
||||
.distinctUntilChanged(),
|
||||
|
|
@ -117,11 +104,9 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
val isReadyToShowRating = array[1] as Boolean
|
||||
val isNeedToBackup = array[2] as Boolean
|
||||
val seedPhraseIssueStatus = array[3] as SeedPhraseNotificationsStatus
|
||||
val shouldShowVisaPromo = array[4] as Boolean
|
||||
val shouldShowSepaBanner = array[5] as Boolean
|
||||
val shouldShowBlackFridayPromo = array[6] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[7] as Boolean
|
||||
val shouldAccessCodeSkipped = array[8] as Boolean
|
||||
val shouldShowOnePlusOnePromo = array[4] as Boolean
|
||||
val shouldShowEnablePushesReminderNotification = array[5] as Boolean
|
||||
val shouldAccessCodeSkipped = array[6] as Boolean
|
||||
|
||||
buildList {
|
||||
addUsedOutdatedDataNotification(totalFiatBalance)
|
||||
|
|
@ -135,16 +120,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
shouldAccessCodeSkipped = shouldAccessCodeSkipped,
|
||||
)
|
||||
|
||||
addBlackFridayPromoNotification(clickIntents, shouldShowBlackFridayPromo)
|
||||
|
||||
addVisaPresalePromoNotification(clickIntents, shouldShowVisaPromo)
|
||||
|
||||
addSepaPromoNotification(
|
||||
userWallet = userWallet,
|
||||
flattenCurrencies = flattenCurrencies,
|
||||
clickIntents = clickIntents,
|
||||
shouldShowSepaPromo = shouldShowSepaBanner,
|
||||
)
|
||||
addOnePlusOnePromoNotification(clickIntents, shouldShowOnePlusOnePromo)
|
||||
|
||||
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
|
||||
|
||||
|
|
@ -297,72 +273,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
|
|||
.map(CryptoCurrencyStatus::currency)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addVisaPresalePromoNotification(
|
||||
private fun MutableList<WalletNotification>.addOnePlusOnePromoNotification(
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowPromo: Boolean,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.VisaPresalePromo(
|
||||
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.VisaPresale) },
|
||||
onClick = { clickIntents.onPromoClick(promoId = PromoId.VisaPresale) },
|
||||
element = WalletNotification.OnePlusOnePromo(
|
||||
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.OnePlusOne) },
|
||||
onClick = { clickIntents.onPromoClick(promoId = PromoId.OnePlusOne) },
|
||||
),
|
||||
condition = shouldShowPromo,
|
||||
)
|
||||
}
|
||||
|
||||
private fun MutableList<WalletNotification>.addBlackFridayPromoNotification(
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowPromo: Boolean,
|
||||
) {
|
||||
addIf(
|
||||
element = WalletNotification.BlackFridayPromo(
|
||||
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.BlackFriday) },
|
||||
onClick = { clickIntents.onPromoClick(promoId = PromoId.BlackFriday) },
|
||||
),
|
||||
condition = shouldShowPromo,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<WalletNotification>.addSepaPromoNotification(
|
||||
userWallet: UserWallet,
|
||||
flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
clickIntents: WalletClickIntents,
|
||||
shouldShowSepaPromo: Boolean,
|
||||
) {
|
||||
val currencies = if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
|
||||
flattenCurrencies.map { statuses -> statuses.map { it.currency } }.getOrNull() ?: run {
|
||||
Timber.e("Error on getting crypto currency list")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
getCryptoCurrenciesUseCase(userWalletId = userWallet.walletId).getOrElse {
|
||||
Timber.e("Error on getting crypto currency list")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val bitcoinCurrency = currencies.find { isBitcoin(it.network.rawId) } ?: return
|
||||
|
||||
val country = getOnrampCountryUseCase.invokeSync(userWallet).getOrElse {
|
||||
Timber.e("Error on getting onramp country")
|
||||
return
|
||||
}
|
||||
|
||||
val isSepaAvailable = onrampSepaAvailableUseCase(
|
||||
userWallet = userWallet,
|
||||
country = country,
|
||||
cryptoCurrency = bitcoinCurrency,
|
||||
)
|
||||
|
||||
addIf(
|
||||
element = WalletNotification.Sepa(
|
||||
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.Sepa) },
|
||||
onClick = { clickIntents.onPromoClick(promoId = PromoId.Sepa, bitcoinCurrency) },
|
||||
),
|
||||
condition = shouldShowSepaPromo && isSepaAvailable,
|
||||
)
|
||||
}
|
||||
|
||||
// private fun MutableList<WalletNotification>.addYieldSupplyNotifications(
|
||||
// flattenCurrencies: Lce<TokenListError, List<CryptoCurrencyStatus>>,
|
||||
// ) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -45,6 +46,7 @@ internal class MultiWalletContentLoader(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
|
||||
|
|
@ -63,6 +65,7 @@ internal class MultiWalletContentLoader(
|
|||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
).let(::add)
|
||||
|
||||
WalletNFTListSubscriber(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -44,6 +45,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
|
||||
|
|
@ -72,6 +74,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
tangemPayFeatureToggles = tangemPayFeatureToggles,
|
||||
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
override fun create(): List<WalletSubscriber> {
|
||||
|
|
@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
).let(::add)
|
||||
MultiWalletWarningsSubscriber(
|
||||
userWallet = userWallet,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.domain.promo.GetStoryContentUseCase
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
|
||||
|
|
@ -36,6 +37,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
|
||||
|
|
@ -55,6 +57,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -376,6 +376,23 @@ sealed class WalletNotification(val config: NotificationConfig) {
|
|||
),
|
||||
)
|
||||
|
||||
data class OnePlusOnePromo(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onClick: () -> Unit,
|
||||
) : WalletNotification(
|
||||
config = NotificationConfig(
|
||||
title = resourceReference(R.string.notification_one_plus_one_title),
|
||||
subtitle = resourceReference(R.string.notification_one_plus_one_text),
|
||||
iconResId = R.drawable.img_one_plus_one_promo,
|
||||
onCloseClick = onCloseClick,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.notification_one_plus_one_button),
|
||||
onClick = onClick,
|
||||
),
|
||||
iconSize = 54.dp,
|
||||
),
|
||||
)
|
||||
|
||||
data class PushNotifications(
|
||||
val onCloseClick: () -> Unit,
|
||||
val onEnabledClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SetTokenListTransformer(
|
||||
private val params: TokenConverterParams,
|
||||
private val userWallet: UserWallet,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
|
|
@ -63,6 +65,7 @@ internal class SetTokenListTransformer(
|
|||
clickIntents = clickIntents,
|
||||
yieldModuleApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
).convert(value = this)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.txhistory.models.TxHistoryStateError
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -17,6 +18,7 @@ internal class SetTxHistoryCountErrorTransformer(
|
|||
private val error: TxHistoryStateError,
|
||||
private val pendingTransactions: Set<TxInfo>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val currency: CryptoCurrency,
|
||||
) : WalletStateTransformer(userWallet.walletId) {
|
||||
|
||||
private val txHistoryItemConverter by lazy {
|
||||
|
|
@ -26,6 +28,7 @@ internal class SetTxHistoryCountErrorTransformer(
|
|||
}
|
||||
|
||||
TxHistoryItemStateConverter(
|
||||
currency = currency,
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -28,17 +28,25 @@ import kotlinx.collections.immutable.PersistentList
|
|||
import kotlinx.collections.immutable.mutate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokenListStateConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val params: TokenConverterParams,
|
||||
private val selectedWallet: UserWallet,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
private val yieldModuleApyMap: Map<String, String>,
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<WalletTokensListState, WalletTokensListState> {
|
||||
|
||||
private val yieldSupplyPromoBannerKeyConverter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap,
|
||||
shouldShowMainPromo,
|
||||
)
|
||||
|
||||
private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit =
|
||||
{ accountId, currencyStatus ->
|
||||
clickIntents.onTokenItemClick(selectedWallet.walletId, currencyStatus)
|
||||
|
|
@ -63,10 +71,12 @@ internal class TokenListStateConverter(
|
|||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKeyConverter.convert(params),
|
||||
stakingApyMap = stakingApyMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) },
|
||||
onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick,
|
||||
)
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.network.TxInfo.TransactionStatus
|
||||
import com.tangem.domain.models.network.TxInfo.TransactionType
|
||||
|
|
@ -20,6 +21,7 @@ import com.tangem.utils.converter.Converter
|
|||
import com.tangem.utils.toBriefAddressFormat
|
||||
|
||||
internal class TxHistoryItemStateConverter(
|
||||
private val currency: CryptoCurrency,
|
||||
private val symbol: String,
|
||||
private val decimals: Int,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
|
|
@ -49,7 +51,9 @@ internal class TxHistoryItemStateConverter(
|
|||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.Approve -> R.drawable.ic_doc_24
|
||||
is TransactionType.YieldSupply.DeployContract,
|
||||
is TransactionType.Approve,
|
||||
-> R.drawable.ic_doc_24
|
||||
is TransactionType.Staking.Stake,
|
||||
is TransactionType.Staking.Vote,
|
||||
is TransactionType.Staking.Restake,
|
||||
|
|
@ -59,11 +63,16 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Staking.Unstake,
|
||||
is TransactionType.Staking.Withdraw,
|
||||
-> R.drawable.ic_transaction_history_unstaking_24
|
||||
is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24
|
||||
is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24
|
||||
is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24
|
||||
is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24
|
||||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.YieldSupply,
|
||||
is TransactionType.UnknownOperation,
|
||||
TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
|
@ -74,32 +83,81 @@ internal class TxHistoryItemStateConverter(
|
|||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
is TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
|
||||
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
|
||||
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
|
||||
is TransactionType.YieldSupply -> when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
TransactionType.YieldSupply.Send -> if (isOutgoing) {
|
||||
resourceReference(R.string.common_transfer)
|
||||
} else {
|
||||
resourceReference(R.string.yield_module_transaction_withdraw)
|
||||
}
|
||||
is TransactionType.YieldSupply.DeployContract -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_deploy_contract,
|
||||
)
|
||||
is TransactionType.YieldSupply.InitializeToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_initialize,
|
||||
)
|
||||
is TransactionType.YieldSupply.ReactivateToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_reactivate,
|
||||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
return when (this.type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Topup,
|
||||
-> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = symbol, decimals = decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
return when (val type = this.type) {
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) {
|
||||
resourceReference(
|
||||
R.string.transaction_history_transaction_for_address,
|
||||
wrappedList(type.address?.toBriefAddressFormat().orEmpty()),
|
||||
)
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_enter_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
TransactionType.YieldSupply.Topup -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_topup_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
TransactionType.YieldSupply.Send -> {
|
||||
if (isOutgoing) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
val amount =
|
||||
amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
|
|
@ -147,14 +205,18 @@ internal class TxHistoryItemStateConverter(
|
|||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun TxInfo.getAmount(): String {
|
||||
if (type is TransactionType.Staking.Vote ||
|
||||
type == TransactionType.Staking.ClaimRewards ||
|
||||
type == TransactionType.Staking.Withdraw ||
|
||||
type == TransactionType.YieldSupply.Enter ||
|
||||
type == TransactionType.YieldSupply.Exit ||
|
||||
type == TransactionType.YieldSupply.Topup
|
||||
) {
|
||||
return ""
|
||||
when (type) {
|
||||
is TransactionType.Staking.Vote,
|
||||
TransactionType.Staking.ClaimRewards,
|
||||
TransactionType.Staking.Withdraw,
|
||||
-> return ""
|
||||
|
||||
is TransactionType.YieldSupply -> {
|
||||
if (currency is CryptoCurrency.Token && type == TransactionType.YieldSupply.Send && !isOutgoing) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
val prefix = when {
|
||||
status == TransactionStatus.Failed -> ""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import com.tangem.lib.crypto.BlockchainUtils
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class YieldSupplyPromoBannerKeyConverter(
|
||||
private val yieldModuleApyMap: Map<String, BigDecimal>,
|
||||
private val shouldShowMainPromo: Boolean,
|
||||
) : Converter<TokenConverterParams, String?> {
|
||||
|
||||
override fun convert(value: TokenConverterParams): String? {
|
||||
if (!shouldShowMainPromo) return null
|
||||
|
||||
val currencies = when (value) {
|
||||
is TokenConverterParams.Wallet -> value.tokenList.flattenCurrencies()
|
||||
is TokenConverterParams.Account -> value.accountList.flattenCurrencies()
|
||||
}.filter { status ->
|
||||
status.value is CryptoCurrencyStatus.Loaded ||
|
||||
status.value is CryptoCurrencyStatus.Custom
|
||||
}
|
||||
|
||||
val tokens = currencies.filter { it.currency is CryptoCurrency.Token }
|
||||
|
||||
if (tokens.any { it.value.yieldSupplyStatus?.isActive == true }) return null
|
||||
if (yieldModuleApyMap.isEmpty()) return null
|
||||
|
||||
val max = tokens.asSequence()
|
||||
.mapNotNull { status ->
|
||||
val token = status.currency as? CryptoCurrency.Token ?: return@mapNotNull null
|
||||
val tokenKey = token.yieldSupplyKey()
|
||||
val shouldIgnoreCase = BlockchainUtils.isCaseInsensitiveContractAddress(token.network.rawId)
|
||||
|
||||
val matchedKey = yieldModuleApyMap.keys.firstOrNull { mapKey ->
|
||||
mapKey.equals(tokenKey, shouldIgnoreCase)
|
||||
} ?: return@mapNotNull null
|
||||
|
||||
status to matchedKey
|
||||
}
|
||||
.maxByOrNull { (status, _) -> status.value.amount ?: BigDecimal.ZERO }
|
||||
|
||||
return max?.second
|
||||
}
|
||||
}
|
||||
|
|
@ -5,16 +5,18 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.utils.coroutines.combine6
|
||||
import com.tangem.utils.coroutines.combine7
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Subscriber that monitors account list related data and updates the wallet state accordingly.
|
||||
|
|
@ -30,19 +32,21 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
override val clickIntents: WalletClickIntents,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicAccountListSubscriber() {
|
||||
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine6(
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = combine7(
|
||||
flow1 = getAccountStatusListFlow(),
|
||||
flow2 = getAppCurrencyFlow(),
|
||||
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
|
||||
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
|
||||
flow5 = yieldSupplyApyFlow(),
|
||||
flow6 = stakingApyFlow(),
|
||||
flow7 = yieldSupplyGetShouldShowMainPromoFlow(),
|
||||
transform = ::updateState,
|
||||
)
|
||||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> {
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> {
|
||||
return yieldSupplyApyFlowUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +54,10 @@ internal class AccountListSubscriber @AssistedInject constructor(
|
|||
return stakingApyFlowUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> {
|
||||
return yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(userWallet: UserWallet): AccountListSubscriber
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenCon
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Basic implementation of [WalletSubscriber] for wallet with accounts.
|
||||
|
|
@ -46,8 +47,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
appCurrency: AppCurrency,
|
||||
expandedAccounts: Set<AccountId>,
|
||||
isAccountMode: Boolean,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean = false,
|
||||
) {
|
||||
val mainAccount = accountList.mainAccount
|
||||
|
||||
|
|
@ -66,11 +68,18 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
portfolioId = PortfolioId(mainAccount.accountId),
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
isAccountMode -> {
|
||||
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
|
||||
updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap)
|
||||
updateContent(
|
||||
params = convertParams,
|
||||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,8 +88,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
maybeTokenList: Lce<TokenListError, TokenList>,
|
||||
appCurrency: AppCurrency,
|
||||
portfolioId: PortfolioId,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
|
|
@ -110,14 +120,16 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateContent(
|
||||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String> = emptyMap(),
|
||||
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
|
||||
stakingApyMap: Map<String, List<Yield.Validator>> = emptyMap(),
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
stateController.update(
|
||||
SetTokenListTransformer(
|
||||
|
|
@ -127,6 +139,7 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
|
|||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
|
|
@ -28,6 +29,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Deprecated("Use AccountListSubscriber instead")
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -40,6 +42,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
private val stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : WalletSubscriber() {
|
||||
|
||||
private val sendAnalyticsJobHolder = JobHolder()
|
||||
|
|
@ -69,7 +72,8 @@ internal abstract class BasicTokenListSubscriber(
|
|||
flow2 = appCurrencyFlow(),
|
||||
flow3 = yieldSupplyApyFlow(),
|
||||
flow4 = stakingApyFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap ->
|
||||
flow5 = yieldSupplyGetShouldShowMainPromoFlow(),
|
||||
transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap, shouldShowMainPromo ->
|
||||
val tokenList = maybeTokenList.getOrElse(
|
||||
ifLoading = { maybeContent ->
|
||||
val isRefreshing = stateHolder.getWalletState(userWallet.walletId)
|
||||
|
|
@ -98,6 +102,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
appCurrency = appCurrency,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
)
|
||||
|
||||
walletWithFundsChecker.check(tokenList)
|
||||
|
|
@ -122,8 +127,9 @@ internal abstract class BasicTokenListSubscriber(
|
|||
private fun updateContent(
|
||||
params: TokenConverterParams,
|
||||
appCurrency: AppCurrency,
|
||||
yieldSupplyApyMap: Map<String, String>,
|
||||
yieldSupplyApyMap: Map<String, BigDecimal>,
|
||||
stakingApyMap: Map<String, List<Yield.Validator>>,
|
||||
shouldShowMainPromo: Boolean,
|
||||
) {
|
||||
stateHolder.update(
|
||||
SetTokenListTransformer(
|
||||
|
|
@ -133,6 +139,7 @@ internal abstract class BasicTokenListSubscriber(
|
|||
clickIntents = clickIntents,
|
||||
yieldSupplyApyMap = yieldSupplyApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
shouldShowMainPromo = shouldShowMainPromo,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -146,9 +153,12 @@ internal abstract class BasicTokenListSubscriber(
|
|||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, String>> = yieldSupplyApyFlowUseCase()
|
||||
private fun yieldSupplyApyFlow(): Flow<Map<String, BigDecimal>> = yieldSupplyApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun stakingApyFlow(): Flow<Map<String, List<Yield.Validator>>> = stakingApyFlowUseCase()
|
||||
.distinctUntilChanged()
|
||||
|
||||
private fun yieldSupplyGetShouldShowMainPromoFlow(): Flow<Boolean> = yieldSupplyGetShouldShowMainPromoUseCase()
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
|||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
|
|
@ -32,6 +33,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -41,6 +43,7 @@ internal class MultiWalletTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
|
||||
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
|
|
@ -27,6 +28,7 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase: StakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) : BasicTokenListSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
|
|
@ -36,6 +38,7 @@ internal class SingleWalletWithTokenListSubscriber(
|
|||
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
|
||||
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
|
||||
stakingApyFlowUseCase = stakingApyFlowUseCase,
|
||||
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
|
||||
) {
|
||||
|
||||
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.paging.cachedIn
|
|||
import androidx.paging.map
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -58,7 +59,7 @@ internal class TxHistorySubscriber(
|
|||
refresh = isRefresh,
|
||||
).map { it.cachedIn(coroutineScope) }
|
||||
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems)
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems, status.currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,18 +68,19 @@ internal class TxHistorySubscriber(
|
|||
private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItemCount.fold(
|
||||
ifLeft = {
|
||||
ifLeft = { error ->
|
||||
SetTxHistoryCountErrorTransformer(
|
||||
userWallet = userWallet,
|
||||
error = it,
|
||||
error = error,
|
||||
pendingTransactions = status.value.pendingTransactions,
|
||||
currency = status.currency,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { txCount ->
|
||||
SetTxHistoryCountTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionsCount = it,
|
||||
transactionsCount = txCount,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
|
|
@ -86,7 +88,7 @@ internal class TxHistorySubscriber(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) {
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) {
|
||||
stateHolder.update(
|
||||
maybeTxHistoryItems.fold(
|
||||
ifLeft = {
|
||||
|
|
@ -102,6 +104,7 @@ internal class TxHistorySubscriber(
|
|||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
currency = currency,
|
||||
)
|
||||
|
||||
SetTxHistoryItemsTransformer(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.paging.cachedIn
|
|||
import androidx.paging.map
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -51,7 +52,7 @@ internal class TxHistorySubscriberV2(
|
|||
refresh = isRefresh,
|
||||
).map { it.cachedIn(coroutineScope) }
|
||||
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems)
|
||||
setLoadedTxHistoryState(maybeTxHistoryItems, currency = status.currency)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,18 +61,19 @@ internal class TxHistorySubscriberV2(
|
|||
private fun setLoadingTxHistoryState(maybeTxHistoryItemCount: MaybeTxHistoryCount, status: CryptoCurrencyStatus) {
|
||||
stateController.update(
|
||||
maybeTxHistoryItemCount.fold(
|
||||
ifLeft = {
|
||||
ifLeft = { error ->
|
||||
SetTxHistoryCountErrorTransformer(
|
||||
userWallet = userWallet,
|
||||
error = it,
|
||||
error = error,
|
||||
pendingTransactions = status.value.pendingTransactions,
|
||||
clickIntents = clickIntents,
|
||||
currency = status.currency,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { txCount ->
|
||||
SetTxHistoryCountTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
transactionsCount = it,
|
||||
transactionsCount = txCount,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
},
|
||||
|
|
@ -79,7 +81,7 @@ internal class TxHistorySubscriberV2(
|
|||
)
|
||||
}
|
||||
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems) {
|
||||
private fun setLoadedTxHistoryState(maybeTxHistoryItems: MaybeTxHistoryItems, currency: CryptoCurrency) {
|
||||
stateController.update(
|
||||
maybeTxHistoryItems.fold(
|
||||
ifLeft = {
|
||||
|
|
@ -92,6 +94,7 @@ internal class TxHistorySubscriberV2(
|
|||
ifRight = { itemsFlow ->
|
||||
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
val itemConverter = TxHistoryItemStateConverter(
|
||||
currency = currency,
|
||||
symbol = blockchain.currency,
|
||||
decimals = blockchain.decimals(),
|
||||
clickIntents = clickIntents,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.currency.yieldSupplyKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class YieldSupplyPromoBannerKeyConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN promo disabled WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xABCDEF")
|
||||
val status = createStatus(token = token, amount = BigDecimal.ONE, isYieldActive = false)
|
||||
val tokenList = ungroupedTokenList(status)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = tokenList,
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.10")),
|
||||
shouldShowMainPromo = false,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty apy map WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xA1")
|
||||
val status = createStatus(token = token, amount = BigDecimal("2.0"), isYieldActive = false)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = emptyMap(),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active yield token present WHEN convert THEN return null`() {
|
||||
val token = createToken(networkId = "ethereum", backendId = "ethereum", contract = "0xAA")
|
||||
val statusActive = createStatus(token = token, amount = BigDecimal("5"), isYieldActive = true)
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusActive),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = mapOf(token.yieldSupplyKey() to BigDecimal("0.12")),
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN multiple candidates EVM case insensitive WHEN convert THEN return key of max amount`() {
|
||||
val evmNetworkId = "ETH"
|
||||
val tokenSmall = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xAbCd")
|
||||
val tokenBig = createToken(networkId = evmNetworkId, backendId = evmNetworkId, contract = "0xBEEF")
|
||||
|
||||
val statusSmall = createStatus(token = tokenSmall, amount = BigDecimal("1.00"), isYieldActive = false)
|
||||
val statusBig = createStatus(token = tokenBig, amount = BigDecimal("10.00"), isYieldActive = false)
|
||||
|
||||
val apyMap = mapOf(
|
||||
"${tokenSmall.network.backendId}_${tokenSmall.contractAddress.lowercase()}" to BigDecimal("0.05"),
|
||||
"${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}" to BigDecimal("0.15"),
|
||||
)
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(statusSmall, statusBig),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
val expectedKey = "${tokenBig.network.backendId}_${tokenBig.contractAddress.uppercase()}"
|
||||
assertThat(result).isEqualTo(expectedKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non evm case sensitive mismatch WHEN convert THEN return null`() {
|
||||
val nonEvmId = "xrp"
|
||||
val token = createToken(networkId = nonEvmId, backendId = nonEvmId, contract = "rAbC123")
|
||||
val status = createStatus(token = token, amount = BigDecimal("3"), isYieldActive = false)
|
||||
|
||||
val mismatchedKey = "${token.network.backendId}_${token.contractAddress.lowercase()}"
|
||||
val apyMap = mapOf(mismatchedKey to BigDecimal("0.07"))
|
||||
|
||||
val params = TokenConverterParams.Wallet(
|
||||
portfolioId = PortfolioId.Wallet(UserWalletId("00")),
|
||||
tokenList = ungroupedTokenList(status),
|
||||
)
|
||||
val converter = YieldSupplyPromoBannerKeyConverter(
|
||||
yieldModuleApyMap = apyMap,
|
||||
shouldShowMainPromo = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(params)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
private fun ungroupedTokenList(vararg statuses: CryptoCurrencyStatus): TokenList.Ungrouped {
|
||||
return TokenList.Ungrouped(
|
||||
totalFiatBalance = com.tangem.domain.models.TotalFiatBalance.Loaded(
|
||||
amount = BigDecimal.ZERO,
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
sortedBy = com.tangem.domain.models.TokensSortType.NONE,
|
||||
currencies = statuses.toList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createStatus(
|
||||
token: CryptoCurrency.Token,
|
||||
amount: BigDecimal,
|
||||
isYieldActive: Boolean,
|
||||
): CryptoCurrencyStatus {
|
||||
val networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
value = "addr",
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
),
|
||||
)
|
||||
val value = CryptoCurrencyStatus.Custom(
|
||||
amount = amount,
|
||||
fiatAmount = null,
|
||||
fiatRate = null,
|
||||
priceChange = null,
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = if (isYieldActive) {
|
||||
YieldSupplyStatus(
|
||||
isActive = true,
|
||||
isInitialized = true,
|
||||
isAllowedToSpend = true,
|
||||
effectiveProtocolBalance = null,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
hasCurrentNetworkTransactions = false,
|
||||
pendingTransactions = emptySet(),
|
||||
networkAddress = networkAddress,
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
)
|
||||
return CryptoCurrencyStatus(
|
||||
currency = token,
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createToken(networkId: String, backendId: String, contract: String): CryptoCurrency.Token {
|
||||
val network = Network(
|
||||
id = Network.ID(value = networkId, derivationPath = Network.DerivationPath.None),
|
||||
backendId = backendId,
|
||||
name = backendId,
|
||||
currencySymbol = "SYM",
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
isTestnet = false,
|
||||
standardType = when (backendId) {
|
||||
"ethereum" -> Network.StandardType.ERC20
|
||||
else -> Network.StandardType.Unspecified("UNSPEC")
|
||||
},
|
||||
hasFiatFeeRate = false,
|
||||
canHandleTokens = true,
|
||||
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||
nameResolvingType = Network.NameResolvingType.NONE,
|
||||
)
|
||||
return CryptoCurrency.Token(
|
||||
id = CryptoCurrency.ID(
|
||||
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||
body = CryptoCurrency.ID.Body.NetworkId(networkId),
|
||||
suffix = CryptoCurrency.ID.Suffix.ContractAddress(contract),
|
||||
),
|
||||
network = network,
|
||||
name = "Token",
|
||||
symbol = "TKN",
|
||||
decimals = 18,
|
||||
iconUrl = null,
|
||||
isCustom = false,
|
||||
contractAddress = contract,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,4 +20,8 @@ internal interface WcCommonTransactionModel {
|
|||
fun showSuccessSignMessage(message: TextReference = resourceReference(R.string.wc_successfully_signed)) {
|
||||
messageSender.send(ToastMessage(message = message))
|
||||
}
|
||||
|
||||
fun showSuccessAddedMessage(message: TextReference = resourceReference(R.string.common_added)) {
|
||||
messageSender.send(ToastMessage(message = message))
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +111,7 @@ internal class WcAddNetworkModel @Inject constructor(
|
|||
modelScope.launch {
|
||||
_uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = true)) }
|
||||
useCase.approve().getOrNull()?.let {
|
||||
showSuccessSignMessage()
|
||||
showSuccessAddedMessage()
|
||||
router.pop()
|
||||
} ?: run {
|
||||
_uiState.update { it?.copy(transaction = it.transaction.copy(isLoading = false)) }
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val appRouter: AppRouter,
|
||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyStopEarningComponent.ModelCallback,
|
||||
YieldSupplyApproveComponent.ModelCallback {
|
||||
|
||||
|
|
@ -241,11 +242,13 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
|||
userWalletId,
|
||||
cryptoCurrencyStatusFlow.value,
|
||||
).onRight { minAmount ->
|
||||
val dustAmount = yieldSupplyGetDustMinAmountUseCase(minAmount = minAmount, appCurrency = appCurrency)
|
||||
uiState.update(
|
||||
YieldSupplyActiveMinAmountTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
|
||||
appCurrency = appCurrency,
|
||||
minAmount = minAmount,
|
||||
dustMinAmount = dustAmount,
|
||||
analyticsHandler = analyticsHandler,
|
||||
onApprove = ::onApprove,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -28,11 +28,12 @@ import java.math.BigDecimal
|
|||
* - Builds the fee policy note text using the minimum amount.
|
||||
* - Adds contextual notifications:
|
||||
* - Approval required notification when spending is not yet allowed (emits analytics on CTA).
|
||||
* - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount].
|
||||
* - "Not all amount supplied" info when the not-supplied balance exceeds the dust threshold [dustMinAmount].
|
||||
*
|
||||
* @property cryptoCurrencyStatus Current currency status used to calculate values and flags.
|
||||
* @property appCurrency Preferred fiat currency for formatting.
|
||||
* @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units).
|
||||
* @property dustMinAmount Threshold used to detect dust/not-supplied balance (in crypto units).
|
||||
* @property analyticsHandler Analytics reporter for user actions.
|
||||
* @property onApprove Action invoked when the "Approve" notification button is tapped.
|
||||
*/
|
||||
|
|
@ -40,6 +41,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
|
|||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val minAmount: BigDecimal,
|
||||
private val dustMinAmount: BigDecimal,
|
||||
private val analyticsHandler: AnalyticsEventHandler,
|
||||
private val onApprove: () -> Unit,
|
||||
) : Transformer<YieldSupplyActiveContentUM> {
|
||||
|
|
@ -89,7 +91,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
|
|||
}
|
||||
|
||||
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
|
||||
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) {
|
||||
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull()
|
||||
val formattedAmount =
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.yield.supply.impl.main.model
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import android.os.SystemClock
|
||||
import com.tangem.common.routing.AppRoute.YieldSupplyPromo
|
||||
|
|
@ -12,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -51,6 +54,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val appRouter: AppRouter,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
|
|
@ -61,6 +65,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||
private val yieldSupplyRepository: YieldSupplyRepository,
|
||||
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
|
||||
) : Model(), YieldSupplyClickIntents {
|
||||
|
||||
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
|
||||
|
|
@ -69,6 +74,7 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
|
||||
|
||||
private val cryptoCurrency = params.cryptoCurrency
|
||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||
var userWallet: UserWallet by Delegates.notNull()
|
||||
|
||||
private val fetchCurrencyJobHolder = JobHolder()
|
||||
|
|
@ -81,10 +87,17 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun checkIfYieldSupplyIsAvailable() {
|
||||
modelScope.launch(dispatchers.io) {
|
||||
modelScope.launch(dispatchers.default) {
|
||||
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
|
||||
if (isAvailable) {
|
||||
subscribeOnCurrencyStatusUpdates()
|
||||
singleNetworkStatusFetcher(
|
||||
params = SingleNetworkStatusFetcher.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -334,7 +347,11 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus)
|
||||
.getOrNull()
|
||||
if (minAmount != null) {
|
||||
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
|
||||
val dustAmount = yieldSupplyGetDustMinAmountUseCase(
|
||||
minAmount = minAmount,
|
||||
appCurrency = appCurrency,
|
||||
)
|
||||
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue