Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-12 21:01:59 +03:00
parent 9aa840f892
commit b654ab08a7
15 changed files with 204 additions and 36 deletions

View file

@ -39,6 +39,23 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) {
),
)
data class SwapPromo(
val onSwapClick: () -> Unit,
val onCloseClick: () -> Unit,
) : TokenDetailsNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.token_swap_promotion_title),
subtitle = resourceReference(id = R.string.token_swap_promotion_message),
iconResId = R.drawable.ic_swap_promo_34,
backgroundResId = R.drawable.img_swap_promo_banner_background,
onCloseClick = onCloseClick,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(id = com.tangem.core.ui.R.string.token_swap_promotion_button),
onClick = onSwapClick,
),
),
)
object NetworksUnreachable : Warning(
title = resourceReference(R.string.warning_network_unreachable_title),
subtitle = resourceReference(R.string.warning_network_unreachable_message),

View file

@ -45,6 +45,10 @@ internal class TokenDetailsNotificationConverter(
is CryptoCurrencyWarning.HasPendingTransactions -> TokenDetailsNotification.HasPendingTransactions(
coinSymbol = warning.blockchainSymbol,
)
is CryptoCurrencyWarning.SwapPromo -> TokenDetailsNotification.SwapPromo(
onSwapClick = clickIntents::onSwapClick,
onCloseClick = clickIntents::onSwapPromoDismiss,
)
}
}
}

View file

@ -28,6 +28,7 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBotto
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationWithBackground
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.event.EventEffect
@ -104,14 +105,21 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
key = { it::class.java },
contentType = { it.config::class.java },
itemContent = {
Notification(
modifier = itemModifier.animateItemPlacement(),
config = it.config,
iconTint = when (it) {
is TokenDetailsNotification.Warning -> null
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
},
)
if (it is TokenDetailsNotification.SwapPromo) {
NotificationWithBackground(
config = it.config,
modifier = itemModifier.animateItemPlacement(),
)
} else {
Notification(
modifier = itemModifier.animateItemPlacement(),
config = it.config,
iconTint = when (it) {
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
else -> null
},
)
}
},
)
if (state.isMarketPriceAvailable) {

View file

@ -3,6 +3,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
import com.tangem.core.ui.components.bottomsheets.tokenreceive.AddressModel
import com.tangem.domain.tokens.model.CryptoCurrency
@Suppress("TooManyFunctions")
interface TokenDetailsClickIntents {
fun onBackClick()
@ -42,4 +43,6 @@ interface TokenDetailsClickIntents {
fun onSwapTransactionClick(txId: String)
fun onGoToProviderClick(url: String)
fun onSwapPromoDismiss()
}

View file

@ -20,6 +20,7 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrency
@ -35,8 +36,8 @@ import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.swap.domain.SwapTransactionRepository
import com.tangem.feature.swap.domain.api.SwapRepository
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTransactionsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
@ -74,6 +75,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase,
private val shouldShowSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
private val swapRepository: SwapRepository,
private val swapTransactionRepository: SwapTransactionRepository,
private val swapTransactionStatusStore: SwapTransactionStatusStore,
@ -547,6 +549,12 @@ internal class TokenDetailsViewModel @Inject constructor(
router.openUrl(url)
}
override fun onSwapPromoDismiss() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoTokenUseCase.neverToShow()
}
}
private companion object {
const val EXCHANGE_STATUS_UPDATE_DELAY = 10_000L
}

View file

@ -166,4 +166,16 @@ sealed class WalletNotification(val config: NotificationConfig) {
onCloseClick = onCloseClick,
),
)
data class SwapPromo(
val onCloseClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.main_swap_promotion_title),
subtitle = resourceReference(id = R.string.main_swap_promotion_message),
iconResId = R.drawable.ic_swap_promo_34,
backgroundResId = R.drawable.img_swap_promo_banner_background,
onCloseClick = onCloseClick,
),
)
}

View file

