Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-27 11:22:25 +05:00
commit 961fdc018c
44 changed files with 21 additions and 1832 deletions

View file

@ -1,116 +0,0 @@
package com.tangem.feature.wallet.child.wallet.model.intents
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.wallet.requireColdWallet
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxDetailsUseCase
import com.tangem.domain.visa.model.VisaTxDetails
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
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.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
internal interface VisaWalletIntents {
fun onBalancesAndLimitsClick()
fun onVisaTransactionClick(id: String)
fun onExploreClick(exploreUrl: String)
fun onDisputeClick(txDetails: VisaTxDetails)
}
@Suppress("LongParameterList")
@ModelScoped
internal class VisaWalletIntentsImplementor @Inject constructor(
private val stateController: WalletStateController,
private val eventSender: WalletEventSender,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val getVisaTxDetailsUseCase: GetVisaTxDetailsUseCase,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
private val getUserWalletsUseCase: GetWalletsUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BaseWalletClickIntents(), VisaWalletIntents {
private val balancesAndLimitsBottomSheetConverter by lazy(mode = LazyThreadSafetyMode.NONE) {
BalancesAndLimitsBottomSheetConverter(eventSender, analyticsEventHandler)
}
override fun onBalancesAndLimitsClick() {
analyticsEventHandler.send(MainScreenAnalyticsEvent.LimitsClicked())
modelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()
val balancesAndLimits = getVisaCurrencyUseCase(userWalletId)
.getOrElse {
Timber.e("Unable to get balances and limits: $it")
return@launch
}
val bottomSheetContent = balancesAndLimitsBottomSheetConverter.convert(
value = balancesAndLimits,
)
stateController.showBottomSheet(bottomSheetContent)
}
}
override fun onVisaTransactionClick(id: String) {
modelScope.launch(dispatchers.main) {
val userWalletId = stateController.getSelectedWalletId()
val visaCurrency = getVisaCurrencyUseCase(userWalletId)
.getOrElse {
Timber.e(it, "Failed to get visa currency")
return@launch
}
val transactionDetails = getVisaTxDetailsUseCase(userWalletId, id)
.getOrElse {
Timber.e(it, "Failed to get transaction details")
return@launch
}
val converter = VisaTxDetailsBottomSheetConverter(
visaCurrency,
clickIntents = this@VisaWalletIntentsImplementor,
)
stateController.showBottomSheet(content = converter.convert(transactionDetails))
}
}
override fun onExploreClick(exploreUrl: String) {
analyticsEventHandler.send(MainScreenAnalyticsEvent.ButtonExplore())
router.openUrl(exploreUrl)
}
override fun onDisputeClick(txDetails: VisaTxDetails) {
modelScope.launch {
val userWalletId = stateController.getSelectedWalletId()
val userWallet = getUserWalletsUseCase.invokeSync()
.firstOrNull { it.walletId == userWalletId } ?: return@launch
val cardInfo = getWalletMetaInfoUseCase.invoke(
userWallet.requireColdWallet().scanResponse,
).getOrNull() ?: return@launch
sendFeedbackEmailUseCase(
FeedbackEmailType.Visa.Dispute(
walletMetaInfo = cardInfo,
visaTxDetails = txDetails,
),
)
}
}
}

View file

@ -29,7 +29,6 @@ internal class WalletClickIntents @Inject constructor(
private val warningsClickIntentsImplementer: WalletWarningsClickIntentsImplementor,
private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor,
private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor,
private val visaWalletIntentsImplementor: VisaWalletIntentsImplementor,
private val pushPermissionClickIntentsImplementor: WalletPushPermissionClickIntentsImplementor,
private val stateHolder: WalletStateController,
private val walletScreenContentLoader: WalletScreenContentLoader,
@ -47,7 +46,6 @@ internal class WalletClickIntents @Inject constructor(
WalletWarningsClickIntents by warningsClickIntentsImplementer,
WalletCurrencyActionsClickIntents by currencyActionsClickIntentsImplementor,
WalletContentClickIntents by contentClickIntentsImplementor,
VisaWalletIntents by visaWalletIntentsImplementor,
WalletPushPermissionClickIntents by pushPermissionClickIntentsImplementor,
TangemPayIntents by tangemPayIntents {
@ -58,7 +56,6 @@ internal class WalletClickIntents @Inject constructor(
warningsClickIntentsImplementer.initialize(router, coroutineScope)
currencyActionsClickIntentsImplementor.initialize(router, coroutineScope)
contentClickIntentsImplementor.initialize(router, coroutineScope)
visaWalletIntentsImplementor.initialize(router, coroutineScope)
pushPermissionClickIntentsImplementor.initialize(router, coroutineScope)
tangemPayIntents.initialize(router, coroutineScope)
}
@ -98,14 +95,11 @@ internal class WalletClickIntents @Inject constructor(
refreshMultiCurrencyContent(showRefreshState)
}
is WalletState.SingleCurrency.Content,
is WalletState.Visa.Content,
-> {
refreshSingleCurrencyContent(showRefreshState)
}
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> Unit
}
}

View file

@ -19,7 +19,6 @@ internal class WalletContentLoaderFactory @Inject constructor(
private val accountsFeatureToggles: AccountsFeatureToggles,
private val singleWalletContentLoaderFactory: SingleWalletContentLoaderFactory,
private val singleWalletContentLoaderV2Factory: SingleWalletContentLoaderV2.Factory,
private val visaWalletContentLoaderFactory: VisaWalletContentLoaderFactory,
) {
fun create(
@ -42,9 +41,6 @@ internal class WalletContentLoaderFactory @Inject constructor(
singleWalletWithTokenContentLoaderFactory.create(userWallet, clickIntents)
}
}
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isVisaWallet() -> {
visaWalletContentLoaderFactory.create(userWallet, clickIntents, isRefresh)
}
userWallet is UserWallet.Cold && !userWallet.isMultiCurrency -> {
if (accountsFeatureToggles.isFeatureEnabled) {
singleWalletContentLoaderV2Factory.create(userWallet, isRefresh)

View file

@ -1,32 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxHistoryUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.VisaWalletSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
internal class VisaWalletContentLoader(
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val isRefresh: Boolean,
private val stateController: WalletStateController,
private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return listOf(
VisaWalletSubscriber(
userWallet = userWallet,
stateController = stateController,
isRefresh = isRefresh,
getVisaCurrencyUseCase = getVisaCurrencyUseCase,
getVisaTxHistoryUseCase = getVisaTxHistoryUseCase,
clickIntents = clickIntents,
),
)
}
}

View file

@ -1,28 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxHistoryUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import javax.inject.Inject
@ModelScoped
internal class VisaWalletContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
return VisaWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
isRefresh = isRefresh,
stateController = stateHolder,
getVisaCurrencyUseCase = getVisaCurrencyUseCase,
getVisaTxHistoryUseCase = getVisaTxHistoryUseCase,
)
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
@Immutable
internal sealed class BalancesAndLimitsBlockState {
object Loading : BalancesAndLimitsBlockState()
object Error : BalancesAndLimitsBlockState()
data class Content(
val availableBalance: String,
val limitDays: Int,
val isEnabled: Boolean,
val onClick: () -> Unit,
) : BalancesAndLimitsBlockState()
}

View file

@ -1,26 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
internal data class BalancesAndLimitsBottomSheetConfig(
val balance: Balance,
val limit: Limit,
) : TangemBottomSheetConfigContent {
data class Balance(
val totalBalance: String,
val availableBalance: String,
val blockedBalance: String,
val debit: String,
val amlVerified: String,
val onInfoClick: () -> Unit,
)
data class Limit(
val availableBy: String,
val total: String,
val other: String,
val singleTransaction: String,
val onInfoClick: () -> Unit,
)
}

View file

@ -1,38 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import kotlinx.collections.immutable.ImmutableList
internal data class VisaTxDetailsBottomSheetConfig(
val transaction: Transaction,
val requests: ImmutableList<Request>,
val onDisputeClick: () -> Unit,
) : TangemBottomSheetConfigContent {
data class Transaction(
val id: String,
val type: String,
val status: String,
val blockchainAmount: String,
val transactionAmount: String,
val transactionCurrencyCode: String,
val merchantName: String,
val merchantCity: String,
val merchantCountryCode: String,
val merchantCategoryCode: String,
)
data class Request(
val id: String,
val type: String,
val status: String,
val blockchainAmount: String,
val transactionAmount: String,
val currencyCode: String,
val errorCode: Int,
val date: String,
val txHash: String,
val txStatus: String,
val onExploreClick: (() -> Unit)?,
)
}

View file

@ -12,7 +12,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistor
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.WalletStateHolder
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
internal const val NOT_INITIALIZED_WALLET_INDEX = -1
@ -97,59 +96,4 @@ internal sealed interface WalletState : WalletStateHolder {
override val marketPriceBlockState: MarketPriceBlockState? = null
}
}
sealed class Visa : WalletState, TxHistoryStateHolder {
abstract val balancesAndLimitBlockState: BalancesAndLimitsBlockState?
data class Content(
override val pullToRefreshConfig: PullToRefreshConfig,
override val walletCardState: WalletCardState,
override val buttons: PersistentList<WalletManageButton>,
override val warnings: ImmutableList<WalletNotification>,
override val bottomSheetConfig: TangemBottomSheetConfig?,
override val balancesAndLimitBlockState: BalancesAndLimitsBlockState,
override val txHistoryState: TxHistoryState,
) : Visa()
data class Locked(
override val walletCardState: WalletCardState,
override val buttons: PersistentList<WalletManageButton>,
override val bottomSheetConfig: TangemBottomSheetConfig?,
val onUnlockNotificationClick: () -> Unit,
val onExploreClick: () -> Unit,
) : Visa(),
TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick),
WalletStateHolder by LockedWalletStateHolder(
walletCardState = walletCardState,
buttons = buttons,
bottomSheetConfig = bottomSheetConfig,
onUnlockNotificationClick = onUnlockNotificationClick,
) {
override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null
}
data class AccessTokenLocked(
override val walletCardState: WalletCardState,
override val buttons: PersistentList<WalletManageButton>,
override val bottomSheetConfig: TangemBottomSheetConfig?,
val onExploreClick: () -> Unit,
val onUnlockVisaAccessNotificationClick: () -> Unit,
) : Visa(),
TxHistoryStateHolder by LockedTxHistoryStateHolder(onExploreClick),
WalletStateHolder by LockedWalletStateHolder(
walletCardState = walletCardState,
buttons = buttons,
bottomSheetConfig = bottomSheetConfig,
onUnlockNotificationClick = {},
) {
override val warnings: ImmutableList<WalletNotification> = persistentListOf(
WalletNotification.UnlockVisaAccess(onUnlockClick = onUnlockVisaAccessNotificationClick),
)
override val balancesAndLimitBlockState: BalancesAndLimitsBlockState? = null
}
}
}

View file

@ -19,15 +19,6 @@ internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletS
is WalletState.SingleCurrency.Locked -> prevState.copy(
bottomSheetConfig = updateConfig(prevState),
)
is WalletState.Visa.Content -> prevState.copy(
bottomSheetConfig = updateConfig(prevState),
)
is WalletState.Visa.Locked -> prevState.copy(
bottomSheetConfig = updateConfig(prevState),
)
is WalletState.Visa.AccessTokenLocked -> prevState.copy(
bottomSheetConfig = updateConfig(prevState),
)
}
}

View file

@ -1,57 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
import kotlin.reflect.KClass
/**
* Transformer for disabling action of multi-currency wallet
*
* @param userWalletId user wallet id
* @property actionClass action class that must be disabled
*/
internal class DisableActionTransformer(
userWalletId: UserWalletId,
private val actionClass: KClass<out WalletManageButton>,
) : WalletStateTransformer(userWalletId = userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.MultiCurrency.Content -> {
prevState.copy(buttons = prevState.buttons.updateButtons())
}
is WalletState.MultiCurrency.Locked -> {
Timber.w("Impossible to disable action for locked wallet")
prevState
}
is WalletState.SingleCurrency -> {
Timber.w("Impossible to disable action for single-currency wallet")
prevState
}
is WalletState.Visa -> {
Timber.w("Impossible to disable action for VISA wallet")
prevState
}
}
}
private fun PersistentList<WalletManageButton>.updateButtons(): PersistentList<WalletManageButton> {
return map { action ->
if (action::class == actionClass) {
when (action) {
is WalletManageButton.Buy -> action.copy(enabled = false)
is WalletManageButton.Swap -> action.copy(enabled = false)
is WalletManageButton.Sell -> action.copy(enabled = false)
else -> action
}
} else {
action
}
}
.toPersistentList()
}
}

View file

@ -3,13 +3,13 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isLocked
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.presentation.wallet.state.model.WalletState.MultiCurrency.WalletType
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.feature.wallet.presentation.wallet.state.model.WalletState.MultiCurrency.WalletType
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -77,15 +77,6 @@ internal class InitializeWalletsTransformer(
onExploreClick = clickIntents::onExploreClick,
)
},
visaWalletCreator = {
WalletState.Visa.Locked(
walletCardState = userWallet.toLockedWalletCardState(),
buttons = createMultiWalletEnabledButtons(userWallet),
bottomSheetConfig = null,
onUnlockNotificationClick = clickIntents::onOpenUnlockWalletsBottomSheetClick,
onExploreClick = clickIntents::onExploreClick,
)
},
)
}

View file

@ -25,15 +25,6 @@ internal class OpenBottomSheetTransformer(
is WalletState.SingleCurrency.Locked -> prevState.copy(
bottomSheetConfig = updateConfig(),
)
is WalletState.Visa.Content -> prevState.copy(
bottomSheetConfig = updateConfig(),
)
is WalletState.Visa.Locked -> prevState.copy(
bottomSheetConfig = updateConfig(),
)
is WalletState.Visa.AccessTokenLocked -> prevState.copy(
bottomSheetConfig = updateConfig(),
)
}
}

View file

@ -13,11 +13,8 @@ internal class RemoveNFTCollectionsTransformer(
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

@ -38,13 +38,8 @@ internal class RenameWalletsTransformer(
is WalletState.SingleCurrency.Content -> {
prevState.copy(walletCardState = prevState.walletCardState.copySealed(title = newName))
}
is WalletState.Visa.Content -> {
prevState.copy(walletCardState = prevState.walletCardState.copySealed(title = newName))
}
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
Timber.e("Impossible to rename wallet in locked state")
prevState

View file

@ -2,12 +2,12 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.PortfolioId
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
@ -32,10 +32,6 @@ internal class SetCryptoCurrencyActionsTransformer(
Timber.w("Impossible to load crypto currency actions for multi-currency wallet")
prevState
}
is WalletState.Visa -> {
Timber.w("Impossible to load crypto currency actions for VISA wallet")
prevState
}
}
}

View file

@ -49,10 +49,6 @@ internal class SetExpressStatusesTransformer(
Timber.w("Impossible to load express statuses for locked wallet")
prevState
}
is WalletState.Visa -> {
Timber.w("Impossible to load express statuses for visa wallet")
prevState
}
is WalletState.MultiCurrency -> {
Timber.w("Impossible to load express statuses for multi-currency wallet")
prevState

View file

@ -1,7 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.nft.models.*
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.nft.models.NFTCollection
import com.tangem.domain.nft.models.NFTCollections
import com.tangem.domain.nft.models.allLoadedCollectionsEmpty
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
@ -21,11 +23,8 @@ internal class SetNFTCollectionsTransformer(
},
)
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

@ -24,10 +24,6 @@ internal class SetPrimaryCurrencyTransformer(
marketPriceBlockState = prevState.marketPriceBlockState.toLoadedState(),
)
}
is WalletState.Visa -> {
Timber.w("Impossible to load primary currency status for VISA wallet")
prevState
}
is WalletState.SingleCurrency.Locked -> {
Timber.w("Impossible to load primary currency status for locked wallet")
prevState

View file

@ -2,8 +2,9 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -26,17 +27,8 @@ internal class SetRefreshStateTransformer(
buttons = prevState.buttons.toUpdatedState(),
)
}
is WalletState.Visa.Content -> {
prevState.copy(
buttons = prevState.buttons.toUpdatedState(),
pullToRefreshConfig = prevState.pullToRefreshConfig.toUpdatedState(isRefreshing),
balancesAndLimitBlockState = prevState.balancesAndLimitBlockState.toUpdatedState(isRefreshing),
)
}
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> prevState
}
}
@ -73,13 +65,4 @@ internal class SetRefreshStateTransformer(
}
}
}
private fun BalancesAndLimitsBlockState.toUpdatedState(isRefreshing: Boolean): BalancesAndLimitsBlockState {
return when (this) {
is BalancesAndLimitsBlockState.Content -> copy(isEnabled = !isRefreshing)
is BalancesAndLimitsBlockState.Error,
is BalancesAndLimitsBlockState.Loading,
-> this
}
}
}

View file

@ -4,8 +4,8 @@ import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.error.TokenListError
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
@ -43,10 +43,6 @@ internal class SetTokenListErrorTransformer(
Timber.w("Impossible to load tokens list for single-currency wallet")
prevState
}
is WalletState.Visa -> {
Timber.w("Impossible to load tokens list for VISA wallet")
prevState
}
}
}
is TokenListError.DataError,

View file

@ -37,7 +37,6 @@ internal class SetTokenListTransformer(
Timber.w("Impossible to load tokens list for locked wallet")
prevState
}
is WalletState.Visa,
is WalletState.SingleCurrency,
-> {
Timber.w("Impossible to load tokens list for single-currency wallet")

View file

@ -5,8 +5,8 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter
@ -38,15 +38,9 @@ internal class SetTxHistoryCountErrorTransformer(
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.SingleCurrency.Content -> prevState.copy(txHistoryState = createErrorState())
is WalletState.Visa.Content -> prevState.copy(txHistoryState = createErrorState())
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
is WalletState.MultiCurrency,
-> {
Timber.w("Impossible to load transactions history for locked wallet")
prevState
}
is WalletState.MultiCurrency -> {
Timber.w("Impossible to load transactions history for multi-currency wallet")
prevState
}

View file

@ -4,8 +4,8 @@ import androidx.paging.PagingData
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.models.wallet.UserWalletId
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.WalletState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
@ -21,12 +21,7 @@ internal class SetTxHistoryCountTransformer(
is WalletState.SingleCurrency.Content -> prevState.copy(
txHistoryState = prevState.txHistoryState.toLoadingState(),
)
is WalletState.Visa.Content -> prevState.copy(
txHistoryState = prevState.txHistoryState.toLoadingState(),
)
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
Timber.w("Impossible to load transactions history for locked wallet")
prevState

View file

@ -1,11 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import timber.log.Timber
internal class SetTxHistoryItemsErrorTransformer(
@ -17,11 +16,7 @@ internal class SetTxHistoryItemsErrorTransformer(
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.SingleCurrency.Content -> prevState.copy(txHistoryState = createErrorState())
is WalletState.Visa.Content -> transformVisaContent(prevState)
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
is WalletState.SingleCurrency.Locked -> {
Timber.w("Impossible to load transactions history for locked wallet")
prevState
}
@ -32,20 +27,6 @@ internal class SetTxHistoryItemsErrorTransformer(
}
}
private fun transformVisaContent(prevState: WalletState.Visa.Content): WalletState {
return if (error.cause is RefreshTokenExpiredException) {
WalletState.Visa.AccessTokenLocked(
walletCardState = prevState.walletCardState,
buttons = prevState.buttons,
bottomSheetConfig = prevState.bottomSheetConfig,
onExploreClick = clickIntents::onExploreClick,
onUnlockVisaAccessNotificationClick = clickIntents::onUnlockVisaAccessClick,
)
} else {
prevState.copy(txHistoryState = createErrorState())
}
}
private fun createErrorState(): TxHistoryState.Error = when (error) {
is TxHistoryListError.DataError -> {
TxHistoryState.Error(

View file

@ -4,9 +4,9 @@ import androidx.paging.PagingData
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemFlowConverter
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
@ -21,13 +21,7 @@ internal class SetTxHistoryItemsTransformer(
is WalletState.SingleCurrency.Content -> prevState.copy(
txHistoryState = prevState.txHistoryState.toContentState(),
)
is WalletState.Visa.Content -> prevState.copy(
txHistoryState = prevState.txHistoryState.toContentState(),
)
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
is WalletState.SingleCurrency.Locked -> {
Timber.w("Impossible to load transactions history for locked wallet")
prevState
}

View file

@ -1,173 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent.Companion.VISA_TYPE
import com.tangem.core.ui.extensions.stringReference
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.card.common.util.getCardsCount
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.visa.exception.RefreshTokenExpiredException
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.*
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 SetVisaInfoTransformer(
private val userWallet: UserWallet.Cold,
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
private val clickIntents: WalletClickIntents,
) : TypedWalletStateTransformer<WalletState.Visa.Content>(
userWalletId = userWallet.walletId,
targetStateClass = WalletState.Visa.Content::class,
) {
override fun transformTyped(prevState: WalletState.Visa.Content): WalletState {
val visaCurrency = maybeVisaCurrency.getOrElse {
if (it is RefreshTokenExpiredException) {
return getRefreshTokenExpiredState(prevState)
}
return prevState.copy(
buttons = createVisaButtonsDimmed(),
walletCardState = getErrorWalletCardState(prevState.walletCardState),
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
)
}
return prevState.copy(
buttons = createVisaButtons(visaCurrency = visaCurrency),
walletCardState = getContentWalletCardState(prevState.walletCardState, visaCurrency),
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
)
}
private fun getContentBlockState(visaCurrency: VisaCurrency) = BalancesAndLimitsBlockState.Content(
availableBalance = visaCurrency.limits.remainingOtp.format {
crypto(visaCurrency.symbol, visaCurrency.decimals)
},
limitDays = Days.daysBetween(DateTime.now(), visaCurrency.limits.expirationDate).days.inc(),
isEnabled = true,
onClick = clickIntents::onBalancesAndLimitsClick,
)
private fun getErrorWalletCardState(prevState: WalletCardState): WalletCardState {
return with(prevState) {
WalletCardState.Error(
id = id,
title = title,
imageResId = imageResId,
dropDownItems = dropDownItems,
)
}
}
private fun getContentWalletCardState(prevState: WalletCardState, visaCurrency: VisaCurrency): WalletCardState {
return with(prevState) {
WalletCardState.Content(
id = id,
title = title,
additionalInfo = createAdditionalInfo(visaCurrency),
imageResId = imageResId,
dropDownItems = dropDownItems,
balance = visaCurrency.balances.available.format {
crypto(visaCurrency.symbol, visaCurrency.decimals)
},
cardCount = userWallet.getCardsCount(),
isZeroBalance = visaCurrency.balances.available.isZero(),
isBalanceFlickering = false,
)
}
}
private fun createAdditionalInfo(visaCurrency: VisaCurrency): WalletAdditionalInfo {
val fiatAmount = visaCurrency.fiatRate?.let { visaCurrency.balances.available.multiply(it) }
.format {
fiat(
fiatCurrencyCode = visaCurrency.fiatCurrency.code,
fiatCurrencySymbol = visaCurrency.fiatCurrency.symbol,
)
}
val infoContent = stringReference(
value = buildString {
append(fiatAmount)
append("")
append(visaCurrency.networkName)
},
)
return WalletAdditionalInfo(
hideable = true,
content = infoContent,
)
}
private fun getRefreshTokenExpiredState(prevState: WalletState.Visa.Content): WalletState {
return WalletState.Visa.AccessTokenLocked(
walletCardState = prevState.walletCardState,
buttons = prevState.buttons,
bottomSheetConfig = prevState.bottomSheetConfig,
onExploreClick = clickIntents::onExploreClick,
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,
stakingBalance = null,
yieldSupplyStatus = null,
hasCurrentNetworkTransactions = false,
pendingTransactions = emptySet(),
networkAddress = visaCurrency.paymentAccountAddress,
sources = CryptoCurrencyStatus.Sources(),
),
)
return persistentListOf(
WalletManageButton.Receive(
enabled = true,
dimContent = false,
onClick = {
clickIntents.onReceiveClick(
userWalletId = userWalletId,
cryptoCurrencyStatus = cryptoCurrencyStatus,
event = MainScreenAnalyticsEvent.ButtonReceive(),
)
},
onLongClick = {
clickIntents.onCopyAddressLongClick(cryptoCurrencyStatus)
},
),
WalletManageButton.Buy(
enabled = true,
dimContent = false,
onClick = {
clickIntents.onMultiWalletBuyClick(userWalletId = userWallet.walletId, screenType = VISA_TYPE)
},
),
)
}
}

View file

@ -33,11 +33,6 @@ internal class SetWalletCardDropDownItemsTransformer(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.Visa.Content -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.MultiCurrency.Locked -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
@ -48,16 +43,6 @@ internal class SetWalletCardDropDownItemsTransformer(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.Visa.Locked -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
is WalletState.Visa.AccessTokenLocked -> prevState.copy(
walletCardState = prevState.walletCardState.copySealed(
dropDownItems = constructDropDownItems(prevState.walletCardState.id),
),
)
}
}

View file

@ -15,11 +15,8 @@ internal class SetWarningsTransformer(
return when (prevState) {
is WalletState.MultiCurrency.Content -> prevState.copy(warnings = warnings)
is WalletState.SingleCurrency.Content -> prevState.copy(warnings = warnings)
is WalletState.Visa.Content -> prevState.copy(warnings = warnings)
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
Timber.w("Impossible to update notifications for locked wallet")
prevState

View file

@ -2,11 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.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.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
@ -42,14 +42,11 @@ internal class UnlockWalletTransformer(
return when (prevState) {
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
-> walletLoadingStateFactory.create(
userWallet = unlockedWallet,
)
is WalletState.MultiCurrency.Content,
is WalletState.SingleCurrency.Content,
is WalletState.Visa.Content,
is WalletState.Visa.AccessTokenLocked,
-> {
Timber.e("Impossible to unlock wallet with not locked state")
prevState

View file

@ -1,64 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
internal class UpdateMultiWalletActionsTransformer(
userWalletId: UserWalletId,
private val buyStatus: Lce<Throwable, Any>,
private val sellStatus: Lce<Throwable, Any>,
private val swapStatus: Lce<Throwable, Any>,
) : WalletStateTransformer(userWalletId = userWalletId) {
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.MultiCurrency.Content -> {
prevState.copy(buttons = prevState.buttons.updateButtons())
}
is WalletState.MultiCurrency.Locked -> {
Timber.w("Impossible to load primary currency status for locked wallet")
prevState
}
is WalletState.SingleCurrency -> {
Timber.w("Impossible to load crypto currency actions for multi-currency wallet")
prevState
}
is WalletState.Visa -> {
Timber.w("Impossible to load crypto currency actions for VISA wallet")
prevState
}
}
}
private fun PersistentList<WalletManageButton>.updateButtons(): PersistentList<WalletManageButton> {
return map {
when (it) {
is WalletManageButton.Buy -> {
it.copy(
enabled = buyStatus.isContent(),
dimContent = !buyStatus.isContent(),
)
}
is WalletManageButton.Sell -> {
it.copy(
enabled = sellStatus.isContent(),
dimContent = !sellStatus.isContent(),
)
}
is WalletManageButton.Swap -> {
it.copy(
enabled = swapStatus.isContent(),
dimContent = !swapStatus.isContent(),
)
}
else -> it
}
}
.toPersistentList()
}
}

View file

@ -21,13 +21,8 @@ internal class UpdateWalletCardsCountTransformer(
is WalletState.SingleCurrency.Content -> {
prevState.copy(walletCardState = prevState.walletCardState.toUpdatedState())
}
is WalletState.Visa.Content -> {
prevState.copy(walletCardState = prevState.walletCardState.toUpdatedState())
}
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
is WalletState.Visa.Locked,
is WalletState.Visa.AccessTokenLocked,
-> {
Timber.e("Impossible to update wallet cards count for locked wallet")
prevState

View file

@ -1,55 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class BalancesAndLimitsBottomSheetConverter(
private val eventSender: WalletEventSender,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Converter<VisaCurrency, BalancesAndLimitsBottomSheetConfig> {
override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig {
fun formatAmount(amount: BigDecimal): String = amount.format { crypto(value.symbol, value.decimals) }
val otpLimit = value.limits.remainingOtp.let(::formatAmount)
val noOtpLimit = value.limits.remainingNoOtp.let(::formatAmount)
return BalancesAndLimitsBottomSheetConfig(
balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = value.balances.total.let(::formatAmount),
availableBalance = value.balances.available.let(::formatAmount),
blockedBalance = value.balances.blocked.let(::formatAmount),
debit = value.balances.debt.let(::formatAmount),
amlVerified = value.balances.verified.let(::formatAmount),
onInfoClick = this::balanceInfoOnClick,
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate),
total = otpLimit,
other = noOtpLimit,
singleTransaction = value.limits.singleTransaction.let(::formatAmount),
onInfoClick = { limitInfoOnClick(otpLimit, noOtpLimit) },
),
)
}
private fun balanceInfoOnClick() {
analyticsEventHandler.send(MainScreenAnalyticsEvent.NoticeBalancesInfo())
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
}
private fun limitInfoOnClick(totalLimit: String, otherLimit: String) {
analyticsEventHandler.send(MainScreenAnalyticsEvent.NoticeLimitsInfo())
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo(totalLimit, otherLimit)))
}
}

View file

@ -1,85 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.ui.extensions.capitalize
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.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxDetails
import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import org.joda.time.DateTimeZone
import java.math.BigDecimal
import java.util.Currency
internal class VisaTxDetailsBottomSheetConverter(
private val visaCurrency: VisaCurrency,
private val clickIntents: VisaWalletIntents,
) : Converter<VisaTxDetails, VisaTxDetailsBottomSheetConfig> {
override fun convert(value: VisaTxDetails): VisaTxDetailsBottomSheetConfig {
return VisaTxDetailsBottomSheetConfig(
transaction = createTransaction(value),
requests = value.requests.map(::createRequest).toImmutableList(),
onDisputeClick = { clickIntents.onDisputeClick(value) },
)
}
private fun createTransaction(details: VisaTxDetails): VisaTxDetailsBottomSheetConfig.Transaction {
return VisaTxDetailsBottomSheetConfig.Transaction(
id = details.id,
type = details.type.capitalize(),
status = details.status.capitalize(),
blockchainAmount = formatNetworkAmount(details.blockchainAmount),
transactionAmount = formatFiatAmount(details.transactionAmount, details.fiatCurrency),
transactionCurrencyCode = details.transactionCurrencyCode.toString(),
merchantName = details.merchantName?.capitalize() ?: UNKNOWN,
merchantCity = details.merchantCity?.capitalize() ?: UNKNOWN,
merchantCountryCode = details.merchantCountryCode ?: UNKNOWN,
merchantCategoryCode = details.merchantCategoryCode ?: UNKNOWN,
)
}
private fun createRequest(request: VisaTxDetails.Request): VisaTxDetailsBottomSheetConfig.Request {
val localDate = request.requestDate.withZone(DateTimeZone.getDefault())
val exploreUrl = request.exploreUrl
return VisaTxDetailsBottomSheetConfig.Request(
id = request.id,
type = request.requestType.capitalize(),
status = request.requestStatus.capitalize(),
blockchainAmount = formatNetworkAmount(request.blockchainAmount),
transactionAmount = formatFiatAmount(request.transactionAmount, request.fiatCurrency),
currencyCode = request.billingCurrencyCode.toString(),
errorCode = request.errorCode,
date = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.dateTimeFormatter),
txHash = request.txHash ?: UNKNOWN,
txStatus = request.txStatus?.capitalize() ?: UNKNOWN,
onExploreClick = if (exploreUrl != null) {
{ clickIntents.onExploreClick(exploreUrl) }
} else {
null
},
)
}
private fun formatNetworkAmount(amount: BigDecimal): String {
return amount.format { crypto(visaCurrency.symbol, visaCurrency.decimals) }
}
private fun formatFiatAmount(amount: BigDecimal, fiatCurrency: Currency): String {
return amount.format {
fiat(
fiatCurrencyCode = fiatCurrency.currencyCode,
fiatCurrencySymbol = fiatCurrency.symbol,
)
}
}
private companion object {
const val UNKNOWN = "Unknown"
}
}

View file

@ -1,47 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.extensions.capitalize
import com.tangem.core.ui.extensions.stringReference
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.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.feature.wallet.child.wallet.model.intents.VisaWalletIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.utils.StringsSigns
import com.tangem.utils.converter.Converter
import org.joda.time.DateTimeZone
internal class VisaTxHistoryItemStateConverter(
private val visaCurrency: VisaCurrency,
private val clickIntents: VisaWalletIntents,
) : Converter<VisaTxHistoryItem, TransactionState> {
override fun convert(value: VisaTxHistoryItem): TransactionState {
val localDate = value.date.withZone(DateTimeZone.getDefault())
val time = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.timeFormatter)
val subtitle = "$time ${StringsSigns.DOT} ${value.status.capitalize()}"
return TransactionState.Content(
txHash = value.id,
amount = value.amount.format { crypto(visaCurrency.symbol, visaCurrency.decimals) },
// Show tx fiat amount instead of tx time
time = value.fiatAmount.format {
fiat(
fiatCurrencyCode = value.fiatCurrency.currencyCode,
fiatCurrencySymbol = value.fiatCurrency.symbol,
)
},
status = TransactionState.Content.Status.Confirmed,
direction = TransactionState.Content.Direction.INCOMING,
iconRes = R.drawable.ic_arrow_up_24,
title = stringReference(value = value.merchantName?.capitalize() ?: "Unknown merchant"),
subtitle = stringReference(subtitle),
timestamp = localDate.millis,
onClick = { clickIntents.onVisaTransactionClick(value.id) },
)
}
}

View file

@ -7,10 +7,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
internal inline fun UserWallet.createStateByWalletType(
multiCurrencyCreator: () -> WalletState.MultiCurrency,
singleCurrencyCreator: () -> WalletState.SingleCurrency,
visaWalletCreator: () -> WalletState.Visa,
): WalletState = when (this) {
is UserWallet.Cold -> when {
isVisaWallet() -> visaWalletCreator()
isWalletWithTokens() -> multiCurrencyCreator()
else -> singleCurrencyCreator()
}
@ -19,8 +17,4 @@ internal inline fun UserWallet.createStateByWalletType(
private fun UserWallet.Cold.isWalletWithTokens(): Boolean {
return isMultiCurrency || scanResponse.cardTypesResolver.isSingleWalletWithToken()
}
private fun UserWallet.Cold.isVisaWallet(): Boolean {
return scanResponse.cardTypesResolver.isVisaWallet()
}

View file

@ -30,7 +30,6 @@ internal class WalletLoadingStateFactory(
userWallet.createStateByWalletType(
multiCurrencyCreator = { createLoadingMultiCurrencyContent(userWallet) },
singleCurrencyCreator = { createLoadingSingleCurrencyContent(userWallet) },
visaWalletCreator = { createLoadingVisaWalletContent(userWallet) },
)
}
is UserWallet.Hot -> {
@ -92,22 +91,6 @@ internal class WalletLoadingStateFactory(
)
}
private fun createLoadingVisaWalletContent(userWallet: UserWallet.Cold): WalletState.Visa.Content {
return WalletState.Visa.Content(
pullToRefreshConfig = createPullToRefreshConfig(),
walletCardState = userWallet.toLoadingWalletCardState(),
buttons = createVisaDimmedButtons(),
warnings = persistentListOf(),
bottomSheetConfig = null,
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Loading,
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
),
),
)
}
private fun createPullToRefreshConfig(): PullToRefreshConfig {
return PullToRefreshConfig(
onRefresh = { clickIntents.onRefreshSwipe(it.value) },
@ -154,18 +137,6 @@ 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(

View file

@ -1,106 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.GetVisaTxHistoryUseCase
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.domain.models.wallet.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.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
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.VisaTxHistoryItemStateConverter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
internal class VisaWalletSubscriber(
private val userWallet: UserWallet.Cold,
private val stateController: WalletStateController,
private val isRefresh: Boolean,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val getVisaTxHistoryUseCase: GetVisaTxHistoryUseCase,
private val clickIntents: WalletClickIntents,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return flow<Any> {
setLoadingTxHistoryState()
val maybeCurrency = getVisaCurrencyUseCase(userWallet.walletId, isRefresh)
setLoadedCurrencyState(maybeCurrency)
val currency = maybeCurrency.getOrElse {
Timber.e(it, "Failed to load VISA currency")
setFailedTxHistoryState(it)
return@flow
}
val txHistoryItemsFlow = getVisaTxHistoryUseCase(userWallet.walletId, isRefresh = isRefresh)
.map { maybeTxHistoryItems ->
maybeTxHistoryItems.getOrElse {
Timber.e(it, "Failed to load tx history for wallet ${userWallet.walletId}")
throw it
}
}
.catch { setFailedTxHistoryState(it) }
.cachedIn(coroutineScope)
setLoadedTxHistoryState(txHistoryItemsFlow, currency)
}
}
private fun setLoadedCurrencyState(maybeCurrency: Either<Throwable, VisaCurrency>) {
stateController.update(
SetVisaInfoTransformer(
userWallet = userWallet,
maybeVisaCurrency = maybeCurrency,
clickIntents = clickIntents,
),
)
}
private fun setLoadingTxHistoryState() {
stateController.update(
SetTxHistoryCountTransformer(
userWalletId = userWallet.walletId,
transactionsCount = 10,
clickIntents = clickIntents,
),
)
}
private fun setFailedTxHistoryState(it: Throwable) {
stateController.update(
SetTxHistoryItemsErrorTransformer(
userWalletId = userWallet.walletId,
error = TxHistoryListError.DataError(it),
clickIntents = clickIntents,
),
)
}
private fun setLoadedTxHistoryState(itemsFlow: Flow<PagingData<VisaTxHistoryItem>>, currency: VisaCurrency) {
val itemConverter = VisaTxHistoryItemStateConverter(currency, clickIntents)
stateController.update(
SetTxHistoryItemsTransformer(
userWallet = userWallet,
flow = itemsFlow.map { items ->
items.map(itemConverter::convert)
},
clickIntents = clickIntents,
),
)
}
}

View file

@ -80,10 +80,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
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.TangemPayMainScreenBlock
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.utils.changeWalletAnimator
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
@ -203,9 +200,7 @@ private fun WalletContent(
}
when (selectedWallet) {
is WalletState.MultiCurrency,
is WalletState.Visa,
-> {
is WalletState.MultiCurrency -> {
actions(
actions = selectedWallet.buttons,
selectedWalletIndex = selectedWalletIndex,
@ -248,13 +243,6 @@ private fun WalletContent(
}
}
(selectedWallet as? WalletState.Visa.Content)?.let {
balancesAndLimitsBlock(
modifier = itemModifier,
state = it.balancesAndLimitBlockState,
)
}
contentItems(
state = selectedWallet,
txHistoryItems = txHistoryItems,
@ -721,8 +709,6 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
is TokenReceiveBottomSheetConfig -> TokenReceiveBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig)
is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig)
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)
}
}

View file

@ -30,8 +30,5 @@ internal fun LazyListScope.contentItems(
is WalletState.SingleCurrency -> {
txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)
}
is WalletState.Visa -> {
txHistoryItems(state.txHistoryState, txHistoryItems, isBalanceHidden, modifier)
}
}
}

View file

@ -1,185 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBlockState
private const val BALANCES_AND_LIMITS_BLOCK_KEY = "BalancesAndLimitsBlock"
internal fun LazyListScope.balancesAndLimitsBlock(state: BalancesAndLimitsBlockState, modifier: Modifier = Modifier) {
item(key = BALANCES_AND_LIMITS_BLOCK_KEY, contentType = BALANCES_AND_LIMITS_BLOCK_KEY) {
BalancesAndLimitsBlock(state, modifier)
}
}
@Composable
private fun BalancesAndLimitsBlock(state: BalancesAndLimitsBlockState, modifier: Modifier = Modifier) {
val onClick: () -> Unit = remember(state) {
{ (state as? BalancesAndLimitsBlockState.Content)?.onClick?.invoke() }
}
val isEnabled: Boolean = remember(state) {
state is BalancesAndLimitsBlockState.Content && state.isEnabled
}
ContentContainer(
modifier = modifier.fillMaxWidth(),
enabled = isEnabled,
onClick = onClick,
title = {
Text(
text = "Balances & Limits",
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
content = {
Content(state = state)
},
endIcon = {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_chevron_right_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
},
)
}
@Composable
private inline fun ContentContainer(
enabled: Boolean,
modifier: Modifier = Modifier,
noinline onClick: () -> Unit,
crossinline title: @Composable () -> Unit,
crossinline content: @Composable () -> Unit,
crossinline endIcon: @Composable () -> Unit,
) {
Card(
modifier = modifier.fillMaxWidth(),
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
colors = CardDefaults.cardColors(
containerColor = TangemTheme.colors.background.primary,
contentColor = TangemTheme.colors.text.primary1,
disabledContainerColor = TangemTheme.colors.background.primary,
disabledContentColor = TangemTheme.colors.text.primary1,
),
onClick = onClick,
enabled = enabled,
) {
Row(
modifier = Modifier
.padding(all = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size48),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Column(
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
horizontalAlignment = Alignment.Start,
) {
title()
content()
}
endIcon()
}
}
}
@Composable
private fun Content(state: BalancesAndLimitsBlockState, modifier: Modifier = Modifier) {
AnimatedContent(
modifier = modifier,
targetState = state,
label = "Update the balances and limits block",
) { blockState ->
when (blockState) {
is BalancesAndLimitsBlockState.Content -> with(blockState) {
AvailableLimit(
availableBalance = availableBalance,
limitDays = limitDays,
)
}
is BalancesAndLimitsBlockState.Error -> {
Text(
text = "",
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
}
is BalancesAndLimitsBlockState.Loading -> {
RectangleShimmer(
modifier = Modifier
.width(TangemTheme.dimens.size200)
.height(TangemTheme.dimens.size20),
)
}
}
}
}
@Composable
private fun AvailableLimit(availableBalance: String, limitDays: Int, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
Text(
text = availableBalance,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = "available for $limitDays day(s)",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BalancesAndLimitsBlockPreview(
@PreviewParameter(BalancesAndLimitsBlockParameterProvider::class) state: BalancesAndLimitsBlockState,
) {
TangemThemePreview {
BalancesAndLimitsBlock(state)
}
}
private class BalancesAndLimitsBlockParameterProvider : CollectionPreviewParameterProvider<BalancesAndLimitsBlockState>(
collection = listOf(
BalancesAndLimitsBlockState.Loading,
BalancesAndLimitsBlockState.Error,
BalancesAndLimitsBlockState.Content(
availableBalance = "400.00 USDT",
limitDays = 7,
isEnabled = true,
onClick = {},
),
),
)
// endregion Preview

View file

@ -1,193 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.BalancesAndLimitsBottomSheetConfig
@Composable
internal fun BalancesAndLimitsBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.secondary,
) { content: BalancesAndLimitsBottomSheetConfig ->
BalancesAndLimitsContent(content)
}
}
@Composable
private fun BalancesAndLimitsContent(config: BalancesAndLimitsBottomSheetConfig, modifier: Modifier = Modifier) {
ContentContainer(
modifier = modifier,
title = {
Text(
text = stringResourceSafe(R.string.visa_main_balances_and_limits),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
},
firstBlock = {
BalancesBlock(balances = config.balance)
},
secondBlock = {
LimitsBlock(limits = config.limit)
},
)
}
@Composable
private fun BalancesBlock(balances: BalancesAndLimitsBottomSheetConfig.Balance, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = resourceReference(R.string.common_balance),
content = {
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_total),
value = balances.totalBalance,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_aml_verified),
value = balances.amlVerified,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_available),
value = balances.availableBalance,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_blocked),
value = balances.blockedBalance,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_debt),
value = balances.debit,
)
},
description = {
InfoButton(onClick = balances.onInfoClick)
},
)
}
@Composable
private fun LimitsBlock(limits: BalancesAndLimitsBottomSheetConfig.Limit, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = resourceReference(R.string.visa_balance_limits_details_limits),
content = {
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_total),
value = limits.total,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_no_otp_limit),
value = limits.other,
)
BlockItem(
title = resourceReference(R.string.visa_balance_limits_details_single_transaction),
value = limits.singleTransaction,
)
},
description = {
Text(
text = stringResourceSafe(R.string.visa_main_available_till_date, limits.availableBy),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
InfoButton(onClick = limits.onInfoClick)
},
)
}
@Composable
private fun InfoButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
IconButton(
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = onClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
@Composable
private inline fun ContentContainer(
modifier: Modifier = Modifier,
title: @Composable BoxScope.() -> Unit,
firstBlock: @Composable ColumnScope.() -> Unit,
secondBlock: @Composable ColumnScope.() -> Unit,
) {
Column(
modifier = modifier.background(TangemTheme.colors.background.secondary),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size44),
contentAlignment = Alignment.Center,
content = title,
)
firstBlock()
secondBlock()
SpacerH16()
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun BalancesAndLimitsBottomSheetPreview(
@PreviewParameter(BalancesAndLimitsBottomSheetParameterProvider::class) state: BalancesAndLimitsBottomSheetConfig,
) {
TangemThemePreview {
BalancesAndLimitsContent(state)
}
}
private class BalancesAndLimitsBottomSheetParameterProvider :
CollectionPreviewParameterProvider<BalancesAndLimitsBottomSheetConfig>(
collection = listOf(
BalancesAndLimitsBottomSheetConfig(
balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = "492.45 USDT",
availableBalance = "392.45 USDT",
blockedBalance = "36.00 USDT",
debit = "00.00 USDT",
amlVerified = "356.45 USDT",
onInfoClick = {},
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = "Nov, 11 USDT",
total = "563.00 USDT",
other = "100.00 USDT",
singleTransaction = "100.00 USDT",
onInfoClick = {},
),
),
),
)
// endregion Preview

