Updated on 2026-08-14
This commit is contained in:
parent
819fecffb6
commit
b3640235ef
15 changed files with 175 additions and 147 deletions
|
|
@ -40,6 +40,7 @@ sealed interface PaymentAccountStatusValueDM {
|
|||
|
||||
@NameLabel("active_account")
|
||||
data class ActiveAccount(
|
||||
@Json(name = "active_account") val marker: Boolean = true,
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "currency_code") val currencyCode: String,
|
||||
@Json(name = "deposit_address") val depositAddress: String?,
|
||||
|
|
@ -59,9 +60,11 @@ sealed interface PaymentAccountStatusValueDM {
|
|||
@NameLabel("deactivated_account")
|
||||
data class DeactivatedAccount(
|
||||
@Json(name = "deactivated_account") val marker: Boolean = true,
|
||||
@Json(name = "customer_id") val customerId: String,
|
||||
@Json(name = "fiat_rate") val fiatRate: BigDecimal?,
|
||||
@Json(name = "fiat_balance") val fiatBalance: FiatBalanceDM,
|
||||
@Json(name = "crypto_balance") val cryptoBalance: CryptoBalanceDM,
|
||||
@Json(name = "available_for_withdrawal") val availableForWithdrawal: BigDecimal,
|
||||
) : PaymentAccountStatusValueDM
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
|||
is PaymentAccountStatusValue.IssuingCard -> PaymentAccountStatusValueDM.IssuingCard()
|
||||
is PaymentAccountStatusValue.Loaded -> PaymentAccountStatusValueDM.ActiveAccount(
|
||||
customerId = value.customerId,
|
||||
currencyCode = value.currencyCode,
|
||||
currencyCode = value.balance.fiatBalance.currency,
|
||||
depositAddress = value.depositAddress,
|
||||
fiatBalance = value.fiatBalance.toDM(),
|
||||
cryptoBalance = value.cryptoBalance.toDM(),
|
||||
availableForWithdrawal = value.availableForWithdrawal,
|
||||
fiatBalance = value.balance.fiatBalance.toDM(),
|
||||
cryptoBalance = value.balance.cryptoBalance.toDM(),
|
||||
availableForWithdrawal = value.balance.availableForWithdrawal,
|
||||
fiatRate = value.fiatRate,
|
||||
cards = value.cards.map { card ->
|
||||
PaymentAccountStatusValueDM.TangemPayCard(
|
||||
|
|
@ -63,9 +63,11 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
|||
)
|
||||
is PaymentAccountStatusValue.Empty -> PaymentAccountStatusValueDM.Empty()
|
||||
is PaymentAccountStatusValue.Deactivated -> PaymentAccountStatusValueDM.DeactivatedAccount(
|
||||
customerId = value.customerId,
|
||||
fiatRate = value.fiatRate,
|
||||
fiatBalance = value.fiatBalance.toDM(),
|
||||
cryptoBalance = value.cryptoBalance.toDM(),
|
||||
fiatBalance = value.balance.fiatBalance.toDM(),
|
||||
cryptoBalance = value.balance.cryptoBalance.toDM(),
|
||||
availableForWithdrawal = value.balance.availableForWithdrawal,
|
||||
)
|
||||
// Transient statuses are not persisted
|
||||
is PaymentAccountStatusValue.Loading,
|
||||
|
|
@ -90,11 +92,12 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
|||
is PaymentAccountStatusValueDM.ActiveAccount -> PaymentAccountStatusValue.Loaded(
|
||||
source = StatusSource.CACHE,
|
||||
customerId = value.customerId,
|
||||
currencyCode = value.currencyCode,
|
||||
depositAddress = value.depositAddress,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
availableForWithdrawal = value.availableForWithdrawal,
|
||||
balance = PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
availableForWithdrawal = value.availableForWithdrawal,
|
||||
),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fiatRate = value.fiatRate,
|
||||
cards = value.cards.map { card ->
|
||||
|
|
@ -123,8 +126,12 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
|||
)
|
||||
is PaymentAccountStatusValueDM.DeactivatedAccount -> PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.CACHE,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
customerId = value.customerId,
|
||||
balance = PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
availableForWithdrawal = value.availableForWithdrawal,
|
||||
),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fiatRate = value.fiatRate,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.tangem.domain.pay.repository.TangemPayCloseCardRepository
|
|||
import com.tangem.security.DeviceSecurityInfoProvider
|
||||
import com.tangem.security.isSecurityExposed
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -287,11 +288,16 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
)
|
||||
}
|
||||
fiatBalance != null && cryptoBalance != null && (isDeactivated || isFormer) -> {
|
||||
fiatBalance != null && cryptoBalance != null && !customerId.isNullOrEmpty() &&
|
||||
(isDeactivated || isFormer) -> {
|
||||
PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatBalance = fiatBalance,
|
||||
cryptoBalance = cryptoBalance,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
balance = PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = fiatBalance,
|
||||
cryptoBalance = cryptoBalance,
|
||||
availableForWithdrawal = availableForWithdrawal.orZero(),
|
||||
),
|
||||
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
|
||||
fiatRate = quotesData?.fiatRate,
|
||||
)
|
||||
|
|
@ -321,11 +327,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
return PaymentAccountStatusValue.Loaded(
|
||||
source = StatusSource.ACTUAL,
|
||||
customerId = customerId,
|
||||
currencyCode = cardInfo.currencyCode,
|
||||
depositAddress = cardInfo.depositAddress,
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
availableForWithdrawal = cardInfo.availableForWithdrawal,
|
||||
balance = PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
availableForWithdrawal = cardInfo.availableForWithdrawal,
|
||||
),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fiatRate = fiatRate,
|
||||
cards = listOf(
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ internal object CustomerInfoConverter : Converter<CustomerMeResponse.Result, Cus
|
|||
state = CustomerInfo.State.fromString(value.state),
|
||||
fiatBalance = fiatBalance?.toDomain(),
|
||||
cryptoBalance = cryptoBalance?.toDomain(),
|
||||
availableForWithdrawal = value.balance?.availableForWithdrawal?.amount,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,11 +64,15 @@ internal class PaymentAccountStatusValueDMConverterTest {
|
|||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||
availableBalance = BigDecimal("100"),
|
||||
currency = "USD",
|
||||
customerId = "customer-1",
|
||||
balance = PaymentAccountStatusValue.Balance(
|
||||
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||
availableBalance = BigDecimal("100"),
|
||||
currency = "USD",
|
||||
),
|
||||
cryptoBalance = cryptoBalance(),
|
||||
availableForWithdrawal = BigDecimal("7"),
|
||||
),
|
||||
cryptoBalance = cryptoBalance(),
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
fiatRate = BigDecimal("1.05"),
|
||||
)
|
||||
|
|
@ -79,8 +83,10 @@ internal class PaymentAccountStatusValueDMConverterTest {
|
|||
// THEN
|
||||
assertThat(result).isInstanceOf(PaymentAccountStatusValueDM.DeactivatedAccount::class.java)
|
||||
val dm = result as PaymentAccountStatusValueDM.DeactivatedAccount
|
||||
assertThat(dm.customerId).isEqualTo("customer-1")
|
||||
assertThat(dm.fiatBalance.availableBalance).isEqualTo(BigDecimal("100"))
|
||||
assertThat(dm.fiatBalance.currency).isEqualTo("USD")
|
||||
assertThat(dm.availableForWithdrawal).isEqualTo(BigDecimal("7"))
|
||||
assertThat(dm.fiatRate).isEqualTo(BigDecimal("1.05"))
|
||||
}
|
||||
|
||||
|
|
@ -141,12 +147,14 @@ internal class PaymentAccountStatusValueDMConverterTest {
|
|||
fun `GIVEN DM DeactivatedAccount WHEN convertBack THEN returns domain Deactivated with CACHE source`() {
|
||||
// GIVEN
|
||||
val dm = PaymentAccountStatusValueDM.DeactivatedAccount(
|
||||
customerId = "customer-2",
|
||||
fiatBalance = PaymentAccountStatusValueDM.FiatBalanceDM(
|
||||
availableBalance = BigDecimal("200"),
|
||||
currency = "EUR",
|
||||
),
|
||||
cryptoBalance = cryptoBalanceDM(),
|
||||
fiatRate = BigDecimal("0.92"),
|
||||
availableForWithdrawal = BigDecimal("5"),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
|
|
@ -156,8 +164,10 @@ internal class PaymentAccountStatusValueDMConverterTest {
|
|||
assertThat(result).isInstanceOf(PaymentAccountStatusValue.Deactivated::class.java)
|
||||
val deactivated = result as PaymentAccountStatusValue.Deactivated
|
||||
assertThat(deactivated.source).isEqualTo(StatusSource.CACHE)
|
||||
assertThat(deactivated.fiatBalance.availableBalance).isEqualTo(BigDecimal("200"))
|
||||
assertThat(deactivated.fiatBalance.currency).isEqualTo("EUR")
|
||||
assertThat(deactivated.customerId).isEqualTo("customer-2")
|
||||
assertThat(deactivated.balance.fiatBalance.availableBalance).isEqualTo(BigDecimal("200"))
|
||||
assertThat(deactivated.balance.fiatBalance.currency).isEqualTo("EUR")
|
||||
assertThat(deactivated.balance.availableForWithdrawal).isEqualTo(BigDecimal("5"))
|
||||
assertThat(deactivated.fiatRate).isEqualTo(BigDecimal("0.92"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ sealed class PaymentAccountStatusValue {
|
|||
is Loading -> TotalFiatBalance.Loading
|
||||
is Loaded -> {
|
||||
val rate = this.fiatRate ?: return TotalFiatBalance.Failed
|
||||
TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source)
|
||||
TotalFiatBalance.Loaded(amount = balance.fiatBalance.availableBalance.multiply(rate), source = source)
|
||||
}
|
||||
is Deactivated -> {
|
||||
val rate = this.fiatRate ?: return TotalFiatBalance.Failed
|
||||
TotalFiatBalance.Loaded(amount = fiatBalance.availableBalance.multiply(rate), source = source)
|
||||
TotalFiatBalance.Loaded(amount = balance.fiatBalance.availableBalance.multiply(rate), source = source)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,8 +104,8 @@ sealed class PaymentAccountStatusValue {
|
|||
* Represents a state where the account is deactivated.
|
||||
*
|
||||
* @property source The source of the status information.
|
||||
* @property fiatBalance The fiat balance details.
|
||||
* @property cryptoBalance The crypto balance details.
|
||||
* @property customerId The unique identifier of the customer.
|
||||
* @property balance The balance details (fiat, crypto and amount available for withdrawal).
|
||||
* @property cryptoCurrency The crypto currency held by the deactivated account.
|
||||
* @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency,
|
||||
* or `null` if the quote is not yet available. When `null`,
|
||||
|
|
@ -114,18 +114,18 @@ sealed class PaymentAccountStatusValue {
|
|||
@Serializable
|
||||
data class Deactivated(
|
||||
override val source: StatusSource,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
val customerId: String,
|
||||
val balance: Balance,
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
val fiatRate: SerializedBigDecimal?,
|
||||
) : PaymentAccountStatusValue() {
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = buildCryptoCurrencyStatusValue(
|
||||
amount = cryptoBalance.balance,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
amount = balance.cryptoBalance.balance,
|
||||
fiatAmount = balance.fiatBalance.availableBalance,
|
||||
fiatRate = fiatRate,
|
||||
depositAddress = cryptoBalance.depositAddress,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -135,11 +135,9 @@ sealed class PaymentAccountStatusValue {
|
|||
*
|
||||
* @property source The source of the status information.
|
||||
* @property customerId The unique identifier of the customer.
|
||||
* @property currencyCode The code of the currency.
|
||||
* @property depositAddress The address for deposits, if available.
|
||||
* @property fiatBalance The fiat balance details.
|
||||
* @property cryptoBalance The crypto balance details.
|
||||
* @property availableForWithdrawal The crypto amount currently available for withdrawal/swap (excludes pending/locked funds).
|
||||
* @property balance The balance details (fiat, crypto and amount available for withdrawal).
|
||||
* The fiat currency code is available via [Balance.fiatBalance].
|
||||
* @property cryptoCurrency The crypto currency held by the account.
|
||||
* @property cards The list of user's cards.
|
||||
* @property fiatRate Exchange rate of [cryptoCurrency] to the account's fiat currency,
|
||||
|
|
@ -150,11 +148,8 @@ sealed class PaymentAccountStatusValue {
|
|||
data class Loaded(
|
||||
override val source: StatusSource,
|
||||
val customerId: String,
|
||||
val currencyCode: String,
|
||||
val depositAddress: String?,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
val availableForWithdrawal: SerializedBigDecimal,
|
||||
val balance: Balance,
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
val cards: List<TangemPayCard>,
|
||||
val fiatRate: SerializedBigDecimal?,
|
||||
|
|
@ -162,10 +157,10 @@ sealed class PaymentAccountStatusValue {
|
|||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = buildCryptoCurrencyStatusValue(
|
||||
amount = availableForWithdrawal,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
amount = balance.availableForWithdrawal,
|
||||
fiatAmount = balance.fiatBalance.availableBalance,
|
||||
fiatRate = fiatRate,
|
||||
depositAddress = cryptoBalance.depositAddress,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -202,6 +197,21 @@ sealed class PaymentAccountStatusValue {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates all balance data of a payment account, as returned by the `customer/me` endpoint.
|
||||
*
|
||||
* @property fiatBalance The fiat balance details.
|
||||
* @property cryptoBalance The crypto balance details.
|
||||
* @property availableForWithdrawal The crypto amount currently available for withdrawal/swap
|
||||
* (excludes pending/locked funds).
|
||||
*/
|
||||
@Serializable
|
||||
data class Balance(
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
val availableForWithdrawal: SerializedBigDecimal,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the fiat balance of the payment account.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ data class CustomerInfo(
|
|||
val state: State,
|
||||
val fiatBalance: PaymentAccountStatusValue.FiatBalance?,
|
||||
val cryptoBalance: PaymentAccountStatusValue.CryptoBalance?,
|
||||
val availableForWithdrawal: BigDecimal?,
|
||||
) {
|
||||
enum class State {
|
||||
NEW,
|
||||
|
|
|
|||
|
|
@ -271,7 +271,7 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private fun AccountStatus.Payment.getDestinationWalletUM(wallet: UserWallet): DestinationWalletUM? {
|
||||
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: return null
|
||||
val (paymentAccountAddress, currency) = when (val status = this.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> status.cryptoBalance.depositAddress to status.cryptoCurrency
|
||||
is PaymentAccountStatusValue.Loaded -> status.balance.cryptoBalance.depositAddress to status.cryptoCurrency
|
||||
else -> return null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ internal class TangemPayCardLimitSetupModel @Inject constructor(
|
|||
?.takeIf { it.period == TangemPayCardLimitPeriod.DAY }
|
||||
?.amount
|
||||
|
||||
val currency = getJavaCurrencyByCode(status.currencyCode)
|
||||
val currency = getJavaCurrencyByCode(status.balance.fiatBalance.currency)
|
||||
uiState.update { state ->
|
||||
val amount = if (index == 0) {
|
||||
currentLimit?.stripTrailingZeros()?.toPlainString().orEmpty()
|
||||
|
|
|
|||
|
|
@ -118,8 +118,9 @@ internal class TangemPayCardPageModel @Inject constructor(
|
|||
val dailyLimitState = if (limit != null) {
|
||||
TangemPayDailyLimitBlockState.Content(
|
||||
limit = limit.amount.format {
|
||||
val symbol = getJavaCurrencyByCode(status.currencyCode).symbol
|
||||
fiat(status.currencyCode, symbol).optionalDecimals()
|
||||
val currencyCode = status.balance.fiatBalance.currency
|
||||
val symbol = getJavaCurrencyByCode(currencyCode).symbol
|
||||
fiat(currencyCode, symbol).optionalDecimals()
|
||||
},
|
||||
onChangeClick = ::onClickLimitChange,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import com.tangem.domain.models.account.PaymentAccountStatusValue
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.pay.TangemPayCardFrozenState
|
||||
import com.tangem.domain.models.pay.TangemPayCardState
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayTopUpData
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
|
||||
|
|
@ -40,8 +40,6 @@ import com.tangem.features.tangempay.entity.TangemPayDetailsErrorType
|
|||
import com.tangem.features.tangempay.entity.TangemPayDetailsNavigation
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsStateFactory
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEvent
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
|
||||
import com.tangem.features.tangempay.model.transformers.*
|
||||
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
|
||||
import com.tangem.features.tangempay.utils.*
|
||||
|
|
@ -52,7 +50,6 @@ import com.tangem.utils.coroutines.JobHolder
|
|||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import com.tangem.utils.transformer.update
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -70,33 +67,36 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
private val cardDetailsRepository: TangemPayCardDetailsRepository,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val cardDetailsEventListener: CardDetailsEventListener,
|
||||
private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener,
|
||||
private val tangemPayWithdrawRepository: TangemPayWithdrawRepository,
|
||||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, AddFundsListener {
|
||||
|
||||
private val params: TangemPayDetailsContainerComponent.Params = paramsContainer.require()
|
||||
|
||||
private val userWalletId = params.initialStatus.userWalletId
|
||||
private val isTangemPayDeactivated = params.initialStatus.isDeactivated
|
||||
private val loaded: PaymentAccountStatusValue.Loaded? =
|
||||
params.initialStatus.value as? PaymentAccountStatusValue.Loaded
|
||||
private val firstCard = loaded?.cards?.firstOrNull()
|
||||
val cryptoCurrency: CryptoCurrency = params.initialStatus.cryptoCurrency
|
||||
|
||||
private val initialCardFrozenState: TangemPayCardFrozenState = when {
|
||||
firstCard == null -> TangemPayCardFrozenState.Unfrozen
|
||||
else -> firstCard.frozenState
|
||||
}
|
||||
private val initialCard = params.initialStatus.ifLoadedOrNull { it.cards.firstOrNull() }
|
||||
|
||||
private val currentStatus = MutableStateFlow(params.initialStatus)
|
||||
|
||||
private val userWalletId
|
||||
get() = currentStatus.value.userWalletId
|
||||
|
||||
val cryptoCurrency
|
||||
get() = currentStatus.value.cryptoCurrency
|
||||
|
||||
private val stateFactory = TangemPayDetailsStateFactory(
|
||||
onBack = router::pop,
|
||||
onOpenMenu = ::onOpenMenu,
|
||||
intents = this,
|
||||
cardFrozenState = initialCardFrozenState,
|
||||
cardFrozenState = when {
|
||||
initialCard == null -> TangemPayCardFrozenState.Unfrozen
|
||||
else -> initialCard.frozenState
|
||||
},
|
||||
isRedesignEnabled = isRedesignEnabled(),
|
||||
)
|
||||
|
||||
|
|
@ -104,39 +104,49 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
field = MutableStateFlow(
|
||||
stateFactory.getInitialState(
|
||||
isTangemPayDeactivated = isTangemPayDeactivated,
|
||||
cardNumberEnd = firstCard?.lastDigits.orEmpty(),
|
||||
isReissuing = firstCard == null || firstCard.state != TangemPayCardState.Active,
|
||||
cardNumberEnd = initialCard?.lastDigits.orEmpty(),
|
||||
isReissuing = initialCard == null || initialCard.state != TangemPayCardState.Active,
|
||||
),
|
||||
)
|
||||
|
||||
private val refreshStateJobHolder = JobHolder()
|
||||
private val fetchBalanceJobHolder = JobHolder()
|
||||
private val addToWalletBannerJobHolder = JobHolder()
|
||||
|
||||
private var balance: TangemPayCardBalance? = null
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<TangemPayDetailsNavigation> = SlotNavigation()
|
||||
|
||||
init {
|
||||
analytics.send(TangemPayAnalyticsEvents.MainScreenOpened())
|
||||
handleBalanceHiding()
|
||||
fetchBalance()
|
||||
if (!isTangemPayDeactivated && firstCard != null) {
|
||||
subscribeToCardFrozenState(firstCard.id)
|
||||
fetchAddToWalletBanner()
|
||||
|
||||
paymentAccountStatusSupplier.invoke(userWalletId)
|
||||
.map { it.value }
|
||||
val statusFlow = paymentAccountStatusSupplier.invoke(userWalletId)
|
||||
.onEach { status -> currentStatus.update { status } }
|
||||
.map { it.value }
|
||||
|
||||
if (isTangemPayDeactivated) {
|
||||
statusFlow
|
||||
.filterIsInstance<PaymentAccountStatusValue.Deactivated>()
|
||||
.onEach { state ->
|
||||
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
} else {
|
||||
if (initialCard != null) {
|
||||
subscribeToCardFrozenState(initialCard.id)
|
||||
}
|
||||
fetchAddToWalletBanner()
|
||||
statusFlow
|
||||
.filterIsInstance<PaymentAccountStatusValue.Loaded>()
|
||||
.filter { it.source == StatusSource.ACTUAL }
|
||||
.onEach { state ->
|
||||
val card = state.cards.firstOrNull() ?: return@onEach
|
||||
uiState.update(
|
||||
TangemPayCardDataTransformer(
|
||||
card = card,
|
||||
onCardClick = { onCardClick() },
|
||||
),
|
||||
)
|
||||
uiState.update(DetailsBalanceTransformer(state.balance.fiatBalance))
|
||||
state.cards.firstOrNull()?.let { card ->
|
||||
uiState.update(
|
||||
TangemPayCardDataTransformer(
|
||||
card = card,
|
||||
onCardClick = { onCardClick() },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -165,17 +175,16 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
override fun onClickAddFunds() {
|
||||
analytics.send(TangemPayAnalyticsEvents.AddFundsClicked())
|
||||
val currentBalance = balance
|
||||
val depositAddress = currentBalance?.depositAddress
|
||||
if (currentBalance == null || depositAddress == null) {
|
||||
val balance = currentStatus.value.balanceOrNull()
|
||||
if (balance == null) {
|
||||
showBottomSheetError(TangemPayDetailsErrorType.Receive)
|
||||
} else {
|
||||
bottomSheetNavigation.activate(
|
||||
TangemPayDetailsNavigation.AddFunds(
|
||||
walletId = userWalletId,
|
||||
fiatBalance = currentBalance.availableForWithdrawal,
|
||||
cryptoBalance = currentBalance.availableForWithdrawal,
|
||||
depositAddress = depositAddress,
|
||||
fiatBalance = balance.availableForWithdrawal,
|
||||
cryptoBalance = balance.availableForWithdrawal,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
),
|
||||
)
|
||||
|
|
@ -184,12 +193,6 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
override fun onClickWithdraw() {
|
||||
analytics.send(TangemPayAnalyticsEvents.WithdrawClicked())
|
||||
val currentBalance = balance
|
||||
val depositAddress = currentBalance?.depositAddress
|
||||
if (currentBalance == null || depositAddress == null) {
|
||||
showBottomSheetError(TangemPayDetailsErrorType.Withdraw)
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
val hasActiveWithdrawal = tangemPayWithdrawRepository.hasWithdrawOrder(userWalletId)
|
||||
if (hasActiveWithdrawal) {
|
||||
|
|
@ -197,18 +200,19 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
} else {
|
||||
uiMessageSender.send(
|
||||
message = TangemPayMessagesFactory.createWithdrawWarning(
|
||||
onGotItClick = { onConfirmWithdrawal(cryptoCurrency, currentBalance, depositAddress) },
|
||||
onGotItClick = { onConfirmWithdrawal(cryptoCurrency) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConfirmWithdrawal(
|
||||
currency: CryptoCurrency,
|
||||
currentBalance: TangemPayCardBalance,
|
||||
depositAddress: String,
|
||||
) {
|
||||
private fun onConfirmWithdrawal(currency: CryptoCurrency) {
|
||||
val balance = currentStatus.value.balanceOrNull()
|
||||
if (balance == null) {
|
||||
showBottomSheetError(TangemPayDetailsErrorType.Withdraw)
|
||||
return
|
||||
}
|
||||
router.push(
|
||||
AppRoute.Swap(
|
||||
cryptoCurrency = currency,
|
||||
|
|
@ -216,27 +220,15 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
screenSource = AnalyticsParam.ScreensSources.TangemPay.value,
|
||||
currencyPosition = AppRoute.Swap.CurrencyPosition.FROM,
|
||||
tangemPayInput = AppRoute.Swap.TangemPayInput(
|
||||
cryptoAmount = currentBalance.availableForWithdrawal,
|
||||
fiatAmount = currentBalance.availableForWithdrawal,
|
||||
depositAddress = depositAddress,
|
||||
cryptoAmount = balance.availableForWithdrawal,
|
||||
fiatAmount = balance.availableForWithdrawal,
|
||||
depositAddress = balance.cryptoBalance.depositAddress,
|
||||
isWithdrawal = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun fetchBalance(): Job {
|
||||
return modelScope.launch {
|
||||
val result = try {
|
||||
cardDetailsRepository.getCardBalance(userWalletId).onRight { balance = it }
|
||||
} catch (e: Exception) {
|
||||
TangemLogger.e("Error", e)
|
||||
return@launch
|
||||
}
|
||||
uiState.update(transformer = DetailsBalanceTransformer(balance = result))
|
||||
}.saveIn(fetchBalanceJobHolder)
|
||||
}
|
||||
|
||||
private fun fetchAddToWalletBanner() {
|
||||
modelScope.launch {
|
||||
val isDone = try {
|
||||
|
|
@ -263,7 +255,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
|
||||
override fun onContactSupportClicked() {
|
||||
analytics.send(Basic.ButtonSupport(source = AnalyticsParam.ScreensSources.TangemPay))
|
||||
val customerId = loaded?.customerId ?: return
|
||||
val customerId = currentStatus.value.ifLoadedOrNull { it.customerId } ?: return
|
||||
modelScope.launch {
|
||||
sendFeedbackEmailUseCase.invoke(
|
||||
type = FeedbackEmailType.Visa.FeatureIsBeta(
|
||||
|
|
@ -277,10 +269,9 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
override fun onRefreshSwipe(refreshState: ShowRefreshState) {
|
||||
modelScope.launch {
|
||||
uiState.update(TangemPayDetailsRefreshTransformer(isRefreshing = refreshState.value))
|
||||
cardDetailsEventListener.send(CardDetailsEvent.Hide)
|
||||
paymentAccountStatusFetcher.invoke(userWalletId)
|
||||
expressTransactionsEventListener.send(ExpressTransactionsEvent.Update)
|
||||
txHistoryUpdateListener.triggerUpdate()
|
||||
fetchBalance().join()
|
||||
uiState.update(TangemPayDetailsRefreshTransformer(isRefreshing = false))
|
||||
}.saveIn(refreshStateJobHolder)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,32 @@
|
|||
package com.tangem.features.tangempay.model.transformers
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.formatStyled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState
|
||||
import com.tangem.features.tangempay.entity.TangemPayDetailsUM
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.util.Currency
|
||||
|
||||
internal class DetailsBalanceTransformer(
|
||||
private val balance: Either<UniversalError, TangemPayCardBalance>,
|
||||
private val fiatBalance: PaymentAccountStatusValue.FiatBalance,
|
||||
) : Transformer<TangemPayDetailsUM> {
|
||||
|
||||
override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM {
|
||||
val balance = when (balance) {
|
||||
is Either.Left<UniversalError> -> {
|
||||
TangemPayDetailsBalanceBlockState.Error(
|
||||
actionButtons = persistentListOf(),
|
||||
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
|
||||
)
|
||||
}
|
||||
is Either.Right<TangemPayCardBalance> -> {
|
||||
TangemPayDetailsBalanceBlockState.Content(
|
||||
isBalanceFlickering = false,
|
||||
fiatBalance = getFiatBalanceText(balance.value),
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
|
||||
)
|
||||
}
|
||||
}
|
||||
val balance = TangemPayDetailsBalanceBlockState.Content(
|
||||
isBalanceFlickering = false,
|
||||
fiatBalance = getFiatBalanceText(fiatBalance),
|
||||
actionButtons = prevState.balanceBlockState.actionButtons,
|
||||
cardsBlockState = prevState.balanceBlockState.cardsBlockState,
|
||||
)
|
||||
return prevState.copy(balanceBlockState = balance)
|
||||
}
|
||||
|
||||
private fun getFiatBalanceText(balance: TangemPayCardBalance): TextReference {
|
||||
val currency = Currency.getInstance(balance.currencyCode)
|
||||
return balance.fiatBalance.formatStyled {
|
||||
private fun getFiatBalanceText(fiatBalance: PaymentAccountStatusValue.FiatBalance): TextReference {
|
||||
val currency = Currency.getInstance(fiatBalance.currency)
|
||||
return fiatBalance.availableBalance.formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = currency.currencyCode,
|
||||
fiatCurrencySymbol = currency.symbol,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,12 @@ internal inline fun <T> AccountStatus.Payment.ifLoadedOrNull(call: (PaymentAccou
|
|||
}
|
||||
}
|
||||
|
||||
internal fun AccountStatus.Payment.balanceOrNull(): PaymentAccountStatusValue.Balance? = when (val v = value) {
|
||||
is PaymentAccountStatusValue.Loaded -> v.balance
|
||||
is PaymentAccountStatusValue.Deactivated -> v.balance
|
||||
else -> null
|
||||
}
|
||||
|
||||
internal fun AccountStatus.Payment.findCard(
|
||||
initialCardId: String,
|
||||
initialStatus: AccountStatus.Payment,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ internal class TangemPayCardLimitSetupModelTest {
|
|||
val statusWithLimit: PaymentAccountStatusValue.Loaded = mockk(relaxed = true) {
|
||||
every { source } returns StatusSource.ACTUAL
|
||||
every { cards } returns listOf(cardWithLimit)
|
||||
every { currencyCode } returns "USD"
|
||||
every { balance } returns mockk(relaxed = true) {
|
||||
every { fiatBalance } returns mockk(relaxed = true) {
|
||||
every { currency } returns "USD"
|
||||
}
|
||||
}
|
||||
}
|
||||
val paymentStatusWithLimit: AccountStatus.Payment = mockk(relaxed = true) {
|
||||
every { value } returns statusWithLimit
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ internal class TangemPayMainBlockConverter(
|
|||
subtitle = TextReference.Res(R.string.tangempay_status_deactivated),
|
||||
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
|
||||
balance = getBalanceText(
|
||||
currencyCode = statusValue.fiatBalance.currency,
|
||||
balance = statusValue.fiatBalance.availableBalance,
|
||||
currencyCode = statusValue.balance.fiatBalance.currency,
|
||||
balance = statusValue.balance.fiatBalance.availableBalance,
|
||||
),
|
||||
balanceSubtitle = stringReference(statusValue.cryptoCurrency.symbol),
|
||||
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
|
||||
|
|
@ -75,8 +75,8 @@ internal class TangemPayMainBlockConverter(
|
|||
},
|
||||
isBalanceFlickering = statusValue.source == StatusSource.CACHE,
|
||||
balance = getBalanceText(
|
||||
currencyCode = statusValue.currencyCode,
|
||||
balance = statusValue.fiatBalance.availableBalance,
|
||||
currencyCode = statusValue.balance.fiatBalance.currency,
|
||||
balance = statusValue.balance.fiatBalance.availableBalance,
|
||||
),
|
||||
balanceSubtitle = stringReference(statusValue.cryptoCurrency.symbol),
|
||||
shouldShowOnlyCacheWarning = statusValue.source == StatusSource.ONLY_CACHE,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue