Updated on 2026-08-14

This commit is contained in:
Tangem 2024-02-02 15:38:53 +04:00
parent 2fd16863a7
commit 9b6de19ebe
25 changed files with 376 additions and 201 deletions

View file

@ -52,6 +52,7 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.transaction)
implementation(projects.domain.analytics)
implementation(projects.domain.visa)
implementation(projects.common)
implementation(projects.core.analytics)
@ -78,6 +79,7 @@ dependencies {
implementation(projects.data.wallets)
implementation(projects.data.analytics)
implementation(projects.data.transaction)
implementation(projects.data.visa)
/** Features */
implementation(projects.features.onboarding)

View file

@ -0,0 +1,18 @@
package com.tangem.tap.di.domain
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.visa.repository.VisaRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
@Module
@InstallIn(ViewModelComponent::class)
internal object VisaDomainModule {
@Provides
fun provideVisaCurrencyUseCase(visaRepository: VisaRepository): GetVisaCurrencyUseCase {
return GetVisaCurrencyUseCase(visaRepository)
}
}

1
data/visa/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,21 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.kapt)
id("configuration")
}
android {
namespace = "com.tangem.data.visa"
}
dependencies {
/** Project - Domain */
implementation(projects.domain.visa)
implementation(projects.domain.wallets.models)
/** DI */
implementation(deps.hilt.core)
kapt(deps.hilt.kapt)
}

View file