View file

@ -1,83 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import com.tangem.core.ui.components.SpacerH8
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
private const val BLOCK_ITEM_NAME_WEIGHT = .45f
private const val BLOCK_ITEM_VALUE_WEIGHT = .55f
@Composable
internal inline fun BlockContent(
title: TextReference,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
description: @Composable RowScope.() -> Unit = {},
) {
Column(
modifier = modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxWidth()
.background(
color = TangemTheme.colors.background.primary,
shape = TangemTheme.shapes.roundedCornersXMedium,
),
) {
Row(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size42),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
SpacerWMax()
description()
}
content()
SpacerH8()
}
}
@Composable
internal fun BlockItem(title: TextReference, value: String, modifier: Modifier = Modifier) {
Row(
modifier = modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size32)
.padding(
vertical = TangemTheme.dimens.spacing8,
horizontal = TangemTheme.dimens.spacing12,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Text(
modifier = Modifier.weight(BLOCK_ITEM_NAME_WEIGHT),
text = title.resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Start,
)
Text(
modifier = Modifier.weight(BLOCK_ITEM_VALUE_WEIGHT),
text = value,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.End,
)
}
}

View file

@ -1,255 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import android.content.res.Configuration
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerW12
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.VisaTxDetailsBottomSheetConfig
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun VisaTxDetailsBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(
config = config,
containerColor = TangemTheme.colors.background.secondary,
) { content: VisaTxDetailsBottomSheetConfig ->
VisaTxDetailsBottomSheetContent(content)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun VisaTxDetailsBottomSheetContent(config: VisaTxDetailsBottomSheetConfig, modifier: Modifier = Modifier) {
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = TangemTheme.dimens.size44)
.background(TangemTheme.colors.background.secondary),
contentAlignment = Alignment.Center,
) {
Text(
text = stringResourceSafe(R.string.visa_transaction_details_header),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
}
LazyColumn(
modifier = modifier.background(TangemTheme.colors.background.secondary),
contentPadding = PaddingValues(
bottom = TangemTheme.dimens.spacing16,
),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing12),
horizontalAlignment = Alignment.CenterHorizontally,
) {
item {
TransactionBlock(config.transaction)
}
items(config.requests) { item ->
BlockchainRequestBlock(item)
}
item {
DisputeButton(config.onDisputeClick)
}
}
}
}
@Composable
private fun TransactionBlock(transaction: VisaTxDetailsBottomSheetConfig.Transaction, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = resourceReference(R.string.visa_transaction_details_title),
content = {
BlockItem(
title = resourceReference(R.string.visa_transaction_details_type),
value = transaction.type,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_status),
value = transaction.status,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_blockchain_amount),
value = transaction.blockchainAmount,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_transaction_amount),
value = transaction.transactionAmount,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_currency_code),
value = transaction.transactionCurrencyCode,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_merchant_name),
value = transaction.merchantName,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_merchant_city),
value = transaction.merchantCity,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_merchant_country_code),
value = transaction.merchantCountryCode,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_merchant_category_code),
value = transaction.merchantCategoryCode,
)
},
)
}
@Suppress("LongMethod")
@Composable
private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Request, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = resourceReference(R.string.visa_transaction_details_transaction_request),
description = {
if (request.onExploreClick != null) {
Row(
modifier = Modifier.clickable(onClick = request.onExploreClick),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Icon(
painter = painterResource(id = R.drawable.ic_compass_24),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size18),
tint = TangemTheme.colors.icon.informative,
)
Text(
text = stringResourceSafe(R.string.common_explore),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
)
}
SpacerW12()
}
},
content = {
BlockItem(
title = resourceReference(R.string.visa_transaction_details_type),
value = request.type,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_status),
value = request.status,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_blockchain_amount),
value = request.blockchainAmount,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_transaction_amount),
value = request.transactionAmount,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_currency_code),
value = request.currencyCode,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_error_code),
value = request.errorCode.toString(),
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_date),
value = request.date,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_transaction_hash),
value = request.txHash,
)
BlockItem(
title = resourceReference(R.string.visa_transaction_details_transaction_status),
value = request.txStatus,
)
},
)
}
@Composable
private fun DisputeButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
SecondaryButtonIconStart(
modifier = modifier.fillMaxWidth(),
text = stringResourceSafe(R.string.visa_tx_dispute_button),
iconResId = R.drawable.ic_alert_triangle_20,
onClick = onClick,
)
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun VisaTxDetailsBottomSheetPreview(
@PreviewParameter(VisaTxDetailsBottomSheetParameterProvider::class) state: VisaTxDetailsBottomSheetConfig,
) {
TangemThemePreview {
VisaTxDetailsBottomSheetContent(state)
}
}
private class VisaTxDetailsBottomSheetParameterProvider :
CollectionPreviewParameterProvider<VisaTxDetailsBottomSheetConfig>(
collection = listOf(
VisaTxDetailsBottomSheetConfig(
transaction = VisaTxDetailsBottomSheetConfig.Transaction(
id = "518385816101345408",
type = "payment",
status = "authorized",
blockchainAmount = "1.0614 USDT",
transactionAmount = "0.99 €",
transactionCurrencyCode = "978",
merchantName = "SQ *FORMATIVE",
merchantCity = "London",
merchantCountryCode = "GB",
merchantCategoryCode = "5814",
),
requests = persistentListOf(
VisaTxDetailsBottomSheetConfig.Request(
id = "524582128501966718",
type = "authorize_payment",
status = "accepted",
blockchainAmount = "1.0593 USDT",
transactionAmount = "0.99 €",
currencyCode = "978",
errorCode = 0,
date = "2023-12-01 14:20:09.230 +0300",
txHash = "0xc458f0204fe43b82c775004baabb38435b5595f4307d8c3ac74625c827be7c29",
txStatus = "confirmed",
onExploreClick = {},
),
),
onDisputeClick = {},
),
),
)
// endregion Preview