Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-21 18:34:38 +03:00
commit 438126dc82
665 changed files with 16174 additions and 6245 deletions

View file

@ -4,36 +4,28 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.ReferralDeepLink
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.nft.ObserveAndClearNFTCacheIfNeedUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.deeplink.WalletDeepLinksHandler
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.*
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.PushNotificationsBottomSheetConfig
@ -47,9 +39,11 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
@ -73,7 +67,6 @@ internal class WalletModel @Inject constructor(
private val isWalletsScrollPreviewEnabled: IsWalletsScrollPreviewEnabled,
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
private val walletDeepLinksHandler: WalletDeepLinksHandler,
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase,
@ -81,11 +74,11 @@ internal class WalletModel @Inject constructor(
private val tokenListStore: MultiWalletTokenListStore,
private val onrampStatusFactory: OnrampStatusFactory,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val deepLinksRegistry: DeepLinksRegistry,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val appRouter: AppRouter,
private val routingFeatureToggle: RoutingFeatureToggle,
private val walletContentFetcher: WalletContentFetcher,
private val tokensFeatureToggles: TokensFeatureToggles,
private val observeAndClearNFTCacheIfNeedUseCase: ObserveAndClearNFTCacheIfNeedUseCase,
private val walletDeepLinkActionListener: WalletDeepLinkActionListener,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -167,7 +160,16 @@ internal class WalletModel @Inject constructor(
return innerWalletRouter.isWalletLastScreen() && shouldShowSaveWalletScreenUseCase() && canUseBiometryUseCase()
}
private fun subscribeToUserWalletsUpdates() {
private fun subscribeToUserWalletsUpdates() = channelFlow<Unit> {
val firstWalletsUseCaseEmit = MutableStateFlow(false)
suspend fun waitFirstWalletsUseCaseEmit() = firstWalletsUseCaseEmit.filter { it }.first()
// deepLinkActionFlow must wait for fist getWalletsUseCase() emit to correct handle Action.InitializeWallets
walletDeepLinkActionListener.selectWalletFlow
.onEach { waitFirstWalletsUseCaseEmit() }
.onEach(::selectWalletById)
.launchIn(this)
getWalletsUseCase()
.conflate()
.distinctUntilChanged()
@ -178,10 +180,13 @@ internal class WalletModel @Inject constructor(
)
}
.onEach(::updateWallets)
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(walletsUpdateJobHolder)
.onEach { firstWalletsUseCaseEmit.update { true } }
.launchIn(this)
awaitClose()
}
.flowOn(dispatchers.default)
.launchIn(modelScope)
.saveIn(walletsUpdateJobHolder)
private fun subscribeOnBalanceHiding() {
getBalanceHidingSettingsUseCase()
@ -226,12 +231,6 @@ internal class WalletModel @Inject constructor(
selectedWalletAnalyticsSender.send(selectedWallet)
}
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
// Registering here, because `WalletDeepLinksHandler` unregisters deeplink when scope is cancelled
// This is temporary solution, will be removed with complete deeplink navigation overhaul
addReferralDeepLink(selectedWallet)
walletDeepLinksHandler.registerForWallet(scope = modelScope, userWallet = selectedWallet)
}
subscribeOnExpressTransactionsUpdates(selectedWallet)
observeAndClearNFTCacheIfNeedUseCase(selectedWallet)
}
@ -240,18 +239,17 @@ internal class WalletModel @Inject constructor(
}
}
private fun addReferralDeepLink(userWallet: UserWallet) {
deepLinksRegistry.register(
ReferralDeepLink(
onReceive = {
if (userWallet !is UserWallet.Cold || userWallet.cardTypesResolver.isTangemWallet()) {
appRouter.push(
AppRoute.ReferralProgram(userWalletId = userWallet.walletId),
)
}
},
),
)
private fun selectWalletById(selectedWalletId: UserWalletId) {
val currentWalletId = stateHolder.getSelectedWalletId()
if (currentWalletId == selectedWalletId) return
val currentIndex = stateHolder.getWalletIndexByWalletId(userWalletId = currentWalletId) ?: return
val newIndex = stateHolder.getWalletIndexByWalletId(userWalletId = selectedWalletId) ?: return
scrollToWallet(prevIndex = currentIndex, newIndex = newIndex) {
stateHolder.update { it.copy(selectedWalletIndex = newIndex) }
}
}
// We need to update the current wallet quotes if the application was in the background for more than 10 seconds
@ -372,24 +370,28 @@ internal class WalletModel @Inject constructor(
coroutineScope = modelScope,
)
fetchIfSingleWallet(action.selectedWallet)
fetchWalletContent(userWallet = action.selectedWallet)
val otherWallets = action.wallets.minus(action.selectedWallet)
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
otherWallets.onEach { userWallet ->
modelScope.launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
}
if (action.wallets.size > 1 && isWalletsScrollPreviewEnabled()) {
withContext(dispatchers.io) { delay(timeMillis = 1_800) }
val direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
Direction.RIGHT
} else {
Direction.LEFT
}
walletEventSender.send(
event = WalletEvent.DemonstrateWalletsScrollPreview(
direction = if (action.selectedWalletIndex == action.wallets.lastIndex) {
Direction.RIGHT
} else {
Direction.LEFT
},
),
)
demonstrateWalletsScrollPreview(direction = direction)
}
}
private fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
private suspend fun reinitializeWallet(action: WalletsUpdateActionResolver.Action.ReinitializeWallet) {
walletScreenContentLoader.cancel(action.prevWalletId)
tokenListStore.remove(action.prevWalletId)
@ -399,7 +401,7 @@ internal class WalletModel @Inject constructor(
coroutineScope = modelScope,
)
fetchIfSingleWallet(userWallet = action.selectedWallet)
fetchWalletContent(userWallet = action.selectedWallet)
stateHolder.update(
ReinitializeWalletTransformer(
@ -411,14 +413,14 @@ internal class WalletModel @Inject constructor(
)
}
private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
private suspend fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
fetchIfSingleWallet(userWallet = action.selectedWallet)
fetchWalletContent(userWallet = action.selectedWallet)
stateHolder.update(
AddWalletTransformer(
@ -490,6 +492,16 @@ internal class WalletModel @Inject constructor(
)
}
private fun demonstrateWalletsScrollPreview(direction: Direction) {
modelScope.launch(dispatchers.mainImmediate) {
delay(timeMillis = 1_800)
walletEventSender.send(
event = WalletEvent.DemonstrateWalletsScrollPreview(direction = direction),
)
}
}
private fun scrollToWallet(prevIndex: Int, newIndex: Int, onConsume: () -> Unit = {}) {
// Should not show scroll animation if WalletScreen isn't in the background.
if (screenLifecycleProvider.isBackgroundState.value) {
@ -517,6 +529,20 @@ internal class WalletModel @Inject constructor(
}
}
private suspend fun fetchWalletContent(userWallet: UserWallet) {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
/*
* 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) }
}
} else {
fetchIfSingleWallet(userWallet = userWallet)
}
}
private fun fetchIfSingleWallet(userWallet: UserWallet) {
if (userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWallet()) {
modelScope.launch {

View file

@ -10,20 +10,19 @@ import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase
import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
import com.tangem.domain.tokens.TokensFeatureToggles
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetRefreshStateTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -44,13 +43,13 @@ internal class WalletClickIntents @Inject constructor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val selectWalletUseCase: SelectWalletUseCase,
private val fetchTokenListUseCase: FetchTokenListUseCase,
private val walletContentFetcher: WalletContentFetcher,
private val tokensFeatureToggles: TokensFeatureToggles,
private val fetchCardTokenListUseCase: FetchCardTokenListUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview,
private val rampStateManager: RampStateManager,
private val dispatchers: CoroutineDispatcherProvider,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val onrampStatusFactory: OnrampStatusFactory,
) : BaseWalletClickIntents(),
@ -88,6 +87,10 @@ internal class WalletClickIntents @Inject constructor(
stateHolder.update { it.copy(selectedWalletIndex = index) }
maybeUserWallet.onRight {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
launch { walletContentFetcher(userWalletId = it.walletId) }
}
walletScreenContentLoader.load(
userWallet = it,
clickIntents = this@WalletClickIntents,
@ -127,36 +130,36 @@ internal class WalletClickIntents @Inject constructor(
)
modelScope.launch {
val maybeFetchResult = if (
userWallet is UserWallet.Cold &&
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
) {
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
} else {
fetchTokenListUseCase(userWalletId = userWallet.walletId, mode = RefreshMode.FULL)
val isSingleWalletWithToken = userWallet is UserWallet.Cold &&
userWallet.cardTypesResolver.isSingleWalletWithToken()
val maybeFetchResult = if (isSingleWalletWithToken) {
fetchCardTokenListUseCase(userWalletId = userWallet.walletId, refresh = true)
} else {
fetchTokenListUseCase(userWalletId = userWallet.walletId)
}
maybeFetchResult.onLeft {
stateHolder.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = it,
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
),
)
}
}
buildList {
if (!onrampFeatureToggles.isFeatureEnabled) {
async { rampStateManager.fetchBuyServiceData() }.let(::add)
}
async { rampStateManager.fetchSellServiceData() }.let(::add)
async { fetchHotCryptoUseCase() }.let(::add)
}
.awaitAll()
maybeFetchResult.onLeft {
stateHolder.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = it,
appCurrency = getSelectedAppCurrencyUseCase.unwrap(),
),
)
}
stateHolder.update(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
)
@ -172,8 +175,13 @@ internal class WalletClickIntents @Inject constructor(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState),
)
modelScope.launch(dispatchers.main) {
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
modelScope.launch {
if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
walletContentFetcher(userWalletId = userWallet.walletId, forceUpdate = true)
} else {
fetchCurrencyStatusUseCase(userWallet.walletId, refresh = true)
}
onrampStatusFactory.updateOnrmapTransactionStatuses(userWallet)
walletScreenContentLoader.load(
userWallet = userWallet,

View file

@ -61,7 +61,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -129,7 +128,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val shareManager: ShareManager,
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val onrampFeatureToggles: OnrampFeatureToggles,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
@ -345,28 +343,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
if (handleUnavailabilityReason(unavailabilityReason)) return
if (onrampFeatureToggles.isFeatureEnabled) {
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
)
} else {
showErrorIfDemoModeOrElse {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
TradeCryptoAction.Buy(
userWallet = userWallet,
source = OnrampSource.TOKEN_LONG_TAP,
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
),
)
}
}
}
appRouter.push(
AppRoute.Onramp(
userWalletId = userWallet.walletId,
currency = cryptoCurrencyStatus.currency,
source = OnrampSource.TOKEN_LONG_TAP,
),
)
}
override fun onSwapClick(
@ -482,11 +465,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletBuyClick(userWalletId: UserWalletId, screenType: String) {
onMultiWalletActionClick(
statusFlow = if (onrampFeatureToggles.isFeatureEnabled) {
rampStateManager.getExpressInitializationStatus(userWalletId)
} else {
rampStateManager.getBuyInitializationStatus()
},
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
route = AppRoute.BuyCrypto(userWalletId = userWalletId),
eventCreator = { MainScreenAnalyticsEvent.ButtonBuy(status = it, screenType = screenType) },
)

View file

@ -87,6 +87,8 @@ internal interface WalletWarningsClickIntents {
fun onSeedPhraseSecondNotificationAccept()
fun onSeedPhraseSecondNotificationReject()
fun onFinishWalletActivationClick()
}
@Suppress("LongParameterList")
@ -371,6 +373,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onFinishWalletActivationClick() {
// TODO implement wallet activation process
}
private suspend fun fetchCryptoCurrencies(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
coroutineScope {
listOf(

View file

@ -0,0 +1,24 @@
package com.tangem.feature.wallet.deeplink
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.receiveAsFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class DefaultWalletDeepLinkActionTrigger @Inject constructor() :
WalletDeepLinkActionTrigger,
WalletDeepLinkActionListener {
private val _selectWalletFlow = Channel<UserWalletId>()
override val selectWalletFlow: Flow<UserWalletId>
get() = _selectWalletFlow.receiveAsFlow()
override fun selectWallet(userWalletId: UserWalletId) {
_selectWalletFlow.trySend(userWalletId)
}
}

View file

@ -1,6 +1,9 @@
package com.tangem.feature.wallet.deeplink.di
import com.tangem.feature.wallet.deeplink.DefaultWalletDeepLinkActionTrigger
import com.tangem.feature.wallet.deeplink.DefaultWalletDeepLinkHandler
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
import com.tangem.features.wallet.deeplink.WalletDeepLinkHandler
import dagger.Binds
import dagger.Module
@ -15,4 +18,12 @@ internal interface WalletDeepLinkModule {
@Binds
@Singleton
fun bindWalletDeepLinkHandlerFactory(impl: DefaultWalletDeepLinkHandler.Factory): WalletDeepLinkHandler.Factory
@Binds
@Singleton
fun bindWalletDeepLinkActionTrigger(impl: DefaultWalletDeepLinkActionTrigger): WalletDeepLinkActionTrigger
@Binds
@Singleton
fun bindWalletDeepLinkActionListener(impl: DefaultWalletDeepLinkActionTrigger): WalletDeepLinkActionListener
}

View file

@ -117,6 +117,7 @@ internal object WalletScreenPreviewData {
buttons = persistentListOf(buyButton),
warnings = persistentListOf(
WalletNotification.Warning.SomeNetworksUnreachable,
WalletNotification.FinishWalletActivation { },
),
bottomSheetConfig = null,
tokensListState = textContentTokensState,

View file

@ -1,94 +0,0 @@
package com.tangem.feature.wallet.presentation.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.deeplink.DeepLink
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.BuyCurrencyDeepLink
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.utils.coroutines.launchOnCancellation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
internal class WalletDeepLinksHandler @Inject constructor(
private val deepLinksRegistry: DeepLinksRegistry,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val router: AppRouter,
) {
private var deepLinksMap = mutableMapOf<UserWalletId, List<DeepLink>>()
fun registerForWallet(scope: CoroutineScope, userWallet: UserWallet) {
val deepLinks = deepLinksMap.getOrPut(userWallet.walletId) {
getDeepLinks(userWallet, scope)
}
deepLinksRegistry.unregisterByIds(deepLinks.map { it.id })
deepLinksRegistry.register(deepLinks = deepLinks)
// When navigation to another screen scope is Cancelled and deeplinks are hot handled
scope.launchOnCancellation {
deepLinksRegistry.unregister(deepLinks)
}
}
private fun getDeepLinks(userWallet: UserWallet, scope: CoroutineScope): List<DeepLink> {
val sellCurrencyDeepLink = SellCurrencyDeepLink(
onReceive = { data ->
scope.launch {
onSellCurrencyDeepLink(userWallet, data)
}
},
shouldHandleDelayed = true,
)
return buildList {
add(sellCurrencyDeepLink)
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
add(
BuyCurrencyDeepLink(
onReceive = {
scope.launch { onBuyCurrencyDeepLink(userWallet) }
},
),
)
}
}
}
private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet, data.currencyId).getOrNull()
if (cryptoCurrency == null) {
Timber.e("onSellCurrencyDeepLink cryptoCurrency is null")
return
}
val route = AppRoute.Send(
currency = cryptoCurrency,
userWalletId = userWallet.walletId,
transactionId = data.transactionId,
destinationAddress = data.depositWalletAddress,
amount = data.baseCurrencyAmount,
tag = data.depositWalletAddressTag,
)
router.push(route)
}
private suspend fun onBuyCurrencyDeepLink(userWallet: UserWallet) {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrNull() ?: return
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
}

View file

@ -61,6 +61,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Warning.NetworksUnreachable,
is WalletNotification.UsedOutdatedData,
is WalletNotification.UnlockVisaAccess,
is WalletNotification.FinishWalletActivation,
-> null
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
is WalletNotification.Critical.SeedPhraseSecondNotification -> MainScreen.NoticeSeedPhraseSupportSecond

View file

@ -55,6 +55,8 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addFinishWalletActivationNotification(userWallet, clickIntents)
addReferralPromoNotification(cardTypesResolver, clickIntents, shouldShowReferralPromo)
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
@ -259,6 +261,23 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
if (condition) add(element = element)
}
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
) {
if (userWallet !is UserWallet.Hot) return
// TODO [REDACTED_TASK_KEY] set an actual value
val shouldShowFinishActivation = false
addIf(
element = WalletNotification.FinishWalletActivation(
onFinishClick = clickIntents::onFinishWalletActivationClick,
),
condition = shouldShowFinishActivation,
)
}
private companion object {
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}

View file

@ -2,21 +2,17 @@ package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.features.nft.NFTFeatureToggles
import kotlinx.coroutines.flow.firstOrNull
class IsWalletNFTEnabledSyncUseCase(
private val walletsRepository: WalletsRepository,
private val nftFeatureToggles: NFTFeatureToggles,
) {
suspend operator fun invoke(userWalletId: UserWalletId): Boolean = if (nftFeatureToggles.isNFTEnabled) {
walletsRepository
.nftEnabledStatuses()
suspend operator fun invoke(userWalletId: UserWalletId): Boolean {
val isNFTEnabled = walletsRepository.nftEnabledStatuses()
.firstOrNull()
?.let { it[userWalletId] }
?: false
} else {
false
?.get(userWalletId)
return isNFTEnabled == true
}
}

View file

@ -39,10 +39,22 @@ internal object WalletAdditionalInfoFactory {
wallet.resolveSingleCurrencyInfo(currencyAmount)
}
}
is UserWallet.Hot -> TODO("[REDACTED_TASK_KEY]")
is UserWallet.Hot -> wallet.resolveAdditionalInfo()
}
}
private fun UserWallet.Hot.resolveAdditionalInfo(): WalletAdditionalInfo {
return WalletAdditionalInfo(
hideable = false,
content = TextReference.Res(R.string.hw_mobile_wallet) +
when {
isLocked -> DIVIDER + TextReference.Res(R.string.common_locked)
backedUp.not() -> DIVIDER + TextReference.Res(R.string.hw_backup_no_backup)
else -> TextReference.Str("")
},
)
}
private fun UserWallet.Cold.resolveMultiCurrencyInfo(): WalletAdditionalInfo {
return if (isLocked) {
WalletAdditionalInfo(

View file

@ -0,0 +1,75 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveInAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
/**
* Wallet content fetcher
*
* @property walletBalanceFetcher fetcher for wallet balance
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class WalletContentFetcher @Inject constructor(
private val walletBalanceFetcher: WalletBalanceFetcher,
private val dispatchers: CoroutineDispatcherProvider,
) {
private val fetchingJobMap = ConcurrentHashMap<UserWalletId, JobHolder>()
private val mutex = Mutex()
suspend operator fun invoke(userWalletId: UserWalletId, forceUpdate: Boolean = false) = supervisorScope {
withContext(dispatchers.default) {
// Use mutex to ensure thread safety
val jobHolder = mutex.withLock {
val savedJobHolder = fetchingJobMap.getOrPut(key = userWalletId, defaultValue = ::JobHolder)
/*
* If this is not a forced update and there is a saved job in the cache
* (doesn't matter if it is active or not), then skip the update process.
*/
if (!forceUpdate && savedJobHolder != null && !savedJobHolder.isEmpty()) {
Timber.d("Skip fetching for $userWalletId")
return@withContext
}
/*
* If this is a forced update and there is already a job in the cache that is updating the balance,
* then cancel the previous update.
*/
if (forceUpdate && savedJobHolder?.isActive == true) {
Timber.d("Cancel old fetching for $userWalletId")
savedJobHolder.cancel()
}
JobHolder().also { fetchingJobMap[userWalletId] = it }
}
Timber.d("Start fetching for $userWalletId")
val maybeResult = launch {
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
.onLeft(Timber::e)
}
.saveInAndJoin(jobHolder)
Timber.d("Finish fetching with result $maybeResult for $userWalletId")
}
}
}

View file

@ -24,7 +24,11 @@ internal class WalletImageResolver @Inject constructor(
/** Get a specified wallet [userWallet] image */
@Suppress("CyclomaticComplexMethod")
@DrawableRes
fun resolve(userWallet: UserWallet.Cold): Int? {
fun resolve(userWallet: UserWallet): Int? {
if (userWallet !is UserWallet.Cold) {
return null
}
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val cobrandImage = Wallet2CobrandImage.entries.firstOrNull {

View file

@ -31,7 +31,7 @@ internal class WalletContentLoaderFactory @Inject constructor(
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isVisaWallet() -> {
visaWalletContentLoaderFactory.create(userWallet, clickIntents, isRefresh)
}
!userWallet.isMultiCurrency -> {
userWallet is UserWallet.Cold && !userWallet.isMultiCurrency -> {
singleWalletContentLoaderFactory.create(userWallet, clickIntents, isRefresh)
}
else -> null

View file

@ -1,8 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
@ -21,11 +19,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import com.tangem.features.nft.NFTFeatureToggles
@Suppress("LongParameterList")
@ModelScoped
@ -45,11 +38,8 @@ internal class MultiWalletContentLoader(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
private val nftFeatureToggles: NFTFeatureToggles,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val routingFeatureToggle: RoutingFeatureToggle,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -64,19 +54,17 @@ internal class MultiWalletContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
routingFeatureToggle = routingFeatureToggle,
).let(::add)
if (nftFeatureToggles.isNFTEnabled) {
WalletNFTListSubscriber(
userWallet = userWallet,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
stateHolder = stateHolder,
walletsRepository = walletsRepository,
clickIntents = clickIntents,
currenciesRepository = currenciesRepository,
).let(::add)
}
WalletNFTListSubscriber(
userWallet = userWallet,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
stateHolder = stateHolder,
walletsRepository = walletsRepository,
clickIntents = clickIntents,
currenciesRepository = currenciesRepository,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -85,11 +73,13 @@ internal class MultiWalletContentLoader(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
).let(::add)
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,

View file

@ -1,8 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
@ -20,7 +18,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.features.nft.NFTFeatureToggles
import javax.inject.Inject
@Suppress("LongParameterList")
@ -38,11 +35,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
private val nftFeatureToggles: NFTFeatureToggles,
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val routingFeatureToggle: RoutingFeatureToggle,
private val currenciesRepository: CurrenciesRepository,
) {
@ -62,11 +56,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
deepLinksRegistry = deepLinksRegistry,
nftFeatureToggles = nftFeatureToggles,
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
routingFeatureToggle = routingFeatureToggle,
currenciesRepository = currenciesRepository,
)
}

View file

@ -19,7 +19,7 @@ import com.tangem.feature.wallet.presentation.wallet.subscribers.*
@Suppress("LongParameterList")
internal class SingleWalletContentLoader(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val isRefresh: Boolean,
private val stateHolder: WalletStateController,

View file

@ -36,7 +36,7 @@ internal class SingleWalletContentLoaderFactory @Inject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
return SingleWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,

View file

@ -1,7 +1,5 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
@ -19,7 +17,7 @@ import com.tangem.feature.wallet.presentation.wallet.subscribers.*
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoader(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val stateHolder: WalletStateController,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
@ -32,8 +30,6 @@ internal class SingleWalletWithTokenContentLoader(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
private val routingFeatureToggle: RoutingFeatureToggle,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -47,8 +43,6 @@ internal class SingleWalletWithTokenContentLoader(
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
routingFeatureToggle = routingFeatureToggle,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,

View file

@ -1,13 +1,12 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
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
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
@ -15,7 +14,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import javax.inject.Inject
// TODO: Refactor
@ -33,11 +31,9 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
private val routingFeatureToggle: RoutingFeatureToggle,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
return SingleWalletWithTokenContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
@ -52,8 +48,6 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
deepLinksRegistry = deepLinksRegistry,
routingFeatureToggle = routingFeatureToggle,
)
}
}

View file

@ -9,7 +9,7 @@ import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscribe
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
internal class VisaWalletContentLoader(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val isRefresh: Boolean,
private val stateController: WalletStateController,

View file

@ -15,7 +15,7 @@ internal class VisaWalletContentLoaderFactory @Inject constructor(
private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
return VisaWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,

View file

@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarCon
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.WalletScreenStateTransformer
import com.tangem.utils.extensions.indexOfFirstOrNull
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -68,6 +69,10 @@ internal class WalletStateController @Inject constructor() {
return with(value) { wallets[selectedWalletIndex].walletCardState.id }
}
fun getWalletIndexByWalletId(userWalletId: UserWalletId): Int? {
return with(value) { wallets.indexOfFirstOrNull { it.walletCardState.id == userWalletId } }
}
fun showBottomSheet(
content: TangemBottomSheetConfigContent,
userWalletId: UserWalletId = getSelectedWalletId(),

View file

@ -255,6 +255,20 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class FinishWalletActivation(
val onFinishClick: () -> Unit,
) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.hw_activation_need_title),
subtitle = resourceReference(R.string.hw_activation_need_description),
iconResId = R.drawable.img_knight_shield_32,
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = onFinishClick,
),
),
)
data class ReferralPromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.isLocked
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.*
@ -55,8 +54,6 @@ internal class InitializeWalletsTransformer(
}
private fun createLockedState(userWallet: UserWallet): WalletState {
userWallet.requireColdWallet()
return userWallet.createStateByWalletType(
multiCurrencyCreator = {
WalletState.MultiCurrency.Locked(
@ -87,7 +84,7 @@ internal class InitializeWalletsTransformer(
)
}
private fun UserWallet.Cold.toLockedWalletCardState(): WalletCardState {
private fun UserWallet.toLockedWalletCardState(): WalletCardState {
return WalletCardState.LockedContent(
id = walletId,
title = name,

View file

@ -6,7 +6,6 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
@ -57,8 +56,6 @@ internal class SetTokenListErrorTransformer(
}
private fun WalletCardState.toLoadedState(): WalletCardState {
selectedWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
return WalletCardState.Content(
id = id,
title = title,
@ -68,7 +65,10 @@ internal class SetTokenListErrorTransformer(
balance = BigDecimal.ZERO.format {
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
},
cardCount = selectedWallet.getCardsCount(),
cardCount = when (selectedWallet) {
is UserWallet.Cold -> selectedWallet.getCardsCount()
is UserWallet.Hot -> null
},
isZeroBalance = true,
isBalanceFlickering = false,
)

View file

@ -13,7 +13,6 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
@ -27,7 +26,7 @@ import org.joda.time.DateTime
import org.joda.time.Days
internal class SetVisaInfoTransformer(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
private val clickIntents: WalletClickIntents,
) : TypedWalletStateTransformer<WalletState.Visa.Content>(
@ -76,8 +75,6 @@ internal class SetVisaInfoTransformer(
}
private fun getContentWalletCardState(prevState: WalletCardState, visaCurrency: VisaCurrency): WalletCardState {
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
return with(prevState) {
WalletCardState.Content(
id = id,

View file

@ -7,7 +7,6 @@ import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.models.StatusSource
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.utils.converter.Converter
@ -48,8 +47,6 @@ internal class MultiWalletCardStateConverter(
}
private fun WalletCardState.toWalletCardState(fiatBalance: TotalFiatBalance.Loaded): WalletCardState {
selectedWallet.requireColdWallet()
return WalletCardState.Content(
id = id,
title = title,
@ -60,7 +57,10 @@ internal class MultiWalletCardStateConverter(
fiat(fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol)
},
isZeroBalance = fiatBalance.amount.isZero(),
cardCount = selectedWallet.getCardsCount(),
cardCount = when (selectedWallet) {
is UserWallet.Cold -> selectedWallet.getCardsCount()
is UserWallet.Hot -> null
},
isBalanceFlickering = fiatBalance.source == StatusSource.CACHE,
)
}

View file

@ -7,7 +7,6 @@ import com.tangem.domain.common.util.getCardsCount
import com.tangem.domain.models.StatusSource
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.utils.StringsSigns.DASH_SIGN
@ -64,7 +63,10 @@ internal class SingleWalletCardStateConverter(
imageResId = imageResId,
dropDownItems = dropDownItems,
balance = formatFiatAmount(status = status, appCurrency = appCurrency),
cardCount = selectedWallet.requireColdWallet().getCardsCount(), // TODO [REDACTED_TASK_KEY]
cardCount = when (selectedWallet) {
is UserWallet.Cold -> selectedWallet.getCardsCount()
is UserWallet.Hot -> null
},
isZeroBalance = status.fiatAmount?.isZero(),
isBalanceFlickering = (status as? CryptoCurrencyStatus.Loaded)?.sources?.total == StatusSource.CACHE,
)

View file

@ -4,14 +4,17 @@ import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
internal inline fun UserWallet.Cold.createStateByWalletType(
internal inline fun UserWallet.createStateByWalletType(
multiCurrencyCreator: () -> WalletState.MultiCurrency,
singleCurrencyCreator: () -> WalletState.SingleCurrency,
visaWalletCreator: () -> WalletState.Visa,
): WalletState = when {
isVisaWallet() -> visaWalletCreator()
isWalletWithTokens() -> multiCurrencyCreator()
else -> singleCurrencyCreator()
): WalletState = when (this) {
is UserWallet.Cold -> when {
isVisaWallet() -> visaWalletCreator()
isWalletWithTokens() -> multiCurrencyCreator()
else -> singleCurrencyCreator()
}
is UserWallet.Hot -> multiCurrencyCreator()
}
private fun UserWallet.Cold.isWalletWithTokens(): Boolean {

View file

@ -6,7 +6,6 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
@ -26,12 +25,35 @@ internal class WalletLoadingStateFactory(
) {
fun create(userWallet: UserWallet): WalletState {
userWallet.requireColdWallet()
return when (userWallet) {
is UserWallet.Cold -> {
userWallet.createStateByWalletType(
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
visaWalletCreator = { createLoadingVisaWalletContent(userWallet) },
)
}
is UserWallet.Hot -> {
createLoadingHotWalletContent(userWallet)
}
}
}
return userWallet.createStateByWalletType(
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
visaWalletCreator = { createLoadingVisaWalletContent(userWallet) },
private fun createLoadingHotWalletContent(userWallet: UserWallet.Hot): WalletState.MultiCurrency.Content {
return WalletState.MultiCurrency.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = WalletCardState.Loading(
id = userWallet.walletId,
title = userWallet.name,
additionalInfo = null, // TODO [REDACTED_TASK_KEY]
imageResId = null, // TODO [REDACTED_TASK_KEY]
dropDownItems = persistentListOf(),
),
buttons = createMultiWalletActions(userWallet),
warnings = persistentListOf(),
bottomSheetConfig = null,
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
)
}
@ -99,15 +121,21 @@ internal class WalletLoadingStateFactory(
)
}
private fun createMultiWalletActions(userWallet: UserWallet.Cold): PersistentList<WalletManageButton> {
val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
private fun createMultiWalletActions(userWallet: UserWallet): PersistentList<WalletManageButton> {
val isSingleWalletWithToken =
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
if (isSingleWalletWithToken) return persistentListOf()
return persistentListOf(
WalletManageButton.Buy(
enabled = true,
dimContent = false,
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId, WALLET_TYPE) },
onClick = {
clickIntents.onMultiWalletBuyClick(
userWalletId = userWallet.walletId,
WALLET_TYPE,
)
},
),
WalletManageButton.Swap(
enabled = true,
@ -124,14 +152,24 @@ internal class WalletLoadingStateFactory(
private fun createVisaDimmedButtons(): PersistentList<WalletManageButton> {
return persistentListOf(
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
WalletManageButton.Receive(
enabled = true,
dimContent = true,
onClick = {},
onLongClick = null,
),
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
)
}
private fun createDimmedButtons(): PersistentList<WalletManageButton> {
return persistentListOf(
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
WalletManageButton.Receive(
enabled = true,
dimContent = true,
onClick = {},
onLongClick = null,
),
WalletManageButton.Send(enabled = true, dimContent = true, onClick = {}),
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
WalletManageButton.Sell(enabled = true, dimContent = true, onClick = {}),

View file

@ -1,10 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import arrow.core.getOrElse
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.core.deeplink.global.ReferralDeepLink
import com.tangem.core.deeplink.global.SellCurrencyDeepLink
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
@ -39,8 +35,6 @@ internal abstract class BasicTokenListSubscriber(
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val deepLinksRegistry: DeepLinksRegistry,
private val routingFeatureToggle: RoutingFeatureToggle,
) : WalletSubscriber() {
private val sendAnalyticsJobHolder = JobHolder()
@ -48,6 +42,8 @@ internal abstract class BasicTokenListSubscriber(
protected abstract fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList>
protected abstract suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>)
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return combine(
flow = tokenListFlow(coroutineScope)
@ -114,21 +110,6 @@ internal abstract class BasicTokenListSubscriber(
}
}
protected open suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
if (!routingFeatureToggle.isDeepLinkNavigationEnabled) {
/* no-op */
// Handling sell deeplink requires full content in order to correctly open Send screen
if (maybeTokenList.getOrNull(false) != null) {
deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = SellCurrencyDeepLink::class.java)
}
// Handling referral deeplink requires only selected wallet to be loaded
// This is temporary solution, will be removed with complete deeplink navigation overhaul
if (maybeTokenList.getOrNull(true) != null) {
deepLinksRegistry.triggerDelayedDeeplink(deepLinkClass = ReferralDeepLink::class.java)
}
}
}
private suspend fun sendTokenListAnalytics(maybeTokenList: Lce<TokenListError, TokenList>) {
val displayedState = stateHolder.getWalletStateIfSelected(userWallet.walletId)

View file

@ -1,7 +1,5 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
@ -30,8 +28,6 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
deepLinksRegistry: DeepLinksRegistry,
routingFeatureToggle: RoutingFeatureToggle,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -40,8 +36,6 @@ internal class MultiWalletTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
routingFeatureToggle = routingFeatureToggle,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
@ -52,7 +46,6 @@ internal class MultiWalletTokenListSubscriber(
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) {
updateSortingIfNeeded(maybeTokenList)
super.onTokenListReceived(maybeTokenList)
}
private suspend fun updateSortingIfNeeded(maybeTokenList: Lce<*, TokenList>) {

View file

@ -1,8 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.common.routing.RoutingFeatureToggle
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.tokens.error.TokenListError
@ -17,7 +16,7 @@ import kotlinx.coroutines.CoroutineScope
@Suppress("LongParameterList")
internal class SingleWalletWithTokenListSubscriber(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val tokenListStore: MultiWalletTokenListStore,
stateHolder: WalletStateController,
clickIntents: WalletClickIntents,
@ -25,8 +24,6 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker: WalletWithFundsChecker,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
deepLinksRegistry: DeepLinksRegistry,
routingFeatureToggle: RoutingFeatureToggle,
) : BasicTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -35,8 +32,6 @@ internal class SingleWalletWithTokenListSubscriber(
walletWithFundsChecker = walletWithFundsChecker,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
routingFeatureToggle = routingFeatureToggle,
) {
override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow<TokenListError, TokenList> {
@ -44,4 +39,6 @@ internal class SingleWalletWithTokenListSubscriber(
return tokenListStore.getOrThrow(userWallet.walletId)
}
override suspend fun onTokenListReceived(maybeTokenList: Lce<TokenListError, TokenList>) = Unit
}

View file

@ -13,7 +13,6 @@ import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
@ -32,7 +31,7 @@ typealias MaybeTxHistoryItems = Either<TxHistoryListError, Flow<PagingData<TxInf
@Suppress("LongParameterList")
internal class TxHistorySubscriber(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val isRefresh: Boolean,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntents,
@ -97,7 +96,7 @@ internal class TxHistorySubscriber(
)
},
ifRight = { itemsFlow ->
val blockchain = userWallet.requireColdWallet().scanResponse.cardTypesResolver.getBlockchain()
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
val itemConverter = TxHistoryItemStateConverter(
symbol = blockchain.currency,
decimals = blockchain.decimals(),

View file

@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.map
import timber.log.Timber
internal class VisaWalletSubscriber(
private val userWallet: UserWallet,
private val userWallet: UserWallet.Cold,
private val stateController: WalletStateController,
private val isRefresh: Boolean,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,

View file

@ -62,7 +62,8 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.test.MarketTooltipTestTags
import com.tangem.core.ui.utils.lineTo
import com.tangem.core.ui.utils.moveTo
import com.tangem.core.ui.utils.toPx
@ -178,7 +179,7 @@ private fun WalletContent(
} ?: PaddingValues(bottom = TangemTheme.dimens.spacing92 + bottomBarHeight)
LazyColumn(
modifier = Modifier.testTag(TestTags.MAIN_SCREEN),
modifier = Modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,
@ -490,7 +491,9 @@ private fun MarketsTooltip(
val slideOffset = 40.dp.toPx()
AnimatedVisibility(
modifier = modifier.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) },
modifier = modifier
.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) }
.testTag(MarketTooltipTestTags.CONTAINER),
visible = visible,
enter = slideIn(
animationSpec = spring(

View file

@ -20,11 +20,13 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.WalletCard
@ -63,7 +65,9 @@ internal fun WalletsList(
WalletCard(
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.width(itemWidth),
modifier = Modifier
.width(itemWidth)
.testTag(MainScreenTestTags.WALLET_LIST_ITEM),
)
}
}

View file

@ -25,6 +25,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
@ -48,6 +49,7 @@ import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemDimens
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDropDownItems
@ -278,7 +280,9 @@ private fun Balance(state: WalletCardState, isBalanceHidden: Boolean, modifier:
when (state) {
is WalletCardState.Content -> {
ResizableText(
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
modifier = Modifier
.defaultMinSize(minHeight = TangemTheme.dimens.size32)
.testTag(MainScreenTestTags.WALLET_BALANCE),
text = balance,
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
overflow = TextOverflow.Ellipsis,

View file

@ -9,8 +9,7 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TestTags.MAIN_SCREEN_MORE_BUTTON
import com.tangem.core.ui.test.TestTags.MAIN_SCREEN_TOP_BAR
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
@ -28,7 +27,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
},
actions = {
IconButton(onClick = config.onDetailsClick, modifier = Modifier.testTag(MAIN_SCREEN_MORE_BUTTON)) {
IconButton(onClick = config.onDetailsClick, modifier = Modifier.testTag(MainScreenTestTags.MORE_BUTTON)) {
Icon(painter = painterResource(id = R.drawable.ic_more_vertical_24), contentDescription = null)
}
},
@ -38,7 +37,7 @@ internal fun WalletTopBar(config: WalletTopBarConfig) {
actionIconContentColor = TangemTheme.colors.icon.primary1,
),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
modifier = Modifier.testTag(MAIN_SCREEN_TOP_BAR),
modifier = Modifier.testTag(MainScreenTestTags.TOP_BAR),
)
}

View file

@ -10,13 +10,17 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.components.tokenlist.TokenListItem
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.utils.lazyListItemPosition
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import kotlinx.collections.immutable.ImmutableList
@ -61,11 +65,14 @@ private fun LazyListScope.contentItems(
TokenListItem(
state = item,
isBalanceHidden = isBalanceHidden,
modifier = modifier.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = items.lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
),
modifier = modifier
.roundedShapeItemDecoration(
currentIndex = index,
lastIndex = items.lastIndex,
backgroundColor = TangemTheme.colors.background.primary,
)
.testTag(MainScreenTestTags.TOKEN_LIST_ITEM)
.semantics { lazyListItemPosition = index },
)
},
)

View file

@ -13,7 +13,6 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.core.utils.toLce
import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase
@ -22,12 +21,12 @@ import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetCardImageUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.impl.R
import com.tangem.features.wallet.utils.UserWalletsFetcher
import com.tangem.operations.attestation.ArtworkSize
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -46,6 +45,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
@Assisted private val messageSender: UiMessageSender,
@Assisted private val onlyMultiCurrency: Boolean,
private val getCardImageUseCase: GetCardImageUseCase,
dispatchers: CoroutineDispatcherProvider,
) : UserWalletsFetcher {
private var loadedArtworks: HashMap<UserWalletId, ArtworkModel> = hashMapOf()
@ -54,7 +54,7 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
@OptIn(ExperimentalCoroutinesApi::class)
override val userWallets: Flow<ImmutableList<UserWalletItemUM>> = walletsFlow.transformLatest { wallets ->
val uiModels = UserWalletItemUMConverter(onClick = { onWalletClick(it) }).convertList(wallets)
val uiModels = UserWalletItemUMConverter(onClick = onWalletClick).convertList(wallets)
.toImmutableList()
emit(uiModels)
@ -65,31 +65,31 @@ internal class DefaultUserWalletsFetcher @AssistedInject constructor(
flow3 = getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(),
flow4 = loadArtworks(wallets),
) { maybeAppCurrency, balanceHidingSettings, maybeBalances, artworks ->
val models = createUiModels(
createUiModels(
wallets = wallets,
maybeAppCurrency = maybeAppCurrency,
maybeBalances = maybeBalances,
balanceHidingSettings = balanceHidingSettings,
artworks = artworks,
).getOrElse(
ifLoading = { return@combine },
ifError = {
val message = resourceReference(R.string.common_unknown_error)
messageSender.send(SnackbarMessage(message))
return@combine
},
)
emit(models)
}.collect()
}
.collectLatest { lceItems: Lce<Error, ImmutableList<UserWalletItemUM>> ->
when (lceItems) {
is Lce.Content<ImmutableList<UserWalletItemUM>> -> emit(lceItems.content)
is Lce.Error<Error> -> {
val message = resourceReference(R.string.common_unknown_error)
messageSender.send(SnackbarMessage(message))
}
is Lce.Loading<*> -> Unit
}
}
}
.flowOn(dispatchers.default)
private fun loadArtworks(wallets: List<UserWallet>): Flow<HashMap<UserWalletId, ArtworkModel>> {
return flow {
emit(hashMapOf()) // emits right away so the transform doesn't wait for the images' loading to finish
wallets.forEach { wallet ->
wallet.requireColdWallet()
wallets.filterIsInstance<UserWallet.Cold>().forEach { wallet ->
val artwork = getCardImageUseCase(
cardId = wallet.cardId,
manufacturerName = wallet.scanResponse.card.manufacturer.name,