@ -5,6 +5,7 @@ import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationWithBackground
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -24,17 +25,24 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
key = { it::class.java },
contentType = { it::class.java },
itemContent = {
Notification(
config = it.config,
modifier = modifier.animateItemPlacement(),
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
is WalletNotification.Warning -> null
},
)
if (it is WalletNotification.SwapPromo) {
NotificationWithBackground(
config = it.config,
modifier = modifier.animateItemPlacement(),
)
} else {
Notification(
config = it.config,
modifier = modifier.animateItemPlacement(),
iconTint = when (it) {
is WalletNotification.Critical -> TangemTheme.colors.icon.warning
is WalletNotification.Informational -> TangemTheme.colors.icon.accent
is WalletNotification.RateApp -> TangemTheme.colors.icon.attention
is WalletNotification.UnlockWallets -> TangemTheme.colors.icon.primary1
else -> null
},
)
}
},
)
}

View file

@ -72,4 +72,6 @@ internal interface WalletClickIntents {
fun onExploreClick()
fun onTransactionClick(txHash: String)
fun onCloseSwapPromoNotificationClick()
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.tokens.GetMissedAddressesCryptoCurrenciesUseCase
import com.tangem.domain.tokens.error.GetCurrenciesError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -26,16 +27,21 @@ import kotlinx.coroutines.flow.flowOf
* @property isDemoCardUseCase use case that checks if card is demo
* @property isReadyToShowRateAppUseCase use case that checks if card is user already rate app
* @property isNeedToBackupUseCase use case that checks if wallet need backup cards
* @property getMissedAddressCryptoCurrenciesUseCase use case that gets missed address crypto currencies
* @property hasSingleWalletSignedHashesUseCase use case that checks if single wallet signed hashes
* @property shouldShowSwapPromoWalletUseCase use case that checks if should show swap promo
* @property clickIntents screen click intents
*
[REDACTED_AUTHOR]
*/
@Suppress("LongParameterList")
internal class WalletNotificationsListFactory(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val getMissedAddressCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val clickIntents: WalletClickIntents,
) {
@ -51,9 +57,12 @@ internal class WalletNotificationsListFactory(
flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(selectedWallet.walletId).conflate(),
flow4 = getMissedAddressCryptoCurrenciesUseCase(selectedWallet.walletId).conflate(),
) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies ->
flow5 = shouldShowSwapPromoWalletUseCase().conflate(),
) { hasSignedHashes, isReadyToShowRating, isNeedToBackup, maybeMissedAddressCurrencies, isShowSwapPromo ->
readyForRateAppNotification = true
buildList {
addSwapPromoNotification(isShowSwapPromo, cardTypesResolver)
addCriticalNotifications(cardTypesResolver)
addInformationalNotifications(cardTypesResolver, maybeMissedAddressCurrencies)
@ -77,6 +86,18 @@ internal class WalletNotificationsListFactory(
}
}
private fun MutableList<WalletNotification>.addSwapPromoNotification(
showSwapPromo: Boolean,
cardTypesResolver: CardTypesResolver,
) {
addIf(
element = WalletNotification.SwapPromo(
clickIntents::onCloseSwapPromoNotificationClick,
),
condition = showSwapPromo && cardTypesResolver.isMultiwalletAllowed(),
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf(
element = WalletNotification.Critical.DevCard,

View file

@ -128,6 +128,7 @@ internal class WalletViewModel @Inject constructor(
private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val scanCardToUnlockWalletUseCase: ScanCardToUnlockWalletClickHandler,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
isNeedToBackupUseCase: IsNeedToBackupUseCase,
getMissedAddressesCryptoCurrenciesUseCase: GetMissedAddressesCryptoCurrenciesUseCase,
@ -146,6 +147,7 @@ internal class WalletViewModel @Inject constructor(
isNeedToBackupUseCase = isNeedToBackupUseCase,
getMissedAddressCryptoCurrenciesUseCase = getMissedAddressesCryptoCurrenciesUseCase,
hasSingleWalletSignedHashesUseCase = hasSingleWalletSignedHashesUseCase,
shouldShowSwapPromoWalletUseCase = shouldShowSwapPromoWalletUseCase,
clickIntents = this,
)
@ -774,6 +776,12 @@ internal class WalletViewModel @Inject constructor(
refreshSingleCurrencyContent(selectedWalletIndex)
}
override fun onCloseSwapPromoNotificationClick() {
viewModelScope.launch(dispatchers.main) {
shouldShowSwapPromoWalletUseCase.neverToShow()
}
}
// FIXME: refreshSingleCurrencyContent mustn't update the TxHistory and Buttons. It only must fetch primary
// currency. Now it not works because GetPrimaryCurrency's subscriber uses .distinctUntilChanged()
private fun refreshSingleCurrencyContent(walletIndex: Int) {