@ -0,0 +1,12 @@
package com.tangem.data.visa
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.domain.wallets.models.UserWalletId
internal class DummyVisaRepository : VisaRepository {
override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency {
TODO(reason = "Implement in [REDACTED_JIRA]")
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.data.visa.di
import com.tangem.data.visa.DummyVisaRepository
import com.tangem.domain.visa.repository.VisaRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object VisaDataModule {
@Provides
@Singleton
fun provideVisaRepository(): VisaRepository {
return DummyVisaRepository()
}
}

View file

@ -72,7 +72,10 @@ internal class TangemCardTypesResolver(
override fun isSingleWalletWithToken(): Boolean = walletData?.token != null && !isMultiwalletAllowed()
override fun isMultiwalletAllowed(): Boolean {
return !isTangemTwins() && !card.isStart2Coin && !isTangemNote() &&
return !isTangemTwins() &&
!card.isStart2Coin &&
!isTangemNote() &&
!isVisaWallet() &&
(multiWalletAvailable() || card.wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1)
}
@ -81,6 +84,7 @@ internal class TangemCardTypesResolver(
override fun getBlockchain(): Blockchain {
return when (productType) {
ProductType.Start2Coin -> if (card.isTestCard) Blockchain.BitcoinTestnet else Blockchain.Bitcoin
ProductType.Visa -> Blockchain.PolygonTestnet
else -> {
val blockchainName: String = walletData?.blockchain
?: if (productType == ProductType.Note) {

1
domain/visa/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,22 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.domain.visa"
}
dependencies {
/** Project - Domain */
implementation(projects.core.utils)
implementation(projects.domain.core)
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.appCurrency.models)
/** Libs - Other */
implementation(deps.jodatime)
}

View file

@ -0,0 +1,18 @@
package com.tangem.domain.visa
import arrow.core.Either
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.domain.wallets.models.UserWalletId
class GetVisaCurrencyUseCase(
private val repository: VisaRepository,
) {
suspend operator fun invoke(
userWalletId: UserWalletId,
isRefresh: Boolean = false,
): Either<Throwable, VisaCurrency> {
return Either.catch { repository.getVisaCurrency(userWalletId, isRefresh) }
}
}

View file

@ -0,0 +1,32 @@
package com.tangem.domain.visa.model
import com.tangem.domain.appcurrency.model.AppCurrency
import org.joda.time.DateTime
import java.math.BigDecimal
data class VisaCurrency(
val networkName: String,
val symbol: String,
val decimals: Int,
val fiatRate: BigDecimal?,
val fiatCurrency: AppCurrency,
val balances: Balances,
val limits: Limits,
) {
data class Balances(
val total: BigDecimal,
val verified: BigDecimal,
val available: BigDecimal,
val blocked: BigDecimal,
val debt: BigDecimal,
val pendingRefund: BigDecimal,
)
data class Limits(
val remainingOtp: BigDecimal,
val remainingNoOtp: BigDecimal,
val singleTransaction: BigDecimal,
val expirationDate: DateTime,
)
}

View file

@ -0,0 +1,9 @@
package com.tangem.domain.visa.repository
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.wallets.models.UserWalletId
interface VisaRepository {
suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean = false): VisaCurrency
}

View file

@ -69,6 +69,7 @@ dependencies {
implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.analytics)
implementation(projects.domain.visa)
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
implementation(projects.features.onboarding)

View file

@ -1,14 +1,11 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.PrimaryCurrencySubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.TxHistorySubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.VisaWalletBalancesAndLimitsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
@ -21,24 +18,20 @@ internal class VisaWalletContentLoader(
private val isRefresh: Boolean,
private val stateHolder: WalletStateController,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return listOf(
PrimaryCurrencySubscriber(
VisaWalletBalancesAndLimitsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
analyticsEventHandler = analyticsEventHandler,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
isRefresh = isRefresh,
getVisaCurrencyUseCase = getVisaCurrencyUseCase,
clickIntents = clickIntents,
),
VisaWalletBalancesAndLimitsSubscriber(userWallet, stateHolder, clickIntents),
TxHistorySubscriber(
userWallet = userWallet,
isRefresh = isRefresh,

View file

@ -1,11 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
@ -17,11 +15,9 @@ import javax.inject.Inject
internal class VisaWalletContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntentsV2, isRefresh: Boolean): WalletContentLoader {
@ -31,11 +27,9 @@ internal class VisaWalletContentLoaderFactory @Inject constructor(
isRefresh = isRefresh,
stateHolder = stateHolder,
getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase,
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
txHistoryItemsUseCase = txHistoryItemsUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
analyticsEventHandler = analyticsEventHandler,
getVisaCurrencyUseCase = getVisaCurrencyUseCase,
)
}
}

View file

@ -11,7 +11,6 @@ internal sealed class BalancesAndLimitsBlockState {
data class Content(
val availableBalance: String,
val currencySymbol: String,
val limitDays: Int,
val isEnabled: Boolean,
val onClick: () -> Unit,

View file

@ -1,32 +1,98 @@
package com.tangem.feature.wallet.presentation.wallet.state2.transformers
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBlockState
import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import org.joda.time.DateTime
import org.joda.time.Days
internal class SetBalancesAndLimitsTransformer(
userWallet: UserWallet,
private val userWallet: UserWallet,
private val maybeVisaCurrency: Either<Throwable, VisaCurrency>,
private val clickIntents: WalletClickIntentsV2,
) : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
return prevState.transformWhenInState<WalletState.Visa.Content> { state ->
val visaCurrency = maybeVisaCurrency.getOrElse {
return state.copy(
walletCardState = getErrorWalletCardState(state.walletCardState),
balancesAndLimitBlockState = BalancesAndLimitsBlockState.Error,
)
}
state.copy(
balancesAndLimitBlockState = state.balancesAndLimitBlockState.toLoadedState(),
walletCardState = getContentWalletCardState(state.walletCardState, visaCurrency),
balancesAndLimitBlockState = getContentBlockState(visaCurrency),
)
}
}
// TODO: Implement in [REDACTED_JIRA]
@Suppress("UnusedReceiverParameter")
private fun BalancesAndLimitsBlockState.toLoadedState(): BalancesAndLimitsBlockState {
return BalancesAndLimitsBlockState.Content(
availableBalance = "400.00",
currencySymbol = "USDT",
limitDays = 7,
isEnabled = true,
onClick = clickIntents::onBalancesAndLimitsClick,
private fun getContentBlockState(visaCurrency: VisaCurrency) = BalancesAndLimitsBlockState.Content(
availableBalance = BigDecimalFormatter.formatCryptoAmount(
visaCurrency.limits.remainingOtp,
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,
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
)
}
}
private fun getContentWalletCardState(prevState: WalletCardState, visaCurrency: VisaCurrency): WalletCardState {
return with(prevState) {
WalletCardState.Content(
id = id,
title = title,
additionalInfo = createAdditionalInfo(visaCurrency),
imageResId = imageResId,
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
balance = BigDecimalFormatter.formatCryptoAmount(
visaCurrency.balances.available,
visaCurrency.symbol,
visaCurrency.decimals,
),
cardCount = userWallet.getCardsCount(),
)
}
}
private fun createAdditionalInfo(visaCurrency: VisaCurrency): WalletAdditionalInfo {
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
fiatAmount = visaCurrency.fiatRate?.let { visaCurrency.balances.available.multiply(it) },
fiatCurrencyCode = visaCurrency.fiatCurrency.code,
fiatCurrencySymbol = visaCurrency.fiatCurrency.symbol,
)
val infoContent = stringReference(
value = buildString {
append(fiatAmount)
append("")
append(visaCurrency.networkName)
},
)
return WalletAdditionalInfo(hideable = true, infoContent)
}
}

View file

@ -8,7 +8,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCard
import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletCardStateConverter
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.SingleWalletMarketPriceConverter
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.VisaWalletCardStateConverter
import timber.log.Timber
internal class SetPrimaryCurrencyTransformer(
@ -25,15 +24,11 @@ internal class SetPrimaryCurrencyTransformer(
marketPriceBlockState = prevState.marketPriceBlockState.toLoadedState(),
)
}
is WalletState.Visa.Content -> {
prevState.copy(
walletCardState = prevState.walletCardState.toLoadedVisaState(),
depositButtonState = prevState.depositButtonState.copy(isEnabled = true),
)
is WalletState.Visa -> {
Timber.w("Impossible to load primary currency status for VISA wallet")
prevState
}
is WalletState.Visa.Locked,
is WalletState.SingleCurrency.Locked,
-> {
is WalletState.SingleCurrency.Locked -> {
Timber.w("Impossible to load primary currency status for locked wallet")
prevState
}
@ -48,10 +43,6 @@ internal class SetPrimaryCurrencyTransformer(
return SingleWalletCardStateConverter(status.value, userWallet, appCurrency).convert(value = this)
}
private fun WalletCardState.toLoadedVisaState(): WalletCardState {
return VisaWalletCardStateConverter(status, userWallet, appCurrency).convert(value = this)
}
private fun MarketPriceBlockState.toLoadedState(): MarketPriceBlockState {
return SingleWalletMarketPriceConverter(status.value, appCurrency).convert(value = this)
}

View file

@ -0,0 +1,52 @@
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class BalancesAndLimitsBottomSheetConverter(
private val eventSender: WalletEventSender,
) : Converter<VisaCurrency, BalancesAndLimitsBottomSheetConfig> {
override fun convert(value: VisaCurrency): BalancesAndLimitsBottomSheetConfig {
return BalancesAndLimitsBottomSheetConfig(
currency = value.symbol,
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),
pending = value.balances.pendingRefund.let(::formatAmount),
amlVerified = value.balances.verified.let(::formatAmount),
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate),
inStore = value.limits.remainingOtp.let(::formatAmount),
other = value.limits.remainingNoOtp.let(::formatAmount),
singleTransaction = value.limits.singleTransaction.let(::formatAmount),
),
onBalanceInfoClick = this::showBalanceInfo,
onLimitInfoClick = this::showLimitInfo,
)
}
private fun formatAmount(amount: BigDecimal): String = BigDecimalFormatter.formatCryptoAmount(
amount,
cryptoCurrency = "",
decimals = 2,
)
private fun showBalanceInfo() {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
}
private fun showLimitInfo() {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo))
}
}

View file

@ -1,89 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.utils.converter.Converter
internal class VisaWalletCardStateConverter(
private val status: CryptoCurrencyStatus,
private val selectedWallet: UserWallet,
private val appCurrency: AppCurrency,
) : Converter<WalletCardState, WalletCardState> {
override fun convert(value: WalletCardState): WalletCardState {
return when (status.value) {
is CryptoCurrencyStatus.Loading -> value.toLoadingState()
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Unreachable,
-> value.toErrorState()
is CryptoCurrencyStatus.NoQuote,
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.NoAmount,
-> value.toContentState(status)
}
}
private fun WalletCardState.toLoadingState(): WalletCardState {
return WalletCardState.Loading(
id = id,
title = title,
imageResId = imageResId,
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
)
}
private fun WalletCardState.toErrorState(): WalletCardState {
return WalletCardState.Error(
id = id,
title = title,
imageResId = imageResId,
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
)
}
private fun WalletCardState.toContentState(status: CryptoCurrencyStatus): WalletCardState {
return WalletCardState.Content(
id = id,
title = title,
additionalInfo = createAdditionalInfo(status),
imageResId = imageResId,
onRenameClick = onRenameClick,
onDeleteClick = onDeleteClick,
balance = formatAmount(status),
cardCount = selectedWallet.getCardsCount(),
)
}
private fun createAdditionalInfo(status: CryptoCurrencyStatus): WalletAdditionalInfo {
val fiatAmount = BigDecimalFormatter.formatFiatAmount(
status.value.fiatAmount,
fiatCurrencyCode = appCurrency.code,
fiatCurrencySymbol = appCurrency.symbol,
)
val infoContent = stringReference(
value = buildString {
append(fiatAmount)
append("")
append(status.currency.network.name)
},
)
return WalletAdditionalInfo(hideable = true, infoContent)
}
private fun formatAmount(status: CryptoCurrencyStatus): String {
val amount = status.value.amount ?: return BigDecimalFormatter.EMPTY_BALANCE_SIGN
return BigDecimalFormatter.formatCryptoAmount(amount, status.currency)
}
}

View file

@ -3,6 +3,8 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers
import androidx.paging.PagingData
import androidx.paging.cachedIn
import arrow.core.Either
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.txhistory.models.TxHistoryItem
@ -13,10 +15,8 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.collectLatest
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryCountErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryCountTransformer
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryItemsErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetTxHistoryItemsTransformer
import com.tangem.feature.wallet.presentation.wallet.state2.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.*
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@ -37,6 +37,24 @@ internal class TxHistorySubscriber(
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<PagingData<TxHistoryItem>> {
// TODO: [REDACTED_JIRA]
if (userWallet.scanResponse.cardTypesResolver.isVisaWallet()) {
return flow {
stateHolder.update(
object : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.Visa.Content -> prevState.copy(
txHistoryState = TxHistoryState.Empty(onExploreClick = {}),
)
else -> prevState
}
}
},
)
}
}
return flow {
getPrimaryCurrencyStatusUpdatesUseCase.collectLatest(userWalletId = userWallet.walletId) { status ->
val maybeTxHistoryItemCount = txHistoryItemsCountUseCase(

View file

@ -1,23 +1,32 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.SetBalancesAndLimitsTransformer
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntentsV2
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flow
@Suppress("LongParameterList")
internal class VisaWalletBalancesAndLimitsSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val isRefresh: Boolean,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val clickIntents: WalletClickIntentsV2,
) : WalletSubscriber() {
// TODO: Implement in [REDACTED_JIRA]
override fun create(coroutineScope: CoroutineScope): Flow<*> = suspend {
delay(timeMillis = 500)
stateHolder.update(SetBalancesAndLimitsTransformer(userWallet, clickIntents))
}.asFlow()
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return flow<Any> {
stateHolder.update(
SetBalancesAndLimitsTransformer(
userWallet = userWallet,
maybeVisaCurrency = getVisaCurrencyUseCase(userWallet.walletId, isRefresh),
clickIntents = clickIntents,
),
)
}
}
}

View file

@ -113,7 +113,6 @@ private fun Content(state: BalancesAndLimitsBlockState, modifier: Modifier = Mod
is BalancesAndLimitsBlockState.Content -> with(blockState) {
AvailableLimit(
availableBalance = availableBalance,
currencySymbol = currencySymbol,
limitDays = limitDays,
)
}
@ -136,19 +135,14 @@ private fun Content(state: BalancesAndLimitsBlockState, modifier: Modifier = Mod
}
@Composable
private fun AvailableLimit(
availableBalance: String,
currencySymbol: String,
limitDays: Int,
modifier: Modifier = Modifier,
) {
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 $currencySymbol",
text = availableBalance,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
@ -186,8 +180,7 @@ private class BalancesAndLimitsBlockParameterProvider : CollectionPreviewParamet
BalancesAndLimitsBlockState.Loading,
BalancesAndLimitsBlockState.Error,
BalancesAndLimitsBlockState.Content(
availableBalance = "400.00",
currencySymbol = "USDT",
availableBalance = "400.00 USDT",
limitDays = 7,
isEnabled = true,
onClick = {},

View file

@ -4,18 +4,16 @@ import arrow.core.getOrElse
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.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.visa.GetVisaCurrencyUseCase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.WalletAlertState
import com.tangem.feature.wallet.presentation.wallet.state.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state2.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state2.model.BalancesAndLimitsBottomSheetConfig
import com.tangem.feature.wallet.presentation.wallet.state2.transformers.converter.BalancesAndLimitsBottomSheetConverter
import com.tangem.feature.wallet.presentation.wallet.state2.utils.WalletEventSender
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@ -31,10 +29,15 @@ internal interface VisaWalletIntents {
internal class VisaWalletIntentsImplementor @Inject constructor(
private val stateController: WalletStateController,
private val eventSender: WalletEventSender,
private val getPrimaryCurrencyUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val getCurrencyStatusUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getVisaCurrencyUseCase: GetVisaCurrencyUseCase,
private val dispatchers: CoroutineDispatcherProvider,
) : BaseWalletClickIntents(), VisaWalletIntents {
private val balancesAndLimitsBottomSheetConverter by lazy(mode = LazyThreadSafetyMode.NONE) {
BalancesAndLimitsBottomSheetConverter(eventSender)
}
override fun onDepositClick() {
val userWalletId = stateController.getSelectedWalletId()
@ -47,15 +50,6 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
}
}
private suspend fun getPrimaryCurrencyStatus(userWalletId: UserWalletId): CryptoCurrencyStatus? {
return getPrimaryCurrencyUseCase(userWalletId)
.firstOrNull()
?.getOrElse {
Timber.e("Failed to get primary currency $it")
null
}
}
private fun createReceiveBottomSheetContent(currencyStatus: CryptoCurrencyStatus): TangemBottomSheetConfigContent? {
val currency = currencyStatus.currency
val addresses = currencyStatus.value.networkAddress?.availableAddresses
@ -76,35 +70,27 @@ internal class VisaWalletIntentsImplementor @Inject constructor(
}
override fun onBalancesAndLimitsClick() {
stateController.showBottomSheet(getBalancesAndLimitsConfig())
viewModelScope.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)
}
}
// TODO: Implement
private fun getBalancesAndLimitsConfig() = BalancesAndLimitsBottomSheetConfig(
currency = "USDT",
balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = "492.45",
availableBalance = "392.45",
blockedBalance = "36.00",
debit = "00.00",
pending = "20.99",
amlVerified = "356.45",
),
limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = "Nov, 11",
inStore = "563.00",
other = "100.00",
singleTransaction = "100.00",
),
onBalanceInfoClick = this::showBalanceInfo,
onLimitInfoClick = this::showLimitInfo,
)
private fun showBalanceInfo() {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
}
private fun showLimitInfo() {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo))
private suspend fun getPrimaryCurrencyStatus(userWalletId: UserWalletId): CryptoCurrencyStatus? {
return getCurrencyStatusUseCase(userWalletId)
.getOrElse {
Timber.e("Failed to get primary currency $it")
null
}
}
}

View file

@ -135,6 +135,7 @@ include(":domain:balance-hiding")
include(":domain:balance-hiding:models")
include(":domain:transaction")
include(":domain:analytics")
include(":domain:visa")
// endregion Domain modules
// region Data modules
@ -150,4 +151,5 @@ include(":data:txhistory")
include(":data:wallets")
include(":data:analytics")
include(":data:transaction")
include(":data:visa")
// endregion Data modules