From 9490d82854e654d3ce1561a850c0447138579e50 Mon Sep 17 00:00:00 2001 From: SeniorZhai Date: Wed, 8 Jul 2026 14:46:16 +0800 Subject: [PATCH 1/2] fix(wallet): cache cash account and ad banners --- .../android/api/response/WalletHomeBanner.kt | 14 +- .../android/repository/CashRepository.kt | 3 + .../ui/wallet/WalletHomeClassicFragment.kt | 37 ++++- .../ui/wallet/WalletHomePrivacyFragment.kt | 78 +++++++++- .../android/ui/wallet/WalletViewModel.kt | 5 + .../ui/wallet/home/WalletHomeBannerCache.kt | 30 ++++ .../android/ui/wallet/home/WalletHomeCache.kt | 13 +- .../ui/wallet/home/WalletHomeCashAccount.kt | 76 +++++++++- .../wallet/home/WalletHomeCashAccountCache.kt | 48 ++++++ .../api/response/WalletHomeBannerTest.kt | 24 +++ .../wallet/home/WalletHomeCashAccountTest.kt | 143 ++++++++++++++++++ 11 files changed, 450 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeBannerCache.kt create mode 100644 app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountCache.kt diff --git a/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt b/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt index 4bfe90bee4..2a32e46ead 100644 --- a/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt +++ b/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt @@ -1,6 +1,7 @@ package one.mixin.android.api.response import com.google.gson.annotations.SerializedName +import org.threeten.bp.Instant data class WalletHomeBanner( @SerializedName(value = "banner_id", alternate = ["id"]) @@ -60,6 +61,13 @@ data class WalletHomeBanner( val isActive: Boolean get() = status.isBlank() || status.equals(BANNER_STATUS_ACTIVE, ignoreCase = true) + fun isExpired(now: Instant = Instant.now()): Boolean { + if (endAt.isBlank()) return false + return runCatching { + !now.isBefore(Instant.parse(endAt)) + }.getOrDefault(false) + } + companion object { const val BANNER_STATUS_ACTIVE = "active" const val BANNER_STATUS_INACTIVE = "inactive" @@ -77,10 +85,14 @@ fun Set.syncedWalletHomeClosedBannerIds(remoteBanners: List.visibleWalletHomeBanners(closedBannerIds: Set): List = +fun List.visibleWalletHomeBanners( + closedBannerIds: Set, + now: Instant = Instant.now(), +): List = filter { banner -> banner.key.isNotBlank() && banner.isActive && + !banner.isExpired(now) && banner.hasVisualContent && (!banner.actionUrl.isNullOrBlank() || banner.visibleActions.isNotEmpty()) && !closedBannerIds.contains(banner.key) diff --git a/app/src/main/java/one/mixin/android/repository/CashRepository.kt b/app/src/main/java/one/mixin/android/repository/CashRepository.kt index 7e94cec21b..962b62aa4e 100644 --- a/app/src/main/java/one/mixin/android/repository/CashRepository.kt +++ b/app/src/main/java/one/mixin/android/repository/CashRepository.kt @@ -14,6 +14,9 @@ class CashRepository private val cashService: CashService, private val userRepository: UserRepository, ) { + suspend fun cachedAccount(): CashAccount? = + PropertyHelper.findCashAccount() + suspend fun account(): MixinResponse { userRepository.getBotPublicKey(MIXIN_CASH_USER_ID, false) val response = cashService.account() diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt index 8ad2636e8e..3f935159f1 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt @@ -83,8 +83,10 @@ import one.mixin.android.ui.wallet.home.WalletHomeType import one.mixin.android.ui.wallet.home.calculateWalletHomeBtcTotal import one.mixin.android.ui.wallet.home.calculateWalletHomeTokenFiat import one.mixin.android.ui.wallet.home.formatWalletHomeBtcTotal +import one.mixin.android.ui.wallet.home.getWalletHomeBannerCache import one.mixin.android.ui.wallet.home.getWalletHomeCache import one.mixin.android.ui.wallet.home.putWalletHomeCache +import one.mixin.android.ui.wallet.home.putWalletHomeBannerCache import one.mixin.android.ui.wallet.home.WalletHomeImportKeyAction import one.mixin.android.ui.wallet.home.WalletHomeImportKeyKind import one.mixin.android.ui.wallet.home.walletHomePendingTransactionCount @@ -92,6 +94,7 @@ import one.mixin.android.ui.wallet.home.walletHomePendingTransactionIndicator import one.mixin.android.ui.wallet.home.walletHomeWatchIndicator import one.mixin.android.ui.wallet.home.walletHomeCacheKey import one.mixin.android.ui.wallet.home.walletHomeImportKeyAction +import one.mixin.android.ui.wallet.home.withDynamicBanners import one.mixin.android.ui.wallet.adapter.WalletWeb3TokenAdapter import one.mixin.android.util.analytics.AnalyticsTracker import one.mixin.android.util.analytics.AnalyticsTracker.TradeSource @@ -617,6 +620,8 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) if (!isAdded || walletId.isEmpty()) return val requestWalletId = walletId walletHomeBannerRefreshJob = lifecycleScope.launch { + val cacheKey = classicWalletHomeCacheKey(requestWalletId) + applyCachedWalletHomeBanners(requestWalletId, cacheKey) val remoteBanners = try { web3ViewModel.walletHomeBanners(walletHomeBannerChains(requestWalletId)) } catch (e: CancellationException) { @@ -625,6 +630,7 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) Timber.w(t, "Fetch wallet home banners failed") null } ?: return@launch + defaultSharedPreferences.putWalletHomeBannerCache(cacheKey, remoteBanners) if (!shouldApplyClassicWalletHomeBannerResponse(requestWalletId, walletId)) return@launch runCatching { syncClosedDynamicBannerIds(remoteBanners) @@ -632,10 +638,33 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) Timber.w(it, "Sync wallet home banner closed ids failed") } if (!shouldApplyClassicWalletHomeBannerResponse(requestWalletId, walletId)) return@launch - dynamicBanners = remoteBanners - isDynamicBannerLoaded = true - renderHome() + updateDynamicBanners(remoteBanners) + } + } + + private suspend fun applyCachedWalletHomeBanners( + requestWalletId: String, + cacheKey: String, + ) { + val cachedBanners = defaultSharedPreferences.getWalletHomeBannerCache(cacheKey) ?: return + if (!shouldApplyClassicWalletHomeBannerResponse(requestWalletId, walletId)) return + updateDynamicBanners(cachedBanners) + runCatching { + syncClosedDynamicBannerIds(cachedBanners) + }.onFailure { + Timber.w(it, "Sync cached wallet home banner closed ids failed") } + if (!shouldApplyClassicWalletHomeBannerResponse(requestWalletId, walletId)) return + updateDynamicBanners(cachedBanners) + } + + private fun updateDynamicBanners(banners: List) { + dynamicBanners = banners + isDynamicBannerLoaded = true + _homeState.value = _homeState.value.withDynamicBanners( + banners.visibleWalletHomeBanners(closedDynamicBannerIds), + ) + renderHome() } private suspend fun walletHomeBannerChains(walletId: String): List = @@ -676,7 +705,7 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) } } - private fun classicWalletHomeCacheKey(): String = + private fun classicWalletHomeCacheKey(walletId: String = this.walletId): String = walletHomeCacheKey(WalletHomeType.CLASSIC, walletId) private fun openClassicBuy() { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt index e1f1bbe65d..318f13e579 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt @@ -90,13 +90,20 @@ import one.mixin.android.ui.wallet.home.calculateWalletHomeBtcTotal import one.mixin.android.ui.wallet.home.calculateWalletHomeTokenFiat import one.mixin.android.ui.wallet.home.calculateWalletHomeTotalFiat import one.mixin.android.ui.wallet.home.formatWalletHomeBtcTotal +import one.mixin.android.ui.wallet.home.getWalletHomeBannerCache +import one.mixin.android.ui.wallet.home.getWalletHomeCashAccountCache import one.mixin.android.ui.wallet.home.getWalletHomeCacheState +import one.mixin.android.ui.wallet.home.getLegacyWalletHomeCashAccountCache import one.mixin.android.ui.wallet.home.positionMarginUsdTotal +import one.mixin.android.ui.wallet.home.putWalletHomeBannerCache +import one.mixin.android.ui.wallet.home.putWalletHomeCashAccountCache import one.mixin.android.ui.wallet.home.putWalletHomeCache import one.mixin.android.ui.wallet.home.toWalletHomeCashAccount import one.mixin.android.ui.wallet.home.toWalletHomePendingIndicator import one.mixin.android.ui.wallet.home.walletHomeCashBalanceUsd import one.mixin.android.ui.wallet.home.walletHomeCacheKey +import one.mixin.android.ui.wallet.home.withCashAccount +import one.mixin.android.ui.wallet.home.withDynamicBanners import one.mixin.android.ui.wallet.TokenListBottomSheetDialogFragment.Companion.TYPE_FROM_RECEIVE import one.mixin.android.ui.wallet.TokenListBottomSheetDialogFragment.Companion.TYPE_FROM_SEND import one.mixin.android.ui.wallet.adapter.WalletAssetAdapter @@ -151,6 +158,8 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) private var closedDynamicBannerIds: Set = emptySet() private var walletHomeBannerRefreshJob: Job? = null private var perpsPositionsRefreshJob: Job? = null + private var cachedCashAccountLoaded = false + private var cachedCashAccountJob: Job? = null private var cashAccount: WalletHomeCashAccount? = null private val assetsAdapter by lazy { WalletAssetAdapter(false) } private val perpetualViewModel by viewModels() @@ -186,11 +195,13 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) savedInstanceState: Bundle?, ): View { _binding = FragmentPrivacyWalletBinding.inflate(inflater, container, false) - defaultSharedPreferences.getWalletHomeCacheState(privacyWalletHomeCacheKey())?.let { + val cacheKey = privacyWalletHomeCacheKey() + defaultSharedPreferences.getWalletHomeCacheState(cacheKey)?.let { _homeState.value = it walletHomeDataState = WalletHomeDataState.CACHE isLoading = false } + loadLocalCashAccountCache(cacheKey) binding.compose.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) binding.compose.setContent { val homeState = _homeState.collectAsState().value @@ -210,6 +221,7 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) super.onViewCreated(view, savedInstanceState) Timber.e("onViewCreated called in WalletHomePrivacyFragment") _walletId.value = Session.getAccountId().orEmpty() + loadCachedCashAccount() refreshBitcoinPrice() refreshWalletHomeBanners() @@ -487,8 +499,9 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) walletViewModel.cashAccount() }.onSuccess { response -> if (response.isSuccess) { - cashAccount = response.data.toWalletHomeCashAccount() - renderHome() + response.data.toWalletHomeCashAccount()?.let { + applyCashAccount(it) + } } else { Timber.w("Fetch cash account failed code=%s message=%s", response.errorCode, response.errorDescription) } @@ -498,6 +511,39 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) } } + private fun loadCachedCashAccount() { + if (cachedCashAccountLoaded || cachedCashAccountJob?.isActive == true) return + cachedCashAccountJob = lifecycleScope.launch { + applyCachedCashAccount() + } + } + + private suspend fun applyCachedCashAccount() { + runCatching { + walletViewModel.cachedCashAccount() + }.onSuccess { account -> + cachedCashAccountLoaded = true + account.toWalletHomeCashAccount()?.let { + applyCashAccount(it) + } + }.onFailure { + Timber.w(it, "Read cached cash account failed") + } + } + + private fun loadLocalCashAccountCache(cacheKey: String) { + val cachedCashAccount = defaultSharedPreferences.getWalletHomeCashAccountCache(cacheKey) + ?: defaultSharedPreferences.getLegacyWalletHomeCashAccountCache(cacheKey) + cachedCashAccount?.let(::applyCashAccount) + } + + private fun applyCashAccount(account: WalletHomeCashAccount) { + cashAccount = account + defaultSharedPreferences.putWalletHomeCashAccountCache(privacyWalletHomeCacheKey(), account) + _homeState.value = _homeState.value.withCashAccount(account) + renderHome() + } + private fun homeBitcoinPriceUsd(): BigDecimal? = tokenSummary.bitcoinPriceUsd ?.toBigDecimalOrNull() @@ -508,6 +554,7 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) walletHomeBannerRefreshJob?.cancel() if (!isAdded) return walletHomeBannerRefreshJob = lifecycleScope.launch { + applyCachedWalletHomeBanners(privacyWalletHomeCacheKey()) val remoteBanners = try { walletViewModel.walletHomeBanners() } catch (e: CancellationException) { @@ -516,15 +563,34 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) Timber.w(t, "Fetch wallet home banners failed") null } ?: return@launch + defaultSharedPreferences.putWalletHomeBannerCache(privacyWalletHomeCacheKey(), remoteBanners) runCatching { syncClosedDynamicBannerIds(remoteBanners) }.onFailure { Timber.w(it, "Sync wallet home banner closed ids failed") } - dynamicBanners = remoteBanners - isDynamicBannerLoaded = true - renderHome() + updateDynamicBanners(remoteBanners) + } + } + + private suspend fun applyCachedWalletHomeBanners(cacheKey: String) { + val cachedBanners = defaultSharedPreferences.getWalletHomeBannerCache(cacheKey) ?: return + updateDynamicBanners(cachedBanners) + runCatching { + syncClosedDynamicBannerIds(cachedBanners) + }.onFailure { + Timber.w(it, "Sync cached wallet home banner closed ids failed") } + updateDynamicBanners(cachedBanners) + } + + private fun updateDynamicBanners(banners: List) { + dynamicBanners = banners + isDynamicBannerLoaded = true + _homeState.value = _homeState.value.withDynamicBanners( + banners.visibleWalletHomeBanners(closedDynamicBannerIds), + ) + renderHome() } private suspend fun syncClosedDynamicBannerIds(remoteBanners: List) { diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt index d7edb20ece..3b6ee74e47 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletViewModel.kt @@ -116,6 +116,11 @@ internal constructor( cashRepository.account() } + suspend fun cachedCashAccount(): CashAccount? = + withContext(Dispatchers.IO) { + cashRepository.cachedAccount() + } + suspend fun assetItemsNotHiddenRaw(): List = withContext(Dispatchers.IO){ return@withContext tokenRepository.assetItemsNotHiddenRaw() } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeBannerCache.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeBannerCache.kt new file mode 100644 index 0000000000..6ec981f365 --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeBannerCache.kt @@ -0,0 +1,30 @@ +package one.mixin.android.ui.wallet.home + +import android.content.SharedPreferences +import com.google.gson.reflect.TypeToken +import one.mixin.android.api.response.WalletHomeBanner +import one.mixin.android.extension.putString +import one.mixin.android.util.GsonHelper +import org.threeten.bp.Instant + +private const val PREF_WALLET_HOME_BANNER_CACHE_PREFIX = "pref_wallet_home_banner_cache" +private val walletHomeBannerListType = object : TypeToken>() {}.type + +fun SharedPreferences.getWalletHomeBannerCache( + key: String, + now: Instant = Instant.now(), +): List? = + runCatching { + getString(walletHomeBannerCacheKey(key), null) + ?.let { GsonHelper.customGson.fromJson>(it, walletHomeBannerListType) } + ?.filterNot { it.isExpired(now) } + }.getOrNull() + +fun SharedPreferences.putWalletHomeBannerCache( + key: String, + banners: List, +) { + putString(walletHomeBannerCacheKey(key), GsonHelper.customGson.toJson(banners)) +} + +private fun walletHomeBannerCacheKey(key: String) = "$PREF_WALLET_HOME_BANNER_CACHE_PREFIX:$key" diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt index af275427cf..397bfc041e 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCache.kt @@ -37,7 +37,7 @@ data class WalletHomeCache( showBanner = false, showReferral = false, hasPositions = false, - hasCashAccount = cashAccount != null, + hasCashAccount = false, hasTopMovers = false, hasTransactions = totalTransactionCount > 0, hasImportKeyAction = cachedImportKeyAction != null, @@ -51,11 +51,10 @@ data class WalletHomeCache( fiatTotal = fiatTotal, btcTotal = btcTotal, fiatSymbol = fiatSymbol, - privacyTokens = privacyTokens, - web3Tokens = web3Tokens, - privacyTransactions = privacyTransactions, - web3Transactions = web3Transactions, - cashAccount = cashAccount, + privacyTokens = privacyTokens.orEmpty(), + web3Tokens = web3Tokens.orEmpty(), + privacyTransactions = privacyTransactions.orEmpty(), + web3Transactions = web3Transactions.orEmpty(), totalTokenCount = totalTokenCount, totalTransactionCount = totalTransactionCount, isWatchWallet = isWatchWallet, @@ -96,7 +95,6 @@ fun SharedPreferences.putWalletHomeCache( state.totalTransactionCount == 0 && state.pendingIndicator == null && state.importKeyAction == null && - state.cashAccount == null && state.watchIndicator == null ) return val cache = WalletHomeCache( @@ -110,7 +108,6 @@ fun SharedPreferences.putWalletHomeCache( web3Transactions = state.web3Transactions.take(WalletHomeSection.PREVIEW_LIMIT), totalTokenCount = state.totalTokenCount, totalTransactionCount = state.totalTransactionCount, - cashAccount = state.cashAccount, isWatchWallet = state.isWatchWallet, watchAddresses = watchAddresses, pendingIndicator = state.pendingIndicator, diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt index 53776578a2..b600721668 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt @@ -1,12 +1,13 @@ package one.mixin.android.ui.wallet.home import one.mixin.android.api.response.CashAccount +import one.mixin.android.api.response.WalletHomeBanner import one.mixin.android.extension.numberFormat2 import java.math.BigDecimal data class WalletHomeCashAccount( - val balanceUsd: BigDecimal, - val rewardApy: String?, + val balanceUsd: BigDecimal = BigDecimal.ZERO, + val rewardApy: String? = null, ) { val balanceAmountText: String get() = balanceUsd.numberFormat2() @@ -37,3 +38,74 @@ internal fun cashAccountApyText(rewardApy: String?): String? = internal fun walletHomeCashBalanceUsd( account: WalletHomeCashAccount?, ): BigDecimal = account?.balanceUsd ?: BigDecimal.ZERO + +internal fun WalletHomeState.withCashAccount( + cashAccount: WalletHomeCashAccount, +): WalletHomeState { + if (walletType != WalletHomeType.PRIVACY) return copy(cashAccount = cashAccount) + + val cashCards = if (cards.isEmpty()) { + WalletHomeBuilder.build( + walletType = walletType, + hasAssetValue = false, + showBanner = false, + showReferral = false, + hasPositions = false, + hasCashAccount = true, + hasTopMovers = false, + hasTransactions = false, + isLoading = false, + ) + } else { + cards.withCashCard() + } + return copy( + cashAccount = cashAccount, + cards = cashCards, + isLoading = false, + ) +} + +private fun List.withCashCard(): List { + if (WalletHomeCardType.CASH in this) return this + val balanceIndex = indexOf(WalletHomeCardType.BALANCE) + if (balanceIndex == -1) return listOf(WalletHomeCardType.CASH) + this + return take(balanceIndex + 1) + WalletHomeCardType.CASH + drop(balanceIndex + 1) +} + +internal fun WalletHomeState.withDynamicBanners( + dynamicBanners: List, +): WalletHomeState { + val bannerCards = when { + dynamicBanners.isEmpty() -> cards - WalletHomeCardType.BANNER + cards.isEmpty() -> WalletHomeBuilder.build( + walletType = walletType, + hasAssetValue = false, + showBanner = true, + showReferral = false, + hasPositions = false, + hasCashAccount = cashAccount != null, + hasTopMovers = false, + hasTransactions = false, + hasImportKeyAction = importKeyAction != null, + hasPendingIndicator = pendingIndicator != null, + isLoading = false, + ) + WalletHomeCardType.BANNER in cards -> cards + else -> cards.withBannerCard() + } + return copy( + cards = bannerCards, + isLoading = if (dynamicBanners.isNotEmpty()) false else isLoading, + dynamicBanners = dynamicBanners, + isDynamicBannerLoaded = true, + ) +} + +private fun List.withBannerCard(): List { + val firstCardIndex = indexOfFirst { + it == WalletHomeCardType.BALANCE || it == WalletHomeCardType.EMPTY_GUIDE + } + if (firstCardIndex == -1) return listOf(WalletHomeCardType.BANNER) + this + return take(firstCardIndex + 1) + WalletHomeCardType.BANNER + drop(firstCardIndex + 1) +} diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountCache.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountCache.kt new file mode 100644 index 0000000000..6910182805 --- /dev/null +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountCache.kt @@ -0,0 +1,48 @@ +package one.mixin.android.ui.wallet.home + +import android.content.SharedPreferences +import com.google.gson.JsonParser +import one.mixin.android.extension.putString +import one.mixin.android.util.GsonHelper +import java.math.BigDecimal + +private const val PREF_WALLET_HOME_CASH_ACCOUNT_CACHE_PREFIX = "pref_wallet_home_cash_account_cache" + +fun SharedPreferences.getWalletHomeCashAccountCache( + key: String, +): WalletHomeCashAccount? = + runCatching { + getString(walletHomeCashAccountCacheKey(key), null) + ?.let { GsonHelper.customGson.fromJson(it, WalletHomeCashAccount::class.java) } + }.getOrNull() + +fun SharedPreferences.putWalletHomeCashAccountCache( + key: String, + cashAccount: WalletHomeCashAccount, +) { + putString(walletHomeCashAccountCacheKey(key), GsonHelper.customGson.toJson(cashAccount)) +} + +fun SharedPreferences.getLegacyWalletHomeCashAccountCache( + walletHomeCacheKey: String, +): WalletHomeCashAccount? = + getString(walletHomeCacheKey, null).legacyWalletHomeCashAccountCache() + +internal fun String?.legacyWalletHomeCashAccountCache(): WalletHomeCashAccount? = + runCatching { + val root = JsonParser.parseString(this).asJsonObject + val cashAccount = root.getAsJsonObject("cashAccount") ?: return@runCatching null + val balance = cashAccount.get("balanceUsd") + ?: cashAccount.get("balance") + ?: return@runCatching null + val balanceValue = balance.asString.toBigDecimalOrNull() ?: BigDecimal.ZERO + val rewardApy = cashAccount.get("rewardApy") + ?.takeUnless { it.isJsonNull } + ?.asString + WalletHomeCashAccount( + balanceUsd = balanceValue, + rewardApy = rewardApy, + ) + }.getOrNull() + +private fun walletHomeCashAccountCacheKey(key: String) = "$PREF_WALLET_HOME_CASH_ACCOUNT_CACHE_PREFIX:$key" diff --git a/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt b/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt index 7ee9663896..fdd019d8f0 100644 --- a/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt +++ b/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt @@ -5,6 +5,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Test +import org.threeten.bp.Instant class WalletHomeBannerTest { @Test @@ -78,4 +79,27 @@ class WalletHomeBannerTest { ) } + @Test + fun visibleBannersExcludeExpiredItems() { + val now = Instant.parse("2026-07-08T10:00:00Z") + + val banners = listOf( + visibleBanner("expired", endAt = "2026-07-08T09:59:59Z"), + visibleBanner("future", endAt = "2026-07-08T10:00:01Z"), + visibleBanner("open-ended"), + ).visibleWalletHomeBanners(emptySet(), now) + + assertEquals(listOf("future", "open-ended"), banners.map { it.bannerId }) + } + + private fun visibleBanner( + id: String, + endAt: String = "", + ) = WalletHomeBanner( + bannerId = id, + title = id, + actionUrl = "mixin://$id", + endAt = endAt, + ) + } diff --git a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt index 82d5f6a51f..a3bd88abd7 100644 --- a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt +++ b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt @@ -2,8 +2,14 @@ package one.mixin.android.ui.wallet.home import com.google.gson.Gson import one.mixin.android.api.response.CashAccount +import one.mixin.android.api.response.WalletHomeBanner +import one.mixin.android.util.GsonHelper import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Test +import java.math.BigDecimal class WalletHomeCashAccountTest { private data class CashAccountEnvelope( @@ -38,4 +44,141 @@ class WalletHomeCashAccountTest { assertEquals("3.5%", cashAccountApyText("3.5%")) assertEquals("3.5%", cashAccountApyText(" 3.5 % ")) } + + @Test + fun walletHomeStateShowsCachedCashAccountWhenStillLoading() { + val cashAccount = WalletHomeCashAccount( + balanceUsd = BigDecimal("5.8"), + rewardApy = "3.5", + ) + + val state = WalletHomeState( + walletType = WalletHomeType.PRIVACY, + isLoading = true, + ).withCashAccount(cashAccount) + + assertEquals(cashAccount, state.cashAccount) + assertEquals( + listOf( + WalletHomeCardType.BALANCE, + WalletHomeCardType.CASH, + WalletHomeCardType.TOKENS, + WalletHomeCardType.SUPPORT, + ), + state.cards, + ) + assertEquals(false, state.isLoading) + } + + @Test + fun walletHomeStateInsertsCachedCashCardAfterBalance() { + val cashAccount = WalletHomeCashAccount( + balanceUsd = BigDecimal("5.8"), + rewardApy = "3.5", + ) + + val state = WalletHomeState( + walletType = WalletHomeType.PRIVACY, + cards = listOf( + WalletHomeCardType.BALANCE, + WalletHomeCardType.TOKENS, + ), + ).withCashAccount(cashAccount) + + assertEquals( + listOf( + WalletHomeCardType.BALANCE, + WalletHomeCardType.CASH, + WalletHomeCardType.TOKENS, + ), + state.cards, + ) + } + + @Test + fun cashAccountReadsLegacyBalanceUsd() { + val directAccount = GsonHelper.customGson.fromJson("""{"balanceUsd":5.8,"rewardApy":"3.5"}""", WalletHomeCashAccount::class.java) + + assertEquals(0, directAccount.balanceUsd.compareTo(BigDecimal("5.8"))) + } + + @Test + fun cashAccountReadsLegacyWalletHomeCache() { + val json = """ + { + "walletType":"PRIVACY", + "fiatTotal":"0.00", + "btcTotal":"0.00", + "fiatSymbol":"USD", + "totalTokenCount":0, + "totalTransactionCount":0, + "cashAccount":{"balanceUsd":5.8,"rewardApy":"3.5"} + } + """.trimIndent() + + val cashAccount = json.legacyWalletHomeCashAccountCache() + + assertEquals(0, cashAccount?.balanceUsd?.compareTo(BigDecimal("5.8"))) + assertEquals("3.5", cashAccount?.rewardApy) + } + + @Test + fun cashAccountReadsLegacyBalanceKey() { + val cashAccount = """ + {"cashAccount":{"balance":"6.2","rewardApy":"4.1"}} + """.trimIndent().legacyWalletHomeCashAccountCache() + + assertEquals(0, cashAccount?.balanceUsd?.compareTo(BigDecimal("6.2"))) + assertEquals("4.1", cashAccount?.rewardApy) + } + + @Test + fun walletHomeCacheDoesNotRestoreCashAccount() { + val json = """ + { + "walletType":"PRIVACY", + "fiatTotal":"0.00", + "btcTotal":"0.00", + "fiatSymbol":"USD", + "totalTokenCount":0, + "totalTransactionCount":0, + "cashAccount":{"balanceUsd":5.8,"rewardApy":"3.5"} + } + """.trimIndent() + + val cache = GsonHelper.customGson.fromJson(json, WalletHomeCache::class.java) + assertNotNull(cache) + assertNotNull(cache.cashAccount) + + val state = cache.toState() + + assertNull(state.cashAccount) + assertFalse(state.cards.contains(WalletHomeCardType.CASH)) + } + + @Test + fun walletHomeStateShowsDynamicBannerWhenStillLoading() { + val banner = WalletHomeBanner( + bannerId = "ad-1", + title = "Ad", + actionUrl = "mixin://ad", + ) + + val state = WalletHomeState( + walletType = WalletHomeType.PRIVACY, + isLoading = true, + ).withDynamicBanners(listOf(banner)) + + assertEquals(listOf(banner), state.dynamicBanners) + assertEquals( + listOf( + WalletHomeCardType.EMPTY_GUIDE, + WalletHomeCardType.BANNER, + WalletHomeCardType.TOKENS, + WalletHomeCardType.SUPPORT, + ), + state.cards, + ) + assertFalse(state.isLoading) + } } From 29c07bf3cd1a236c231287e63009ad54df203e21 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 10 Jul 2026 14:20:43 +0800 Subject: [PATCH 2/2] fix(wallet): filter banners by active wallet --- .../android/api/response/WalletHomeBanner.kt | 9 +++ .../android/api/service/ReferralService.kt | 5 +- .../android/repository/ReferralRepository.kt | 4 +- .../android/ui/home/web3/Web3ViewModel.kt | 2 +- .../ui/wallet/WalletHomeClassicFragment.kt | 32 ++++++++-- .../ui/wallet/WalletHomePrivacyFragment.kt | 6 +- .../ui/wallet/home/WalletHomeCashAccount.kt | 7 ++- .../api/response/WalletHomeBannerTest.kt | 26 ++++++++ .../wallet/home/WalletHomeBannerStateTest.kt | 63 +++++++++++++++++++ .../wallet/home/WalletHomeCashAccountTest.kt | 5 +- 10 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeBannerStateTest.kt diff --git a/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt b/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt index 2a32e46ead..b28c31c587 100644 --- a/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt +++ b/app/src/main/java/one/mixin/android/api/response/WalletHomeBanner.kt @@ -81,6 +81,15 @@ data class WalletHomeBannerAction( val action: String = "", ) +fun List.filterWalletHomeBannersByChains(chains: Collection): List { + val walletChains = chains.filterTo(mutableSetOf(), String::isNotBlank) + if (walletChains.isEmpty()) return this + + return filter { banner -> + banner.chains.none(String::isNotBlank) || banner.chains.any(walletChains::contains) + } +} + fun Set.syncedWalletHomeClosedBannerIds(remoteBanners: List): Set { return this } diff --git a/app/src/main/java/one/mixin/android/api/service/ReferralService.kt b/app/src/main/java/one/mixin/android/api/service/ReferralService.kt index f1d42ed9e2..306a0e3a0e 100644 --- a/app/src/main/java/one/mixin/android/api/service/ReferralService.kt +++ b/app/src/main/java/one/mixin/android/api/service/ReferralService.kt @@ -4,14 +4,11 @@ import one.mixin.android.api.MixinResponse import one.mixin.android.api.response.WalletHomeBanner import one.mixin.android.api.response.referral.ReferralResponse import retrofit2.http.GET -import retrofit2.http.Query interface ReferralService { @GET("referral") suspend fun referral(): MixinResponse @GET("app-banners") - suspend fun walletHomeBanners( - @Query("chain") chains: List? = null, - ): MixinResponse> + suspend fun walletHomeBanners(): MixinResponse> } diff --git a/app/src/main/java/one/mixin/android/repository/ReferralRepository.kt b/app/src/main/java/one/mixin/android/repository/ReferralRepository.kt index 2a4b1b10ab..97a00cac74 100644 --- a/app/src/main/java/one/mixin/android/repository/ReferralRepository.kt +++ b/app/src/main/java/one/mixin/android/repository/ReferralRepository.kt @@ -90,7 +90,7 @@ class ReferralRepository ) } - suspend fun fetchWalletHomeBanners(chains: List = emptyList()): List? { + suspend fun fetchWalletHomeBanners(): List? { runCatching { userRepository.getBotPublicKey(REFERRAL_BOT_USER_ID, false) }.onFailure { @@ -98,7 +98,7 @@ class ReferralRepository } return requestReferralMixinAPI( - invokeNetwork = { referralService.walletHomeBanners(chains.takeIf { it.isNotEmpty() }) }, + invokeNetwork = { referralService.walletHomeBanners() }, successBlock = { response -> response.data.orEmpty() }, failureBlock = { response -> Timber.d( diff --git a/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt b/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt index 5290af448d..e27ef7ef31 100644 --- a/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt +++ b/app/src/main/java/one/mixin/android/ui/home/web3/Web3ViewModel.kt @@ -99,7 +99,7 @@ class Web3ViewModel @Inject constructor( suspend fun findOrSyncApp(appId: String) = userRepository.findOrSyncApp(appId) - suspend fun walletHomeBanners(chains: List) = referralRepository.fetchWalletHomeBanners(chains) + suspend fun walletHomeBanners() = referralRepository.fetchWalletHomeBanners() suspend fun findMarketItemByAssetId(assetId: String) = tokenRepository.findMarketItemByAssetId(assetId) diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt index 3f935159f1..b2457db55b 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomeClassicFragment.kt @@ -33,6 +33,7 @@ import one.mixin.android.R import one.mixin.android.RxBus import one.mixin.android.api.response.WalletHomeBanner import one.mixin.android.api.response.WalletHomeBannerAction +import one.mixin.android.api.response.filterWalletHomeBannersByChains import one.mixin.android.api.response.syncedWalletHomeClosedBannerIds import one.mixin.android.api.response.visibleWalletHomeBanners import one.mixin.android.databinding.FragmentPrivacyWalletBinding @@ -146,6 +147,7 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) private var pendingTransactionCount: Int = 0 private var watchAddresses: List = emptyList() private var dynamicBanners: List = emptyList() + private var currentWalletHomeBannerChains: Set = emptySet() private var isDynamicBannerLoaded = false private var closedDynamicBannerIds: Set = emptySet() private var walletHomeBannerRefreshJob: Job? = null @@ -172,6 +174,7 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) field = value walletHomeDataState = WalletHomeDataState.EMPTY dynamicBanners = emptyList() + currentWalletHomeBannerChains = emptySet() isDynamicBannerLoaded = false _walletId.value = value loadWalletHomeCache() @@ -502,7 +505,7 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) fiatRate = fiatRate, ) val showAddWalletBanner = !defaultSharedPreferences.getBoolean(PREF_WALLET_HOME_ADD_WALLET_BANNER_CLOSED, false) - val visibleDynamicBanners = dynamicBanners.visibleWalletHomeBanners(closedDynamicBannerIds) + val visibleDynamicBanners = currentVisibleDynamicBanners() val showBanner = shouldShowWalletHomeBannerCard( showAddWalletBanner = showAddWalletBanner, isDynamicBannerLoaded = isDynamicBannerLoaded, @@ -620,10 +623,20 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) if (!isAdded || walletId.isEmpty()) return val requestWalletId = walletId walletHomeBannerRefreshJob = lifecycleScope.launch { + val requestWalletChains = try { + walletHomeBannerChains(requestWalletId) + } catch (e: CancellationException) { + throw e + } catch (t: Throwable) { + Timber.w(t, "Read wallet chains for wallet home banners failed") + return@launch + } + if (!shouldApplyClassicWalletHomeBannerResponse(requestWalletId, walletId)) return@launch + currentWalletHomeBannerChains = requestWalletChains val cacheKey = classicWalletHomeCacheKey(requestWalletId) applyCachedWalletHomeBanners(requestWalletId, cacheKey) val remoteBanners = try { - web3ViewModel.walletHomeBanners(walletHomeBannerChains(requestWalletId)) + web3ViewModel.walletHomeBanners() } catch (e: CancellationException) { throw e } catch (t: Throwable) { @@ -662,16 +675,25 @@ class WalletHomeClassicFragment : BaseFragment(R.layout.fragment_privacy_wallet) dynamicBanners = banners isDynamicBannerLoaded = true _homeState.value = _homeState.value.withDynamicBanners( - banners.visibleWalletHomeBanners(closedDynamicBannerIds), + dynamicBanners = currentVisibleDynamicBanners(), + showAddWalletBanner = !defaultSharedPreferences.getBoolean( + PREF_WALLET_HOME_ADD_WALLET_BANNER_CLOSED, + false, + ), ) renderHome() } - private suspend fun walletHomeBannerChains(walletId: String): List = + private fun currentVisibleDynamicBanners(): List = + dynamicBanners + .filterWalletHomeBannersByChains(currentWalletHomeBannerChains) + .visibleWalletHomeBanners(closedDynamicBannerIds) + + private suspend fun walletHomeBannerChains(walletId: String): Set = web3ViewModel.getAddresses(walletId) .map { it.chainId } .filter(String::isNotBlank) - .distinct() + .toSet() private suspend fun syncClosedDynamicBannerIds(remoteBanners: List) { val closedBannerIds = findWalletHomeDynamicBannerClosedIds() diff --git a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt index 318f13e579..11ed945e39 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/WalletHomePrivacyFragment.kt @@ -588,7 +588,11 @@ class WalletHomePrivacyFragment : BaseFragment(R.layout.fragment_privacy_wallet) dynamicBanners = banners isDynamicBannerLoaded = true _homeState.value = _homeState.value.withDynamicBanners( - banners.visibleWalletHomeBanners(closedDynamicBannerIds), + dynamicBanners = banners.visibleWalletHomeBanners(closedDynamicBannerIds), + showAddWalletBanner = !defaultSharedPreferences.getBoolean( + PREF_WALLET_HOME_ADD_WALLET_BANNER_CLOSED, + false, + ), ) renderHome() } diff --git a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt index b600721668..e59b85b0e1 100644 --- a/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt +++ b/app/src/main/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccount.kt @@ -75,9 +75,11 @@ private fun List.withCashCard(): List { internal fun WalletHomeState.withDynamicBanners( dynamicBanners: List, + showAddWalletBanner: Boolean, ): WalletHomeState { + val showBanner = showAddWalletBanner || dynamicBanners.isNotEmpty() val bannerCards = when { - dynamicBanners.isEmpty() -> cards - WalletHomeCardType.BANNER + !showBanner -> cards - WalletHomeCardType.BANNER cards.isEmpty() -> WalletHomeBuilder.build( walletType = walletType, hasAssetValue = false, @@ -96,7 +98,8 @@ internal fun WalletHomeState.withDynamicBanners( } return copy( cards = bannerCards, - isLoading = if (dynamicBanners.isNotEmpty()) false else isLoading, + isLoading = if (showBanner) false else isLoading, + showAddWalletBanner = showAddWalletBanner, dynamicBanners = dynamicBanners, isDynamicBannerLoaded = true, ) diff --git a/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt b/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt index fdd019d8f0..cee8806f0d 100644 --- a/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt +++ b/app/src/test/java/one/mixin/android/api/response/WalletHomeBannerTest.kt @@ -65,6 +65,32 @@ class WalletHomeBannerTest { assertEquals(listOf("First"), banner.visibleActions.map { it.label }) } + @Test + fun filterByChainsKeepsGlobalAndMatchingBanners() { + val banners = listOf( + WalletHomeBanner(bannerId = "global"), + WalletHomeBanner(bannerId = "blank", chains = listOf("")), + WalletHomeBanner(bannerId = "ethereum", chains = listOf("ethereum")), + WalletHomeBanner(bannerId = "multi-chain", chains = listOf("bitcoin", "ethereum")), + WalletHomeBanner(bannerId = "solana", chains = listOf("solana")), + ) + + assertEquals( + listOf("global", "blank", "ethereum", "multi-chain"), + banners.filterWalletHomeBannersByChains(listOf("ethereum")).map { it.bannerId }, + ) + } + + @Test + fun filterByChainsWithoutWalletChainsKeepsAllBanners() { + val banners = listOf( + WalletHomeBanner(bannerId = "global"), + WalletHomeBanner(bannerId = "ethereum", chains = listOf("ethereum")), + ) + + assertEquals(banners, banners.filterWalletHomeBannersByChains(emptyList())) + } + @Test fun syncClosedBannerIdsKeepsExistingIdsWhenRemoteResponseIsPartial() { val remoteBanners = listOf( diff --git a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeBannerStateTest.kt b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeBannerStateTest.kt new file mode 100644 index 0000000000..39afa3c9e7 --- /dev/null +++ b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeBannerStateTest.kt @@ -0,0 +1,63 @@ +package one.mixin.android.ui.wallet.home + +import one.mixin.android.api.response.WalletHomeBanner +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test + +class WalletHomeBannerStateTest { + @Test + fun cachedStateShowsAddWalletAndDynamicBannerTogether() { + val banner = WalletHomeBanner(bannerId = "banner-1") + + val state = cachedState().withDynamicBanners( + dynamicBanners = listOf(banner), + showAddWalletBanner = true, + ) + + assertTrue(state.showAddWalletBanner) + assertEquals(listOf(banner), state.dynamicBanners) + assertEquals(1, state.cards.count { it == WalletHomeCardType.BANNER }) + assertTrue(state.isDynamicBannerLoaded) + } + + @Test + fun cachedStateKeepsAddWalletBannerWithoutDynamicBanners() { + val state = cachedState().withDynamicBanners( + dynamicBanners = emptyList(), + showAddWalletBanner = true, + ) + + assertTrue(state.showAddWalletBanner) + assertTrue(WalletHomeCardType.BANNER in state.cards) + assertTrue(state.dynamicBanners.isEmpty()) + } + + @Test + fun cachedStateRemovesBannerWhenNoBannerShouldBeShown() { + val state = cachedState() + .withDynamicBanners( + dynamicBanners = listOf(WalletHomeBanner(bannerId = "banner-1")), + showAddWalletBanner = true, + ) + .withDynamicBanners( + dynamicBanners = emptyList(), + showAddWalletBanner = false, + ) + + assertFalse(state.showAddWalletBanner) + assertFalse(WalletHomeCardType.BANNER in state.cards) + assertTrue(state.dynamicBanners.isEmpty()) + } + + private fun cachedState(): WalletHomeState = + WalletHomeCache( + walletType = WalletHomeType.CLASSIC, + fiatTotal = "0.00", + btcTotal = "0.00", + fiatSymbol = "\$", + totalTokenCount = 1, + totalTransactionCount = 0, + ).toState() +} diff --git a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt index a3bd88abd7..57a652153c 100644 --- a/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt +++ b/app/src/test/java/one/mixin/android/ui/wallet/home/WalletHomeCashAccountTest.kt @@ -167,7 +167,10 @@ class WalletHomeCashAccountTest { val state = WalletHomeState( walletType = WalletHomeType.PRIVACY, isLoading = true, - ).withDynamicBanners(listOf(banner)) + ).withDynamicBanners( + dynamicBanners = listOf(banner), + showAddWalletBanner = false, + ) assertEquals(listOf(banner), state.dynamicBanners) assertEquals(