Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-02 10:43:16 +03:00
commit 4daf088b89
648 changed files with 20086 additions and 2462 deletions

View file

@ -10,9 +10,11 @@ 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.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
import com.tangem.domain.settings.*
import com.tangem.domain.tokens.RefreshMultiCurrencyWalletQuotesUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
@ -38,9 +40,9 @@ import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.biometry.BiometryFeatureToggles
import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION
import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -73,9 +75,10 @@ internal class WalletModel @Inject constructor(
private val walletImageResolver: WalletImageResolver,
private val tokenListStore: MultiWalletTokenListStore,
private val onrampStatusFactory: OnrampStatusFactory,
private val walletFeatureToggles: WalletFeatureToggles,
private val biometryFeatureToggles: BiometryFeatureToggles,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val fetchNFTCollectionsUseCase: FetchNFTCollectionsUseCase,
private val walletsRepository: WalletsRepository,
val screenLifecycleProvider: ScreenLifecycleProvider,
val innerWalletRouter: InnerWalletRouter,
) : Model() {
@ -86,6 +89,7 @@ internal class WalletModel @Inject constructor(
private val walletsUpdateJobHolder = JobHolder()
private val refreshWalletJobHolder = JobHolder()
private val expressStatusJobHolder = JobHolder()
private val walletsNFTsUpdateJobHolder = JobHolder()
private var needToRefreshWallet = false
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
@ -103,6 +107,7 @@ internal class WalletModel @Inject constructor(
subscribeToScreenBackgroundState()
subscribeOnPushNotificationsPermission()
subscribeOnExpressTransactionsUpdates()
subscribeOnNFTUpdates()
clickIntents.initialize(innerWalletRouter, modelScope)
}
@ -160,7 +165,10 @@ internal class WalletModel @Inject constructor(
.conflate()
.distinctUntilChanged()
.map {
walletsUpdateActionResolver.resolve(wallets = it, currentState = stateHolder.value)
walletsUpdateActionResolver.resolve(
wallets = it,
currentState = stateHolder.value,
)
}
.onEach(::updateWallets)
.flowOn(dispatchers.main)
@ -256,6 +264,29 @@ internal class WalletModel @Inject constructor(
}.saveIn(expressStatusJobHolder)
}
@OptIn(ExperimentalCoroutinesApi::class)
private fun subscribeOnNFTUpdates() {
getWalletsUseCase()
.conflate()
.distinctUntilChanged()
.flatMapLatest { wallets ->
wallets
.map { wallet ->
walletsRepository.nftEnabledStatus(wallet.walletId)
.distinctUntilChanged()
.onEach { nftEnabled ->
if (nftEnabled) {
fetchNFTCollectionsUseCase.invoke(wallet.walletId)
}
}
}
.merge()
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
.saveIn(walletsNFTsUpdateJobHolder)
}
private fun needToRefreshTimer() {
modelScope.launch {
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
@ -313,7 +344,6 @@ internal class WalletModel @Inject constructor(
wallets = action.wallets,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
),
)
@ -354,7 +384,6 @@ internal class WalletModel @Inject constructor(
newUserWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
),
)
}
@ -371,7 +400,6 @@ internal class WalletModel @Inject constructor(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
),
)
@ -382,7 +410,7 @@ internal class WalletModel @Inject constructor(
}
}
private suspend fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
private fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
walletScreenContentLoader.cancel(action.deletedWalletId)
tokenListStore.remove(action.deletedWalletId)
@ -427,7 +455,6 @@ internal class WalletModel @Inject constructor(
unlockedWallets = action.unlockedWallets,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
),
)

View file

@ -231,7 +231,10 @@ internal class WalletsUpdateActionResolver @Inject constructor(
* @property prevWalletId previous selected wallet id
* @property selectedWallet selected wallet
*/
data class ReinitializeWallet(val prevWalletId: UserWalletId, val selectedWallet: UserWallet) : Action() {
data class ReinitializeWallet(
val prevWalletId: UserWalletId,
val selectedWallet: UserWallet,
) : Action() {
override fun toString(): String {
return "ReinitializeWallet(prevWalletId = $prevWalletId, selectedWallet = ${selectedWallet.walletId})"

View file

@ -2,29 +2,19 @@ package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.BalancesAndLimitsBottomSheetConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxDetailsBottomSheetConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
internal interface VisaWalletIntents {
fun onDepositClick()
fun onBalancesAndLimitsClick()
fun onVisaTransactionClick(id: String)
@ -36,7 +26,6 @@ internal interface VisaWalletIntents {
internal class VisaWalletIntentsImplementor @Inject constructor(
private val stateController: WalletStateController,
private val eventSender: WalletEventSender,
private val getCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
private val dispatchers: CoroutineDispatcherProvider,
@ -46,38 +35,6 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
BalancesAndLimitsBottomSheetConverter(eventSender)
}
override fun onDepositClick() {
val userWalletId = stateController.getSelectedWalletId()
modelScope.launch(dispatchers.main) {
val currencyStatus = getPrimaryCurrencyStatus(userWalletId) ?: return@launch
createReceiveBottomSheetContent(currencyStatus)?.let { content ->
stateController.showBottomSheet(content)
}
}
}
private fun createReceiveBottomSheetContent(currencyStatus: CryptoCurrencyStatus): TangemBottomSheetConfigContent? {
val currency = currencyStatus.currency
val addresses = currencyStatus.value.networkAddress?.availableAddresses
if (addresses == null) {
Timber.e("Addresses should not be null")
return null
}
return TokenReceiveBottomSheetConfig(
name = currency.name,
symbol = currency.symbol,
network = currency.network.name,
addresses = addresses.mapToAddressModels(currency).toImmutableList(),
showMemoDisclaimer = currency.network.transactionExtrasType != Network.TransactionExtrasType.NONE,
onCopyClick = { /* no-op */ },
onShareClick = { /* no-op */ },
)
}
override fun onBalancesAndLimitsClick() {
modelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()
@ -95,14 +52,6 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
}
}
private suspend fun getPrimaryCurrencyStatus(userWalletId: UserWalletId): CryptoCurrencyStatus? {
return getCurrencyStatusUseCase(userWalletId)
.getOrElse {
Timber.e("Failed to get primary currency $it")
null
}
}
override fun onVisaTransactionClick(id: String) {
modelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()

View file

@ -5,6 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.extenstions.unwrap
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.nft.FetchNFTCollectionsUseCase
import com.tangem.domain.onramp.FetchHotCryptoUseCase
import com.tangem.domain.settings.NeverToShowWalletsScrollPreview
import com.tangem.domain.tokens.FetchCardTokenListUseCase
@ -14,6 +15,7 @@ import com.tangem.domain.tokens.FetchTokenListUseCase.RefreshMode
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.IsWalletNFTEnabledSyncUseCase
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
@ -50,6 +52,8 @@ internal class WalletClickIntents @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val fetchNFTCollectionsUseCase: FetchNFTCollectionsUseCase,
private val isWalletNFTEnabledSyncUseCase: IsWalletNFTEnabledSyncUseCase,
) : BaseWalletClickIntents(),
WalletCardClickIntents by walletCardClickIntentsImplementor,
WalletWarningsClickIntents by warningsClickIntentsImplementer,
@ -137,8 +141,10 @@ internal class WalletClickIntents @Inject constructor(
async { rampStateManager.fetchSellServiceData() }.let(::add)
if (onrampFeatureToggles.isHotTokensEnabled) {
async { fetchHotCryptoUseCase() }.let(::add)
async { fetchHotCryptoUseCase() }.let(::add)
if (isWalletNFTEnabledSyncUseCase.invoke(userWallet.walletId)) {
async { fetchNFTCollectionsUseCase(userWalletId = userWallet.walletId) }.let(::add)
}
}
.awaitAll()

View file

@ -12,6 +12,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
@ -55,6 +56,8 @@ internal interface WalletContentClickIntents {
fun onConfirmDisposeExpressStatus()
fun onDisposeExpressStatus()
fun onNFTClick(userWalletId: UserWalletId)
}
@Suppress("LongParameterList")
@ -235,4 +238,8 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
stateHolder.update(CloseBottomSheetTransformer(userWalletId))
}
override fun onNFTClick(userWalletId: UserWalletId) {
router.openNFTCollectionsScreen(userWalletId)
}
}

View file

@ -56,7 +56,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensLis
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.features.swap.SwapFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -125,7 +124,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val onrampFeatureToggles: OnrampFeatureToggles,
private val swapFeatureToggles: SwapFeatureToggles,
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
override fun onSendClick(
@ -632,9 +630,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
private suspend fun getSwapRoute(targetRoute: AppRoute): AppRoute {
val isSwapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
val maybeSwapStories = getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id)
val showSwapStories = maybeSwapStories.getOrNull() != null && isSwapStoriesEnabled
val showSwapStories = maybeSwapStories.getOrNull() != null
return if (showSwapStories) {
AppRoute.Stories(

View file

@ -1,13 +1,6 @@
package com.tangem.feature.wallet.featuretoggles
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import javax.inject.Inject
internal class DefaultWalletFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) : WalletFeatureToggles {
override val isMainActionButtonsEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "MAIN_ACTION_BUTTONS_ENABLED")
}
internal class DefaultWalletFeatureToggles @Inject constructor() : WalletFeatureToggles

View file

@ -120,6 +120,13 @@ internal object WalletScreenPreviewData {
),
bottomSheetConfig = null,
tokensListState = textContentTokensState,
nftState = WalletNFTItemUM.Content(
previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")),
collectionsCount = 1,
assetsCount = 3,
isFlickering = false,
onItemClick = { },
),
)
}

View file

@ -54,7 +54,7 @@ internal class DefaultWalletRouter @Inject constructor(
AppRoute.Onboarding(
scanResponse = scanResponse,
mode = if (continueBackup) {
AppRoute.Onboarding.Mode.AddBackup
AppRoute.Onboarding.Mode.AddBackupWallet1
} else {
AppRoute.Onboarding.Mode.Onboarding
},
@ -115,4 +115,8 @@ internal class DefaultWalletRouter @Inject constructor(
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
reduxStateHolder.dispatchDialogShow(StateDialog.ScanFailsDialog(StateDialog.ScanFailsSource.MAIN, onTryAgain))
}
override fun openNFTCollectionsScreen(userWalletId: UserWalletId) {
router.push(AppRoute.NFTCollections(userWalletId))
}
}

View file

@ -56,4 +56,7 @@ internal interface InnerWalletRouter {
/** Open scan failed dialog */
fun openScanFailedDialog(onTryAgain: () -> Unit)
/** Open NFT collections screen */
fun openNFTCollectionsScreen(userWalletId: UserWalletId)
}

View file

@ -10,8 +10,7 @@ sealed class WalletScreenAnalyticsEvent {
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
error: Throwable? = null,
) : AnalyticsEvent(category = "Basic", event = event, params = params, error = error) {
) : AnalyticsEvent(category = "Basic", event = event, params = params) {
class WalletToppedUp(userWalletId: UserWalletId, walletType: AnalyticsParam.WalletType) :
Basic(

View file

@ -0,0 +1,22 @@
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()
.firstOrNull()
?.let { it[userWalletId] }
?: false
} else {
false
}
}

View file

@ -8,7 +8,6 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.card.ScanCardProcessor
import com.tangem.domain.wallets.builder.UserWalletBuilder
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import javax.inject.Inject
@ -16,7 +15,7 @@ import javax.inject.Inject
internal class ScanCardToUnlockWalletClickHandler @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val saveWalletUseCase: SaveWalletUseCase,
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
private val userWalletBuilderFactory: UserWalletBuilder.Factory,
) {
private var scanFailsCounter = 0
@ -34,7 +33,7 @@ internal class ScanCardToUnlockWalletClickHandler @Inject constructor(
scanFailsCounter = 0
// If card's public key is null then user wallet will be null
val scannedWallet = UserWalletBuilder(result.data, generateWalletNameUseCase).build()
val scannedWallet = userWalletBuilderFactory.create(scanResponse = result.data).build()
ensure(walletId == scannedWallet?.walletId) {
ScanCardToUnlockWalletError.WrongCardIsScanned

View file

@ -40,7 +40,7 @@ internal enum class Wallet2CobrandImage(
BitcoinGold(
cards2ResId = R.drawable.ill_bitcoingold_card2_120_106,
cards3ResId = R.drawable.ill_bitcoingold_card3_120_106,
batchIds = setOf("AF71"),
batchIds = setOf("AF71", "AF990016", "AF990009"),
),
BitcoinPizzaDay(
@ -169,6 +169,12 @@ internal enum class Wallet2CobrandImage(
batchIds = setOf("AF95"),
),
Keiro(
cards2ResId = R.drawable.ill_keiro_card2_120_106,
cards3ResId = R.drawable.ill_keiro_card3_120_106,
batchIds = setOf("BB000017"),
),
KishuInu(
cards2ResId = R.drawable.ill_kishu_inu_card2_120_106,
cards3ResId = R.drawable.ill_kishu_inu_card3_120_106,

View file

@ -3,10 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
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
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
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
@ -21,7 +23,7 @@ import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActi
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.swap.SwapFeatureToggles
import com.tangem.features.nft.NFTFeatureToggles
@Suppress("LongParameterList")
@ModelScoped
@ -34,14 +36,16 @@ internal class MultiWalletContentLoader(
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val tokenListStore: MultiWalletTokenListStore,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
private val nftFeatureToggles: NFTFeatureToggles,
private val walletsRepository: WalletsRepository,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
@ -58,6 +62,15 @@ internal class MultiWalletContentLoader(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
).let(::add)
if (nftFeatureToggles.isNFTEnabled) {
WalletNFTListSubscriber(
userWallet = userWallet,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
stateHolder = stateHolder,
walletsRepository = walletsRepository,
clickIntents = clickIntents,
).let(::add)
}
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -66,13 +79,11 @@ internal class MultiWalletContentLoader(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
).let(::add)
if (swapFeatureToggles.isPromoStoriesEnabled) {
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
}
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,

View file

@ -3,10 +3,12 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
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
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
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
@ -16,7 +18,7 @@ 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.swap.SwapFeatureToggles
import com.tangem.features.nft.NFTFeatureToggles
import javax.inject.Inject
@Suppress("LongParameterList")
@ -34,8 +36,10 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
private val nftFeatureToggles: NFTFeatureToggles,
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
@ -54,8 +58,10 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
swapFeatureToggles = swapFeatureToggles,
deepLinksRegistry = deepLinksRegistry,
nftFeatureToggles = nftFeatureToggles,
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
)
}
}

View file

@ -15,7 +15,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.features.swap.SwapFeatureToggles
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoader(
@ -32,7 +31,6 @@ internal class SingleWalletWithTokenContentLoader(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletContentLoader(id = userWallet.walletId) {
@ -57,13 +55,11 @@ internal class SingleWalletWithTokenContentLoader(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
).let(::add)
if (swapFeatureToggles.isPromoStoriesEnabled) {
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
}
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
WalletDropDownItemsSubscriber(
stateHolder = stateHolder,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,

View file

@ -15,7 +15,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.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.swap.SwapFeatureToggles
import javax.inject.Inject
// TODO: Refactor
@ -33,7 +32,6 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) {
@ -52,7 +50,6 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
getStoryContentUseCase = getStoryContentUseCase,
shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase,
swapFeatureToggles = swapFeatureToggles,
deepLinksRegistry = deepLinksRegistry,
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState

View file

@ -1,6 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
internal data class DepositButtonState(
val isEnabled: Boolean,
val onClick: () -> Unit,
)

View file

@ -10,7 +10,9 @@ sealed class WalletNFTItemUM {
data object Loading : WalletNFTItemUM()
data object Empty : WalletNFTItemUM()
data class Empty(
val onItemClick: () -> Unit,
) : WalletNFTItemUM()
data object Failed : WalletNFTItemUM()
@ -19,6 +21,7 @@ sealed class WalletNFTItemUM {
val collectionsCount: Int,
val assetsCount: Int,
val isFlickering: Boolean,
val onItemClick: () -> Unit,
) : WalletNFTItemUM() {
@Immutable

View file

@ -22,6 +22,7 @@ internal sealed interface WalletState : WalletStateHolder {
sealed class MultiCurrency : WalletState {
abstract val tokensListState: WalletTokensListState
abstract val nftState: WalletNFTItemUM
data class Content(
override val pullToRefreshConfig: PullToRefreshConfig,
@ -30,6 +31,7 @@ internal sealed interface WalletState : WalletStateHolder {
override val warnings: ImmutableList<WalletNotification>,
override val bottomSheetConfig: TangemBottomSheetConfig?,
override val tokensListState: WalletTokensListState,
override val nftState: WalletNFTItemUM,
) : MultiCurrency()
data class Locked(
@ -46,6 +48,7 @@ internal sealed interface WalletState : WalletStateHolder {
) {
override val tokensListState = WalletTokensListState.ContentState.Locked
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
}
}
@ -96,7 +99,6 @@ internal sealed interface WalletState : WalletStateHolder {
override val bottomSheetConfig: TangemBottomSheetConfig?,
override val balancesAndLimitBlockState: BalancesAndLimitsBlockState,
override val txHistoryState: TxHistoryState,
val depositButtonState: DepositButtonState,
) : Visa()
data class Locked(

View file

@ -5,21 +5,18 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlinx.collections.immutable.toImmutableList
internal class AddWalletTransformer(
private val userWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
)
}

View file

@ -8,7 +8,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -18,14 +17,12 @@ internal class InitializeWalletsTransformer(
private val wallets: List<UserWallet>,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
)
}
@ -38,7 +35,9 @@ internal class InitializeWalletsTransformer(
if (userWallet.isLocked) {
createLockedState(userWallet)
} else {
walletLoadingStateFactory.create(userWallet)
walletLoadingStateFactory.create(
userWallet = userWallet,
)
}
}
.toImmutableList(),
@ -96,7 +95,7 @@ internal class InitializeWalletsTransformer(
private fun createMultiWalletEnabledButtons(userWallet: UserWallet): PersistentList<WalletManageButton> {
val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
if (!walletFeatureToggles.isMainActionButtonsEnabled || isSingleWalletWithToken) return persistentListOf()
if (isSingleWalletWithToken) return persistentListOf()
return persistentListOf(
WalletManageButton.Buy(enabled = false, dimContent = false, onClick = {}),

View file

@ -2,11 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlinx.collections.immutable.toImmutableList
/**
@ -23,14 +22,12 @@ internal class ReinitializeWalletTransformer(
private val newUserWallet: UserWallet,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
)
}
@ -38,7 +35,11 @@ internal class ReinitializeWalletTransformer(
return prevState.copy(
wallets = prevState.wallets
.filterNot { it.walletCardState.id == prevWalletId }
.plus(element = walletLoadingStateFactory.create(newUserWallet))
.plus(
element = walletLoadingStateFactory.create(
userWallet = newUserWallet,
),
)
.toImmutableList(),
)
}

View file

@ -0,0 +1,23 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
internal class RemoveNFTCollectionsTransformer(
userWalletId: UserWalletId,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState = when (prevState) {
is WalletState.MultiCurrency.Content -> prevState.copy(
nftState = WalletNFTItemUM.Hidden,
)
is WalletState.SingleCurrency.Content,
is WalletState.Visa.Content,
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> prevState
}
}

View file

@ -0,0 +1,76 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.StatusSource
import com.tangem.domain.nft.models.*
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlinx.collections.immutable.toPersistentList
internal class SetNFTCollectionsTransformer(
userWalletId: UserWalletId,
private val nftCollections: List<NFTCollections>,
private val onItemClick: () -> Unit,
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState = when (prevState) {
is WalletState.MultiCurrency.Content -> prevState.copy(
nftState = when {
nftCollections.allCollectionsFailed() ->
WalletNFTItemUM.Failed
nftCollections.anyCollectionFailed() && nftCollections.allLoadedCollectionsEmpty() ->
WalletNFTItemUM.Failed
nftCollections.allCollectionsLoaded() && nftCollections.allCollectionsEmpty() ->
WalletNFTItemUM.Empty(onItemClick)
!nftCollections.allCollectionsLoaded() && nftCollections.allCollectionsEmpty() ->
WalletNFTItemUM.Loading
else -> createContentNFTItemUM(onItemClick)
},
)
is WalletState.SingleCurrency.Content,
is WalletState.Visa.Content,
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> prevState
}
private fun createContentNFTItemUM(onItemClick: () -> Unit): WalletNFTItemUM.Content {
val collectionsContent = nftCollections
.map { it.content }
.filterIsInstance<NFTCollections.Content.Collections>()
val isFlickering = collectionsContent
.map { it.source }
.any { it == StatusSource.CACHE }
val collections = collectionsContent
.mapNotNull { it.collections }
.flatten()
return WalletNFTItemUM.Content(
previews = if (collections.size > NFT_COLLECTIONS_MAX_PREVIEWS_COUNT) {
collections
.take(NFT_COLLECTIONS_MAX_PREVIEWS_COUNT - 1)
.map { WalletNFTItemUM.Content.CollectionPreview.Image(it.logoUrl.orEmpty()) }
.plus(WalletNFTItemUM.Content.CollectionPreview.More)
.toPersistentList()
} else {
collections
.take(NFT_COLLECTIONS_MAX_PREVIEWS_COUNT)
.map { WalletNFTItemUM.Content.CollectionPreview.Image(it.logoUrl.orEmpty()) }
.toPersistentList()
},
collectionsCount = collections.size,
assetsCount = collections
.sumOf { it.count },
isFlickering = isFlickering,
onItemClick = onItemClick,
)
}
companion object {
private const val NFT_COLLECTIONS_MAX_PREVIEWS_COUNT = 4
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -29,8 +28,8 @@ internal class SetRefreshStateTransformer(
}
is WalletState.Visa.Content -> {
prevState.copy(
buttons = prevState.buttons.toUpdatedState(),
pullToRefreshConfig = prevState.pullToRefreshConfig.toUpdatedState(isRefreshing),
depositButtonState = prevState.depositButtonState.toUpdatedState(isRefreshing),
balancesAndLimitBlockState = prevState.balancesAndLimitBlockState.toUpdatedState(isRefreshing),
)
}
@ -75,10 +74,6 @@ internal class SetRefreshStateTransformer(
}
}
private fun DepositButtonState.toUpdatedState(isRefreshing: Boolean): DepositButtonState {
return copy(isEnabled = !isRefreshing)
}
private fun BalancesAndLimitsBlockState.toUpdatedState(isRefreshing: Boolean): BalancesAndLimitsBlockState {
return when (this) {
is BalancesAndLimitsBlockState.Content -> copy(isEnabled = !isRefreshing)

View file

@ -7,6 +7,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.common.util.getCardsCount
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
@ -15,11 +16,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditiona
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.utils.extensions.isZero
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import org.joda.time.DateTime
import org.joda.time.Days
internal class SetBalancesAndLimitsTransformer(
internal class SetVisaInfoTransformer(
private val userWallet: UserWallet,
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
private val clickIntents: WalletClickIntents,
@ -35,15 +39,15 @@ internal class SetBalancesAndLimitsTransformer(
}
return prevState.copy(
buttons = createVisaButtonsDimmed(),
walletCardState = getErrorWalletCardState(prevState.walletCardState),
depositButtonState = prevState.depositButtonState.copy(isEnabled = false),
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
)
}
return prevState.copy(
buttons = createVisaButtons(visaCurrency = visaCurrency),
walletCardState = getContentWalletCardState(prevState.walletCardState, visaCurrency),
depositButtonState = prevState.depositButtonState.copy(isEnabled = true),
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
)
}
@ -115,4 +119,43 @@ internal class SetBalancesAndLimitsTransformer(
onUnlockVisaAccessNotificationClick = clickIntents::onUnlockVisaAccessClick,
)
}
private fun createVisaButtonsDimmed(): PersistentList<WalletManageButton> {
return persistentListOf(
WalletManageButton.Receive(enabled = true, dimContent = true, onClick = {}, onLongClick = null),
WalletManageButton.Buy(enabled = true, dimContent = true, onClick = {}),
)
}
private fun createVisaButtons(visaCurrency: VisaCurrency): PersistentList<WalletManageButton> {
// [Second Visa Iteration] Make VisaCurrency contain CryptoCurrencyStatus
val cryptoCurrencyStatus = CryptoCurrencyStatus(
currency = visaCurrency.cryptoCurrency,
value = CryptoCurrencyStatus.Loaded(
amount = visaCurrency.balances.available,
fiatAmount = visaCurrency.balances.available.multiply(visaCurrency.fiatRate),
fiatRate = visaCurrency.fiatRate,
priceChange = visaCurrency.priceChange,
yieldBalance = null,
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = visaCurrency.paymentAccountAddress,
sources = CryptoCurrencyStatus.Sources(),
),
)
return return persistentListOf(
WalletManageButton.Receive(
enabled = true,
dimContent = false,
onClick = { clickIntents.onReceiveClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
onLongClick = { clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus = cryptoCurrencyStatus) },
),
WalletManageButton.Buy(
enabled = true,
dimContent = false,
onClick = { clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId) },
),
)
}
}

View file

@ -7,7 +7,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenSta
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
@ -15,14 +14,12 @@ internal class UnlockWalletTransformer(
private val unlockedWallets: List<UserWallet>,
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val walletFeatureToggles: WalletFeatureToggles,
) : WalletScreenStateTransformer {
private val walletLoadingStateFactory by lazy {
WalletLoadingStateFactory(
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
walletFeatureToggles = walletFeatureToggles,
)
}
@ -46,7 +43,9 @@ internal class UnlockWalletTransformer(
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
-> walletLoadingStateFactory.create(userWallet = unlockedWallet)
-> walletLoadingStateFactory.create(
userWallet = unlockedWallet,
)
is WalletState.MultiCurrency.Content,
is WalletState.SingleCurrency.Content,
is WalletState.Visa.Content,

View file

@ -1,15 +1,14 @@
package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
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
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
@ -22,7 +21,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
internal class WalletLoadingStateFactory(
private val clickIntents: WalletClickIntents,
private val walletImageResolver: WalletImageResolver,
private val walletFeatureToggles: WalletFeatureToggles,
) {
fun create(userWallet: UserWallet): WalletState {
@ -41,6 +39,7 @@ internal class WalletLoadingStateFactory(
warnings = persistentListOf(),
bottomSheetConfig = null,
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
)
}
@ -67,7 +66,7 @@ internal class WalletLoadingStateFactory(
return WalletState.Visa.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = userWallet.toLoadingWalletCardState(),
buttons = createMultiWalletActions(userWallet),
buttons = createVisaDimmedButtons(),
warnings = persistentListOf(),
bottomSheetConfig = null,
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading,
@ -76,12 +75,14 @@ internal class WalletLoadingStateFactory(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
),
),
depositButtonState = DepositButtonState(isEnabled = false, clickIntents::onDepositClick),
)
}
private fun createPullToRefreshConfig(): PullToRefreshConfig {
return PullToRefreshConfig(onRefresh = { clickIntents.onRefreshSwipe(it.value) }, isRefreshing = false)
return PullToRefreshConfig(
onRefresh = { clickIntents.onRefreshSwipe(it.value) },
isRefreshing = false,
)
}
private fun UserWallet.toLoadingWalletCardState(): WalletCardState {
@ -96,7 +97,7 @@ internal class WalletLoadingStateFactory(
private fun createMultiWalletActions(userWallet: UserWallet): PersistentList<WalletManageButton> {
val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
if (!walletFeatureToggles.isMainActionButtonsEnabled || isSingleWalletWithToken) return persistentListOf()
if (isSingleWalletWithToken) return persistentListOf()
return persistentListOf(
WalletManageButton.Buy(
@ -117,6 +118,13 @@ internal class WalletLoadingStateFactory(
)
}
private fun createVisaDimmedButtons(): PersistentList<WalletManageButton> {
return persistentListOf(
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),

View file

@ -13,7 +13,7 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetBalancesAndLimitsTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetVisaInfoTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer
@ -62,7 +62,7 @@ internal class VisaWalletSubscriber(
private fun setLoadedCurrencyState(maybeCurrency: Either<Throwable, VisaCurrency>) {
stateController.update(
SetBalancesAndLimitsTransformer(
SetVisaInfoTransformer(
userWallet = userWallet,
maybeVisaCurrency = maybeCurrency,
clickIntents = clickIntents,

View file

@ -0,0 +1,52 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.RemoveNFTCollectionsTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetNFTCollectionsTransformer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
internal class WalletNFTListSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val clickIntents: WalletClickIntents,
) : WalletSubscriber() {
@OptIn(ExperimentalCoroutinesApi::class)
override fun create(coroutineScope: CoroutineScope): Flow<*> = walletsRepository
.nftEnabledStatus(userWallet.walletId)
.distinctUntilChanged()
.flatMapLatest { nftEnabled ->
// if NFT is enabled for this wallet, then start observing changes from store and apply transformer if need
if (nftEnabled) {
getNFTCollectionsUseCase(userWallet.walletId)
.shareIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(),
replay = 1,
)
.onEach {
stateHolder.update(
SetNFTCollectionsTransformer(
userWalletId = userWallet.walletId,
nftCollections = it,
onItemClick = { clickIntents.onNFTClick(userWallet.walletId) },
),
)
}
} else {
// otherwise, hide NFT from wallet
stateHolder.update(
RemoveNFTCollectionsTransformer(userWallet.walletId),
)
emptyFlow()
}
}
}

View file

@ -59,6 +59,7 @@ import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalIsNavigationRefactoringEnabled
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
@ -80,12 +81,12 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.TokenActionsB
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.actions
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.nftCollections
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.BalancesAndLimitsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDetailsBottomSheet
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.MarketsEntryComponent
@ -230,11 +231,6 @@ private fun WalletContent(
}
(selectedWallet as? WalletState.Visa.Content)?.let {
depositButton(
modifier = itemModifier.fillMaxWidth(),
state = it.depositButtonState,
)
balancesAndLimitsBlock(
modifier = itemModifier,
state = it.balancesAndLimitBlockState,
@ -248,6 +244,8 @@ private fun WalletContent(
modifier = movableItemModifier,
)
nftCollections(state = selectedWallet, itemModifier = itemModifier)
organizeTokens(state = selectedWallet, itemModifier = itemModifier)
}
@ -430,7 +428,11 @@ private inline fun BaseScaffoldWithMarkets(
.padding(bottom = 24.dp)
.fillMaxWidth(fraction = 0.7f),
isVisible = state.showMarketsOnboarding,
availableHeight = maxHeight - statusBarHeight - bottomBarHeight,
availableHeight = if (LocalIsNavigationRefactoringEnabled.current) {
maxHeight
} else {
maxHeight - statusBarHeight - bottomBarHeight
},
bottomSheetState = bottomSheetState,
)
}
@ -706,6 +708,15 @@ internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modi
}
}
internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modifier) {
(state as? WalletState.MultiCurrency)?.let {
nftCollections(
modifier = itemModifier,
state = it.nftState,
)
}
}
@Composable
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig != null) {

View file

@ -34,19 +34,19 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun WalletNFTItem(state: WalletNFTItemUM, modifier: Modifier = Modifier, onClick: () -> Unit = { }) {
internal fun WalletNFTItem(state: WalletNFTItemUM, modifier: Modifier = Modifier) {
when (state) {
is WalletNFTItemUM.Hidden -> Unit
is WalletNFTItemUM.Empty -> WalletNFTItemEmpty(
modifier = modifier,
onClick = onClick,
onClick = state.onItemClick,
)
is WalletNFTItemUM.Failed -> WalletNFTItemFailed(modifier = modifier)
is WalletNFTItemUM.Loading -> WalletNFTItemLoading(modifier = modifier)
is WalletNFTItemUM.Content -> WalletNFTItemContent(
state = state,
onClick = onClick,
onClick = state.onItemClick,
modifier = modifier,
)
}
@ -131,7 +131,20 @@ private fun WalletNFTItemFailed(modifier: Modifier = Modifier) {
RowContentContainer(
modifier = modifier,
icon = {
CollectionsPreviewsPlaceholder()
Box(
modifier = Modifier
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
.background(TangemTheme.colors.field.primary),
contentAlignment = Alignment.Center,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size20),
painter = painterResource(R.drawable.ic_error_sync_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
},
text = {
Text(
@ -158,7 +171,12 @@ private fun WalletNFTItemLoading(modifier: Modifier = Modifier) {
RowContentContainer(
modifier = modifier,
icon = {
CollectionsPreviewsPlaceholder()
Box(
modifier = Modifier
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
.background(TangemTheme.colors.field.primary),
)
},
text = {
TextShimmer(
@ -178,16 +196,6 @@ private fun WalletNFTItemLoading(modifier: Modifier = Modifier) {
)
}
@Composable
private fun CollectionsPreviewsPlaceholder(modifier: Modifier = Modifier) {
Box(
modifier = modifier
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(TangemTheme.dimens.radius8))
.background(TangemTheme.colors.field.primary),
)
}
@Composable
@Suppress("MagicNumber")
private fun BoxScope.CollectionsPreviews(previews: ImmutableList<CollectionPreview>, modifier: Modifier = Modifier) {
@ -207,7 +215,7 @@ private fun BoxScope.CollectionsPreviews(previews: ImmutableList<CollectionPrevi
is CollectionPreview.Image -> {
SubcomposeAsyncImage(
modifier = modifier,
model = s,
model = s.url,
loading = {
RectangleShimmer(radius = 0.dp)
},
@ -386,18 +394,17 @@ private fun RowContentContainer(
@Preview(widthDp = 360)
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_WalletNFTItem_InLight(@PreviewParameter(WalletNFTItemProvider::class) state: WalletNFTItemUM) {
private fun Preview_WalletNFTItem(@PreviewParameter(WalletNFTItemProvider::class) state: WalletNFTItemUM) {
TangemThemePreview {
WalletNFTItem(
state = state,
onClick = {},
)
WalletNFTItem(state = state)
}
}
private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletNFTItemUM>(
collection = listOf(
WalletNFTItemUM.Empty,
WalletNFTItemUM.Empty(
onItemClick = { },
),
WalletNFTItemUM.Loading,
WalletNFTItemUM.Failed,
WalletNFTItemUM.Content(
@ -407,6 +414,7 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletN
assetsCount = 125,
collectionsCount = 11,
isFlickering = true,
onItemClick = { },
),
WalletNFTItemUM.Content(
previews = persistentListOf(
@ -416,6 +424,7 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletN
assetsCount = 125,
collectionsCount = 11,
isFlickering = false,
onItemClick = { },
),
WalletNFTItemUM.Content(
previews = persistentListOf(
@ -426,6 +435,7 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletN
assetsCount = 125,
collectionsCount = 11,
isFlickering = false,
onItemClick = { },
),
WalletNFTItemUM.Content(
previews = persistentListOf(
@ -437,6 +447,7 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletN
assetsCount = 125,
collectionsCount = 11,
isFlickering = false,
onItemClick = { },
),
WalletNFTItemUM.Content(
previews = persistentListOf(
@ -448,6 +459,7 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider<WalletN
assetsCount = 125,
collectionsCount = 11,
isFlickering = true,
onItemClick = { },
),
),
)

View file

@ -0,0 +1,17 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletNFTItem
private const val NFT_COLLECTIONS_CONTENT_TYPE = "NFTCollections"
internal fun LazyListScope.nftCollections(state: WalletNFTItemUM, modifier: Modifier = Modifier) {
item(key = NFT_COLLECTIONS_CONTENT_TYPE, contentType = NFT_COLLECTIONS_CONTENT_TYPE) {
WalletNFTItem(
modifier = modifier,
state = state,
)
}
}

View file

@ -1,21 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState
private const val DEPOSIT_BUTTON_CONTENT_TYPE = "DepositButton"
internal fun LazyListScope.depositButton(state: DepositButtonState, modifier: Modifier = Modifier) {
item(key = DEPOSIT_BUTTON_CONTENT_TYPE, contentType = DEPOSIT_BUTTON_CONTENT_TYPE) {
PrimaryButtonIconStart(
modifier = modifier,
text = "Deposit",
iconResId = R.drawable.ic_arrow_down_24,
onClick = state.onClick,
enabled = state.isEnabled,
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB