Updated on 2026-08-14
This commit is contained in:
parent
9c8bfd4139
commit
0896f80f53
30 changed files with 483 additions and 263 deletions
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.account.AccountCharIcon
|
||||
import com.tangem.core.ui.components.account.AccountResIcon
|
||||
import com.tangem.core.ui.components.account.PaymentAccountIcon
|
||||
import com.tangem.core.ui.components.currency.DefaultCurrencyIcon
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -61,6 +62,7 @@ internal fun ContentIcon(
|
|||
background = icon.background,
|
||||
alpha = alpha,
|
||||
)
|
||||
is CurrencyIconState.PaymentAccount -> PaymentAccountIcon(modifier = modifier, size = icon.size)
|
||||
is CurrencyIconState.CryptoPortfolio.Icon -> AccountResIcon(
|
||||
modifier = modifier,
|
||||
resId = icon.resId,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ fun CurrencyIcon(
|
|||
is CurrencyIconState.FiatIcon,
|
||||
is CurrencyIconState.CustomTokenIcon,
|
||||
is CurrencyIconState.TokenIcon,
|
||||
is CurrencyIconState.PaymentAccount,
|
||||
is CurrencyIconState.CryptoPortfolio.Icon,
|
||||
is CurrencyIconState.CryptoPortfolio.Letter,
|
||||
-> {
|
||||
|
|
|
|||
|
|
@ -88,6 +88,12 @@ sealed class CurrencyIconState {
|
|||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
data class PaymentAccount(val size: AccountIconSize = AccountIconSize.Default) : CurrencyIconState() {
|
||||
override val isGrayscale: Boolean = false
|
||||
override val shouldShowCustomBadge: Boolean = false
|
||||
override val topBadgeIconResId: Int? = null
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class CryptoPortfolio : CurrencyIconState() {
|
||||
override val shouldShowCustomBadge: Boolean = false
|
||||
|
|
@ -155,6 +161,7 @@ sealed class CurrencyIconState {
|
|||
is CryptoPortfolio.Letter -> copy(
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
is PaymentAccount,
|
||||
is Loading,
|
||||
is Locked,
|
||||
is Empty,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ package com.tangem.core.ui.components.tokenlist
|
|||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.SharedTransitionScope.ResizeMode.Companion.scaleToBounds
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.core.ui.components.TextShimmer
|
|||
import com.tangem.core.ui.components.account.AccountCharIcon
|
||||
import com.tangem.core.ui.components.account.AccountIconSize
|
||||
import com.tangem.core.ui.components.account.AccountResIcon
|
||||
import com.tangem.core.ui.components.account.PaymentAccountIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.fields.SearchBar
|
||||
|
|
@ -98,6 +100,9 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea
|
|||
icon.copy(
|
||||
size = if (isExpanded) AccountIconSize.ExtraSmall else AccountIconSize.Default,
|
||||
)
|
||||
is CurrencyIconState.PaymentAccount -> icon.copy(
|
||||
size = if (isExpanded) AccountIconSize.ExtraSmall else AccountIconSize.Default,
|
||||
)
|
||||
else -> icon
|
||||
}
|
||||
|
||||
|
|
@ -183,7 +188,7 @@ fun PortfolioTokensListItem(state: PortfolioTokensListItemUM, isBalanceHidden: B
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun ExpandedPortfolioHeader(
|
||||
state: TokenItemState,
|
||||
|
|
@ -209,6 +214,7 @@ fun ExpandedPortfolioHeader(
|
|||
composables.icon.invoke(Modifier)
|
||||
} else {
|
||||
when (val icon = state.iconState) {
|
||||
is CurrencyIconState.PaymentAccount -> PaymentAccountIcon(size = icon.size)
|
||||
is CurrencyIconState.CryptoPortfolio.Icon -> AccountResIcon(
|
||||
resId = icon.resId,
|
||||
color = icon.color,
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ private fun BoxScope.CurrencyIconBackgroundLayer(state: CurrencyIconState, blurR
|
|||
blurRadius = blurRadius,
|
||||
)
|
||||
}
|
||||
is CurrencyIconState.PaymentAccount -> Unit
|
||||
CurrencyIconState.Loading -> Unit
|
||||
CurrencyIconState.Locked -> Unit
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
|
|
@ -16,14 +16,8 @@ import com.tangem.utils.logging.TangemLogger
|
|||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: DefaultTangemPayCryptoCurrencyFactory"
|
||||
/**
|
||||
* Custom token parameters. Will be used only for F&F.
|
||||
*/
|
||||
private const val TOKEN_ID = "usd-coin"
|
||||
private const val TOKEN_NAME = "USDC"
|
||||
private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
|
||||
private const val TOKEN_DECIMALS = 6
|
||||
|
||||
@Deprecated("Use TangemPayCurrencyFactory instead")
|
||||
internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val errorConverter: TangemPayErrorConverter,
|
||||
|
|
@ -47,32 +41,11 @@ internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
|
|||
)
|
||||
cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
)
|
||||
}.mapLeft { exception ->
|
||||
TangemLogger.withTag(TAG).e("Error", exception)
|
||||
errorConverter.convert(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token> {
|
||||
return catch {
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
extraDerivationPath = null,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
rawId = CryptoCurrency.RawID(TangemPayCurrencyFactory.TOKEN_ID),
|
||||
name = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
symbol = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
contractAddress = TangemPayCurrencyFactory.TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS,
|
||||
)
|
||||
}.mapLeft { exception ->
|
||||
TangemLogger.withTag(TAG).e("Error", exception)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.data.pay.converter
|
||||
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convert
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convertBack
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Two-way converter between [PaymentAccountStatusValue] and [PaymentAccountStatusValueDM].
|
||||
|
|
@ -15,10 +16,12 @@ import com.tangem.utils.converter.TwoWayConverter
|
|||
*
|
||||
* [convertBack] maps data model → domain. All restored statuses have [StatusSource.CACHE] as source.
|
||||
*/
|
||||
internal object PaymentAccountStatusValueDMConverter :
|
||||
TwoWayConverter<PaymentAccountStatusValue, PaymentAccountStatusValueDM?> {
|
||||
@Singleton
|
||||
internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
|
||||
) {
|
||||
|
||||
override fun convert(value: PaymentAccountStatusValue): PaymentAccountStatusValueDM? {
|
||||
fun convert(value: PaymentAccountStatusValue): PaymentAccountStatusValueDM? {
|
||||
return when (value) {
|
||||
is PaymentAccountStatusValue.NotCreated -> PaymentAccountStatusValueDM.NotCreated()
|
||||
is PaymentAccountStatusValue.UnderReview -> PaymentAccountStatusValueDM.UnderReview(
|
||||
|
|
@ -60,7 +63,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue {
|
||||
fun convertBack(userWalletId: UserWalletId, value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue {
|
||||
return when (value) {
|
||||
is PaymentAccountStatusValueDM.NotCreated -> PaymentAccountStatusValue.NotCreated
|
||||
is PaymentAccountStatusValueDM.CardIssueFailed -> PaymentAccountStatusValue.Error.CardIssueFailed(
|
||||
|
|
@ -80,6 +83,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
isPinSet = value.isPinSet,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
|
||||
)
|
||||
} else {
|
||||
PaymentAccountStatusValue.Loaded(
|
||||
|
|
@ -92,6 +96,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
isPinSet = value.isPinSet,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
|
||||
)
|
||||
}
|
||||
is PaymentAccountStatusValueDM.UnderReview -> PaymentAccountStatusValue.UnderReview(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.datastore.dataStoreFile
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory
|
||||
import com.tangem.data.pay.DefaultTangemPayEligibilityManager
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusProducer
|
||||
import com.tangem.data.pay.repository.*
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
|||
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.pay.repository.*
|
||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
|
||||
|
|
@ -112,6 +114,7 @@ internal interface TangemPayDataModule {
|
|||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
scope: AppCoroutineScope,
|
||||
converter: PaymentAccountStatusValueDMConverter,
|
||||
): PaymentAccountStatusesStore {
|
||||
return PaymentAccountStatusesStore(
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
|
|
@ -124,6 +127,7 @@ internal interface TangemPayDataModule {
|
|||
produceFile = { context.dataStoreFile(fileName = "payment_account_statuses") },
|
||||
scope = scope,
|
||||
),
|
||||
converter = converter,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -139,6 +143,14 @@ internal interface TangemPayDataModule {
|
|||
) {}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTangemPayCryptoCurrencyStatusUseCase(
|
||||
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
): GetPaymentAccountCryptoCurrencyStatusUseCase {
|
||||
return GetPaymentAccountCryptoCurrencyStatusUseCase(paymentAccountStatusSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayMainScreenCustomerInfoUseCase(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.data.pay.entity
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.requireUserWalletsSync
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class TangemPayCurrencyFactory @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val networkFactory: NetworkFactory,
|
||||
) {
|
||||
private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
CryptoCurrencyFactory(excludedBlockchains)
|
||||
}
|
||||
|
||||
fun create(userWalletId: UserWalletId): CryptoCurrency.Token {
|
||||
val userWallet = userWalletsListRepository.requireUserWalletsSync()
|
||||
.firstOrNull { it.walletId == userWalletId }
|
||||
?: error("User wallet with id $userWalletId not found")
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
userWallet = userWallet,
|
||||
extraDerivationPath = null,
|
||||
)
|
||||
return cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val TOKEN_ID = "usd-coin"
|
||||
internal const val TOKEN_NAME = "USDC"
|
||||
internal const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
|
||||
internal const val TOKEN_DECIMALS = 6
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.pay.flow
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.domain.models.account.Account
|
|||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
|
|
@ -29,6 +31,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val deviceSecurity: DeviceSecurityInfoProvider,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
|
||||
) : PaymentAccountStatusFetcher {
|
||||
|
||||
private val logger = TangemLogger.withTag(TAG)
|
||||
|
|
@ -132,7 +135,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
},
|
||||
ifRight = { customerInfo ->
|
||||
logger.i("proceedWithoutOrder data customerInfo ${account.userWalletId}")
|
||||
val status = customerInfo.mapToPaymentAccountStatus()
|
||||
val status = customerInfo.mapToPaymentAccountStatus(account.userWalletId)
|
||||
if (status is PaymentAccountStatusValue.IssuingCard && customerInfo.kycStatus == KycStatus.APPROVED) {
|
||||
// If order id wasn't saved -> start order creation and get customer info
|
||||
onboardingRepository.createOrder(account.userWalletId)
|
||||
|
|
@ -167,7 +170,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
},
|
||||
ifRight = { customerInfo ->
|
||||
if (customerInfo.kycStatus == KycStatus.REJECTED) {
|
||||
customerInfo.mapToPaymentAccountStatus()
|
||||
customerInfo.mapToPaymentAccountStatus(account.userWalletId)
|
||||
} else {
|
||||
PaymentAccountStatusValue.Error.CardIssueFailed(
|
||||
customerId = orderData.customerId,
|
||||
|
|
@ -182,7 +185,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId)
|
||||
.fold(
|
||||
ifLeft = { it.mapToPaymentAccountStatus() },
|
||||
ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() },
|
||||
ifRight = { it.mapToPaymentAccountStatus(account.userWalletId) },
|
||||
)
|
||||
}
|
||||
OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable
|
||||
|
|
@ -191,7 +194,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatusValue {
|
||||
private fun CustomerInfo.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue {
|
||||
val cardInfo = this.cardInfo
|
||||
val productInstance = this.productInstance
|
||||
return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) {
|
||||
|
|
@ -202,6 +205,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
)
|
||||
} else if (cardInfo != null && productInstance != null && !customerId.isNullOrEmpty()) {
|
||||
convertToContentState(
|
||||
userWalletId = userWalletId,
|
||||
productInstance = productInstance,
|
||||
cardInfo = cardInfo,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
|
|
@ -212,10 +216,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
}
|
||||
|
||||
private fun convertToContentState(
|
||||
userWalletId: UserWalletId,
|
||||
productInstance: CustomerInfo.ProductInstance,
|
||||
cardInfo: CustomerInfo.CardInfo,
|
||||
customerId: String,
|
||||
): PaymentAccountStatusValue {
|
||||
val cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId)
|
||||
return when (productInstance.frozenState) {
|
||||
TangemPayCardFrozenState.Frozen -> PaymentAccountStatusValue.Locked(
|
||||
source = StatusSource.ACTUAL,
|
||||
|
|
@ -227,6 +233,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
isPinSet = cardInfo.isPinSet,
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
else -> PaymentAccountStatusValue.Loaded(
|
||||
source = StatusSource.ACTUAL,
|
||||
|
|
@ -238,6 +245,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
isPinSet = cardInfo.isPinSet,
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatu
|
|||
internal class PaymentAccountStatusesStore(
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithPaymentStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithPaymentStatusDM>,
|
||||
private val converter: PaymentAccountStatusValueDMConverter,
|
||||
scope: AppCoroutineScope,
|
||||
) {
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ internal class PaymentAccountStatusesStore(
|
|||
runtimeStore.store(
|
||||
value = cachedStatuses.mapValues { (rawUserWalletId, statusDM) ->
|
||||
val account = Account.Payment(userWalletId = UserWalletId(rawUserWalletId))
|
||||
val statusValue = PaymentAccountStatusValueDMConverter.convertBack(value = statusDM)
|
||||
val statusValue = converter.convertBack(userWalletId = account.userWalletId, value = statusDM)
|
||||
AccountStatus.Payment(account = account, value = statusValue)
|
||||
},
|
||||
)
|
||||
|
|
@ -87,7 +88,7 @@ internal class PaymentAccountStatusesStore(
|
|||
}
|
||||
|
||||
private suspend fun storeInPersistence(userWalletId: UserWalletId, status: PaymentAccountStatusValue) {
|
||||
val statusDM = PaymentAccountStatusValueDMConverter.convert(value = status) ?: return
|
||||
val statusDM = converter.convert(value = status) ?: return
|
||||
persistenceDataStore.updateData { storedStatuses ->
|
||||
storedStatuses.toMutableMap().apply {
|
||||
put(key = userWalletId.stringValue, value = statusDM)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,13 @@ package com.tangem.domain.models.account
|
|||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.models.network.NetworkAddress
|
||||
import com.tangem.domain.models.serialization.SerializedBigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Represents the various states a payment account can have, encapsulating different information based on the state.
|
||||
|
|
@ -104,7 +108,29 @@ sealed class PaymentAccountStatusValue {
|
|||
val isPinSet: Boolean,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
) : PaymentAccountStatusValue()
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
) : PaymentAccountStatusValue() {
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = cryptoBalance.balance,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
value = cryptoBalance.depositAddress,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
pendingTransactions = emptySet(),
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a state where the payment account is successfully loaded with complete information.
|
||||
|
|
@ -130,7 +156,29 @@ sealed class PaymentAccountStatusValue {
|
|||
val isPinSet: Boolean,
|
||||
val fiatBalance: FiatBalance,
|
||||
val cryptoBalance: CryptoBalance,
|
||||
) : PaymentAccountStatusValue()
|
||||
val cryptoCurrency: CryptoCurrency.Token,
|
||||
) : PaymentAccountStatusValue() {
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus = CryptoCurrencyStatus(
|
||||
currency = cryptoCurrency,
|
||||
value = CryptoCurrencyStatus.Loaded(
|
||||
amount = cryptoBalance.balance,
|
||||
fiatAmount = fiatBalance.availableBalance,
|
||||
fiatRate = BigDecimal.ONE,
|
||||
priceChange = BigDecimal.ZERO,
|
||||
networkAddress = NetworkAddress.Single(
|
||||
defaultAddress = NetworkAddress.Address(
|
||||
type = NetworkAddress.Address.Type.Primary,
|
||||
value = cryptoBalance.depositAddress,
|
||||
),
|
||||
),
|
||||
sources = CryptoCurrencyStatus.Sources(),
|
||||
pendingTransactions = emptySet(),
|
||||
stakingBalance = null,
|
||||
yieldSupplyStatus = null,
|
||||
hasCurrentNetworkTransactions = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
/** Represents an error state for the payment account status. */
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import com.tangem.core.error.UniversalError
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
@Deprecated("TangemPayCurrencyFactory")
|
||||
interface TangemPayCryptoCurrencyFactory {
|
||||
|
||||
fun create(userWallet: UserWallet, chainId: Int): Either<UniversalError, CryptoCurrency>
|
||||
fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token>
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Option
|
||||
import arrow.core.none
|
||||
import arrow.core.some
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
|
||||
class GetPaymentAccountCryptoCurrencyStatusUseCase(
|
||||
private val paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Option<Pair<Account.Payment, CryptoCurrencyStatus>> {
|
||||
val accountStatus = paymentAccountStatusSupplier.invoke(userWalletId).firstOrNull() ?: return none()
|
||||
val cryptoCurrencyStatus = when (val statusValue = accountStatus.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
|
||||
is PaymentAccountStatusValue.Locked -> statusValue.cryptoCurrencyStatus
|
||||
else -> return none()
|
||||
}
|
||||
return if (cryptoCurrencyStatus.currency == cryptoCurrency) {
|
||||
(accountStatus.account to cryptoCurrencyStatus).some()
|
||||
} else {
|
||||
none()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.network.CryptoCurrencyAddress
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
|
||||
|
|
@ -69,7 +68,6 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
private val parseQrCodeUseCase: ParseQrCodeUseCase,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val tangemPayCryptoCurrencyFactory: TangemPayCryptoCurrencyFactory,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
) : Model(), SendDestinationClickIntents {
|
||||
|
|
@ -260,16 +258,16 @@ internal class SendDestinationModel @Inject constructor(
|
|||
|
||||
private fun AccountStatus.Payment.getDestinationWalletUM(wallet: UserWallet): DestinationWalletUM? {
|
||||
val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: return null
|
||||
val address = when (val status = this.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> status.cryptoBalance.depositAddress
|
||||
is PaymentAccountStatusValue.Locked -> status.cryptoBalance.depositAddress
|
||||
val (paymentAccountAddress, currency) = when (val status = this.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> status.cryptoBalance.depositAddress to status.cryptoCurrency
|
||||
is PaymentAccountStatusValue.Locked -> status.cryptoBalance.depositAddress to status.cryptoCurrency
|
||||
else -> return null
|
||||
}
|
||||
val currency = tangemPayCryptoCurrencyFactory.create(wallet).getOrNull() ?: return null
|
||||
|
||||
return if (contractAddress.equals(currency.contractAddress, true)) {
|
||||
DestinationWalletUM(
|
||||
name = wallet.name,
|
||||
address = address,
|
||||
address = paymentAccountAddress,
|
||||
cryptoCurrency = currency,
|
||||
userWalletId = wallet.walletId,
|
||||
account = account,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.tangem.features.swap
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -17,8 +15,6 @@ interface SwapComponent : ComposableContentComponent {
|
|||
val isInitialReverseOrder: Boolean = false,
|
||||
val screenSource: String,
|
||||
val tangemPayInput: TangemPayInput? = null,
|
||||
val preselectedToToken: CryptoCurrencyStatus? = null,
|
||||
val preselectedAccount: Account? = null,
|
||||
) {
|
||||
data class TangemPayInput(
|
||||
val cryptoAmount: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -162,8 +162,12 @@ internal class SavedSwapTransactionListConverter(
|
|||
}
|
||||
|
||||
private fun findAccountByDerivationIndex(accountList: AccountList?, derivationIndex: DerivationIndex?): Account? {
|
||||
return accountList?.accounts?.asSequence()?.filterIsInstance<Account.CryptoPortfolio>()
|
||||
?.firstOrNull { it.derivationIndex == derivationIndex }
|
||||
val accounts = accountList?.accounts ?: return null
|
||||
|
||||
return accounts.asSequence()
|
||||
.filterIsInstance<Account.CryptoPortfolio>()
|
||||
.firstOrNull { it.derivationIndex == derivationIndex }
|
||||
?: accounts.firstOrNull { it is Account.Payment }.takeIf { derivationIndex == null }
|
||||
}
|
||||
|
||||
private fun UserTokensResponse.Token.getDerivationIndex(): DerivationIndex? {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ dependencies {
|
|||
implementation(projects.domain.express.models)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.visa.models)
|
||||
|
||||
implementation(projects.features.swap.domain.api)
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ interface SwapInteractor {
|
|||
@Throws(IllegalStateException::class)
|
||||
suspend fun findBestQuote(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
providers: List<SwapProvider>,
|
||||
amountToSwap: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import com.tangem.domain.exchange.RampStateManager
|
|||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -134,13 +134,13 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun getAccountCurrencyTokensDataState(currency: CryptoCurrency): TokensDataStateExpress {
|
||||
val walletAccountCurrencyStatuses = singleAccountStatusListSupplier.getSyncOrNull(
|
||||
SingleAccountStatusListProducer.Params(userWalletId),
|
||||
)?.accountStatuses.orEmpty().filterCryptoPortfolio()
|
||||
)?.accountStatuses.orEmpty()
|
||||
|
||||
val walletAccountCurrencyStatusesExceptInitial: Map<Account, List<CryptoCurrencyStatus>> =
|
||||
walletAccountCurrencyStatuses.mapNotNull { accountStatus ->
|
||||
val filteredCurrencies = when (accountStatus) {
|
||||
is AccountStatus.CryptoPortfolio -> accountStatus.flattenCurrencies().filterCurrencies(currency)
|
||||
is AccountStatus.Payment -> TODO("[REDACTED_JIRA]")
|
||||
is AccountStatus.Payment -> getPaymentAccountCurrencies(accountStatus)
|
||||
}
|
||||
|
||||
if (filteredCurrencies.isNotEmpty()) {
|
||||
|
|
@ -184,6 +184,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun getPaymentAccountCurrencies(accountStatus: AccountStatus.Payment): List<CryptoCurrencyStatus> {
|
||||
val currencyStatus = when (val statusValue = accountStatus.value) {
|
||||
is PaymentAccountStatusValue.Loaded -> statusValue.cryptoCurrencyStatus
|
||||
is PaymentAccountStatusValue.Locked -> statusValue.cryptoCurrencyStatus
|
||||
else -> return emptyList()
|
||||
}
|
||||
|
||||
return listOf(currencyStatus)
|
||||
}
|
||||
|
||||
private fun List<CryptoCurrencyStatus>.filterCurrencies(currency: CryptoCurrency) = this.filter { status ->
|
||||
val isDifferentCurrency = status.currency.network.backendId != currency.network.backendId ||
|
||||
status.currency.getContractAddress() != currency.getContractAddress()
|
||||
|
|
@ -207,11 +217,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
tokenInfoForFilter(pair).network == currency.network.backendId
|
||||
}
|
||||
|
||||
val accountCurrencyList = cryptoCurrenciesList.mapNotNull { (accountEntry, currencyStatusList) ->
|
||||
val cryptoPortfolio = accountEntry as? Account.CryptoPortfolio ?: return@mapNotNull null
|
||||
|
||||
val accountCurrencyList = cryptoCurrenciesList.map { (accountEntry, currencyStatusList) ->
|
||||
AccountSwapAvailability(
|
||||
account = cryptoPortfolio,
|
||||
account = accountEntry,
|
||||
currencyList = currencyStatusList.map { currencyStatus ->
|
||||
val providers = findProvidersForPair(
|
||||
cryptoCurrencyStatuses = currencyStatus,
|
||||
|
|
@ -314,11 +322,12 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun findBestQuote(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
providers: List<SwapProvider>,
|
||||
amountToSwap: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
|
|
@ -343,7 +352,10 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
return providers.associateWith { createEmptyAmountState() }
|
||||
}
|
||||
val amount = SwapAmount(amountDecimal, fromToken.currency.decimals)
|
||||
val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null)
|
||||
val isBalanceWithoutFeeEnough = when (fromAccount) {
|
||||
is Account.Payment -> true
|
||||
else -> isBalanceEnough(fromToken, amount, null)
|
||||
}
|
||||
val networkId = fromToken.currency.network.backendId
|
||||
when (provider.type) {
|
||||
ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> {
|
||||
|
|
@ -397,9 +409,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun manageDex(
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
provider: SwapProvider,
|
||||
txFeeSealedState: TxFeeSealedState,
|
||||
amount: SwapAmount,
|
||||
|
|
@ -480,9 +492,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun manageDexSolana(
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
provider: SwapProvider,
|
||||
txFeeSealedState: TxFeeSealedState,
|
||||
amount: SwapAmount,
|
||||
|
|
@ -535,9 +547,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun manageCex(
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
provider: SwapProvider,
|
||||
amount: SwapAmount,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
|
|
@ -1292,9 +1304,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
amount: SwapAmount,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toTokenStatus: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
provider: SwapProvider,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
|
|
@ -1386,9 +1398,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
quoteDataModel: Either<ExpressDataError, QuoteModel>,
|
||||
amount: SwapAmount,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
networkId: String,
|
||||
isAllowedToSpend: Boolean,
|
||||
isBalanceWithoutFeeEnough: Boolean,
|
||||
|
|
@ -1500,7 +1512,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
|
||||
private suspend fun createSwapErrorWith(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
amount: SwapAmount,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
expressDataError: ExpressDataError,
|
||||
|
|
@ -1690,9 +1702,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
provider: SwapProvider,
|
||||
networkId: String,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
amount: SwapAmount,
|
||||
txFeeSealedState: TxFeeSealedState,
|
||||
expressOperationType: ExpressOperationType,
|
||||
|
|
@ -1834,7 +1846,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun produceDexSwapDataError(
|
||||
error: ExpressDataError,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
amount: SwapAmount,
|
||||
): SwapState.SwapError {
|
||||
val rates = getQuotes(fromToken.currency.id)
|
||||
|
|
@ -1931,9 +1943,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun updateBalances(
|
||||
provider: SwapProvider,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toTokenStatus: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
fromTokenAmount: SwapAmount,
|
||||
toTokenAmount: SwapAmount,
|
||||
swapData: SwapDataModel?,
|
||||
|
|
@ -1999,7 +2011,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
private suspend fun updatePermissionState(
|
||||
networkId: String,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
swapAmount: SwapAmount,
|
||||
quotesLoadedState: SwapState.QuotesLoadedState,
|
||||
spenderAddress: String?,
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ data class TokenSwapInfo(
|
|||
val tokenAmount: SwapAmount,
|
||||
val amountFiat: BigDecimal,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val account: Account.CryptoPortfolio?,
|
||||
val account: Account?,
|
||||
)
|
||||
|
||||
data class RequestApproveStateData(
|
||||
|
|
|
|||
|
|
@ -45,13 +45,13 @@ data class CurrenciesGroup(
|
|||
)
|
||||
|
||||
data class AccountSwapAvailability(
|
||||
val account: Account.CryptoPortfolio,
|
||||
val account: Account,
|
||||
val currencyList: List<AccountSwapCurrency>,
|
||||
)
|
||||
|
||||
data class AccountSwapCurrency(
|
||||
val isAvailable: Boolean,
|
||||
val account: Account.CryptoPortfolio,
|
||||
val account: Account,
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val providers: List<SwapProvider>,
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.core.decompose.factory.ComponentFactory
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
|
|
@ -23,14 +24,14 @@ internal interface ChooseTokenBridge {
|
|||
val onClose: Channel<Unit>
|
||||
|
||||
// todo swap legacy api, remove
|
||||
val onTokenSelected: Channel<Pair<String, ChooseTokenAnalyticsPayload.IsSearched>>
|
||||
val onTokenSelected: Channel<ChooseTokenResultOld>
|
||||
val onNewTokenAdded: Channel<Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>>
|
||||
|
||||
val searchQueryState: StateFlow<String>
|
||||
val currenciesGroup: Flow<CurrenciesGroup>
|
||||
|
||||
fun onTokenSelected(tokenId: Pair<String, ChooseTokenAnalyticsPayload.IsSearched>) {
|
||||
onTokenSelected.trySend(tokenId)
|
||||
fun onTokenSelected(result: ChooseTokenResultOld) {
|
||||
onTokenSelected.trySend(result)
|
||||
onSearchQuery("")
|
||||
}
|
||||
|
||||
|
|
@ -57,6 +58,12 @@ internal interface ChooseTokenBridge {
|
|||
}
|
||||
}
|
||||
|
||||
data class ChooseTokenResultOld(
|
||||
val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val account: Account,
|
||||
val isSearched: Boolean,
|
||||
)
|
||||
|
||||
data class ChooseTokenResult(
|
||||
val currency: CryptoCurrencyStatus,
|
||||
val account: AccountStatus,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenAnalyticsPayload
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResult
|
||||
import com.tangem.feature.swap.choosetoken.api.ChooseTokenResultOld
|
||||
import com.tangem.feature.swap.choosetoken.impl.model.ChooseTokenModel.Companion.DEBOUNCE_SEARCH_DELAY
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -19,7 +20,7 @@ internal class DefaultChooseTokenBridge @AssistedInject constructor(
|
|||
|
||||
override val onCurrencyChosen: Channel<ChooseTokenResult> = Channel()
|
||||
|
||||
override val onTokenSelected: Channel<Pair<String, ChooseTokenAnalyticsPayload.IsSearched>> = Channel()
|
||||
override val onTokenSelected: Channel<ChooseTokenResultOld> = Channel()
|
||||
override val onNewTokenAdded: Channel<Pair<CryptoCurrency, ChooseTokenAnalyticsPayload.IsSearched>> = Channel()
|
||||
override val onClose: Channel<Unit> = Channel()
|
||||
|
||||
|
|
|
|||
|
|
@ -116,10 +116,13 @@ internal class ChooseTokenModel @Inject constructor(
|
|||
|
||||
TokensDataConverter(
|
||||
onSearchEntered = { query -> bridge.onSearchQuery(query) },
|
||||
onTokenClick = { tokenId ->
|
||||
val selected = tokenId to ChooseTokenAnalyticsPayload
|
||||
.IsSearched(isSearchingState)
|
||||
bridge.onTokenSelected(selected)
|
||||
onTokenClick = { account, cryptoCurrencyStatus ->
|
||||
val result = ChooseTokenResultOld(
|
||||
account = account,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
isSearched = searchQueryState.value.isNotEmpty(),
|
||||
)
|
||||
bridge.onTokenSelected(result)
|
||||
},
|
||||
onAccountClick = { account ->
|
||||
expandedAccountsFlow.update { expandedList ->
|
||||
|
|
|
|||
|
|
@ -2,15 +2,21 @@ package com.tangem.feature.swap.converters
|
|||
|
||||
import com.tangem.common.getTotalCryptoAmount
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
|
||||
import com.tangem.common.ui.account.TokensListPortfolioItemConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
||||
import com.tangem.common.ui.tokens.TokenItemStateConverter.Companion.isFlickering
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.pluralReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
|
|
@ -29,34 +35,68 @@ internal class AccountTokenItemConverter(
|
|||
private val appCurrency: AppCurrency,
|
||||
private val unavailableErrorText: TextReference,
|
||||
private val expandedAccounts: Map<AccountId, Boolean>,
|
||||
private val onTokenItemClick: (String) -> Unit,
|
||||
private val onAccountItemClick: (Account.CryptoPortfolio) -> Unit,
|
||||
private val onTokenItemClick: (Account, CryptoCurrencyStatus) -> Unit,
|
||||
private val onAccountItemClick: (Account) -> Unit,
|
||||
) : Converter<AccountSwapAvailability, TokensListItemUM.Portfolio> {
|
||||
|
||||
override fun convert(value: AccountSwapAvailability): TokensListItemUM.Portfolio {
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = AccountCryptoPortfolioItemStateConverter(
|
||||
val headerTokenItemState = when (val account = value.account) {
|
||||
is Account.CryptoPortfolio -> AccountCryptoPortfolioItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
account = value.account.copy(
|
||||
cryptoCurrencies = value.currencyList.map { it.cryptoCurrencyStatus.currency },
|
||||
),
|
||||
account = account.copy(cryptoCurrencies = value.currencyList.map { it.cryptoCurrencyStatus.currency }),
|
||||
onItemClick = onAccountItemClick,
|
||||
).convert(
|
||||
TotalFiatBalance.Loaded(
|
||||
amount = value.currencyList.sumOf { it.cryptoCurrencyStatus.value.fiatAmount.orZero() },
|
||||
source = StatusSource.ONLY_CACHE,
|
||||
),
|
||||
),
|
||||
)
|
||||
is Account.Payment -> createPaymentAccountHeaderState(value)
|
||||
}
|
||||
return TokensListPortfolioItemConverter(
|
||||
tokenItemUM = headerTokenItemState,
|
||||
isExpanded = expandedAccounts[value.account.accountId] != false,
|
||||
isCollapsable = true,
|
||||
tokens = value.currencyList.map { accountSwapCurrency ->
|
||||
createAvailableItemConverter()
|
||||
createAvailableItemConverter(value.account)
|
||||
.convert(accountSwapCurrency.cryptoCurrencyStatus)
|
||||
}.map(TokensListItemUM::Token).toPersistentList(),
|
||||
).convert(Unit)
|
||||
}
|
||||
|
||||
fun createAvailableItemConverter(): TokenItemStateConverter {
|
||||
private fun createPaymentAccountHeaderState(accountSwapAvailability: AccountSwapAvailability): TokenItemState {
|
||||
val account = accountSwapAvailability.account
|
||||
val tokensCount = accountSwapAvailability.currencyList.size
|
||||
val fiatBalance =
|
||||
accountSwapAvailability.currencyList.sumOf { it.cryptoCurrencyStatus.value.fiatAmount.orZero() }
|
||||
return TokenItemState.Content(
|
||||
id = account.accountId.value,
|
||||
iconState = CurrencyIconState.PaymentAccount(),
|
||||
titleState = TokenItemState.TitleState.Content(text = account.accountName.toUM().value),
|
||||
subtitleState = TokenItemState.SubtitleState.TextContent(
|
||||
value = pluralReference(
|
||||
R.plurals.common_tokens_count,
|
||||
count = tokensCount,
|
||||
formatArgs = wrappedList(tokensCount),
|
||||
),
|
||||
isAvailable = false,
|
||||
),
|
||||
onItemClick = { onAccountItemClick(account) },
|
||||
fiatAmountState = FiatAmountState.Content(
|
||||
text = fiatBalance.format {
|
||||
fiat(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
)
|
||||
},
|
||||
isFlickering = false,
|
||||
),
|
||||
subtitle2State = null,
|
||||
onItemLongClick = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun createAvailableItemConverter(account: Account): TokenItemStateConverter {
|
||||
return TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
subtitleStateProvider = { status ->
|
||||
|
|
@ -70,7 +110,7 @@ internal class AccountTokenItemConverter(
|
|||
fiatAmountStateProvider = {
|
||||
createFiatAmountStateProvider(status = it, appCurrency = appCurrency, isAvailable = true)
|
||||
},
|
||||
onItemClick = { account, currencyStatus -> onTokenItemClick(currencyStatus.currency.id.value) },
|
||||
onItemClick = { _, currencyStatus -> onTokenItemClick(account, currencyStatus) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.feature.swap.domain.models.ui.CurrenciesGroup
|
||||
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
|
||||
import com.tangem.feature.swap.models.TokenListUMData
|
||||
|
|
@ -16,10 +17,10 @@ import kotlinx.collections.immutable.toPersistentList
|
|||
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokensDataConverter(
|
||||
private val onSearchEntered: (String) -> Unit,
|
||||
onTokenClick: (String) -> Unit,
|
||||
onAccountClick: (Account.CryptoPortfolio) -> Unit,
|
||||
onTokenClick: (Account, CryptoCurrencyStatus) -> Unit,
|
||||
onAccountClick: (Account) -> Unit,
|
||||
private val expandedAccounts: Map<AccountId, Boolean>,
|
||||
private val onSearchEntered: (String) -> Unit,
|
||||
private val tokensDataState: CurrenciesGroup,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val isAccountsMode: Boolean,
|
||||
|
|
@ -52,7 +53,7 @@ internal class TokensDataConverter(
|
|||
} else {
|
||||
val tokensList = accountList.flatMap { (_, currencyList) ->
|
||||
currencyList.asSequence().map { accountSwapCurrency ->
|
||||
accountListItemConverter.createAvailableItemConverter()
|
||||
accountListItemConverter.createAvailableItemConverter(accountSwapCurrency.account)
|
||||
.convert(accountSwapCurrency.cryptoCurrencyStatus)
|
||||
}.map(TokensListItemUM::Token).toPersistentList()
|
||||
}.toPersistentList()
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import com.tangem.core.ui.message.EventMessageAction
|
|||
import com.tangem.core.ui.utils.InputNumberFormatter
|
||||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
|
|
@ -49,12 +48,14 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
|||
import com.tangem.domain.feedback.models.BlockchainErrorInfo
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.derivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isHotWallet
|
||||
import com.tangem.domain.pay.WithdrawalResult
|
||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.promo.ShouldShowStoriesUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
|
|
@ -144,6 +145,7 @@ internal class SwapModel @Inject constructor(
|
|||
private val appsFlyerStore: AppsFlyerStore,
|
||||
private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val paymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase,
|
||||
chooseTokenBridgeFactory: ChooseTokenBridge.Factory,
|
||||
giveApprovalFeatureToggles: GiveApprovalFeatureToggles,
|
||||
) : Model() {
|
||||
|
|
@ -219,8 +221,10 @@ internal class SwapModel @Inject constructor(
|
|||
private val swapRouter: SwapRouter = SwapRouter(router = router)
|
||||
private var userCountry: UserCountry? = null
|
||||
|
||||
private var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
private var fromAccount: Account? = null
|
||||
private var toAccount: Account? = null
|
||||
private var fromAccountStatus: CryptoCurrencyStatus? = null
|
||||
private var toAccountStatus: CryptoCurrencyStatus? = null
|
||||
|
||||
/**
|
||||
* If user came from Tangem Pay -> fromAccountCurrencyStatus == null
|
||||
|
|
@ -286,8 +290,12 @@ internal class SwapModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
|
||||
chooseTokenBridge.onTokenSelected.receiveAsFlow()
|
||||
.onEach { (addedToken, isSearched) ->
|
||||
onTokenSelect(addedToken, isSearched.value)
|
||||
.onEach { result ->
|
||||
onTokenSelect(
|
||||
account = result.account,
|
||||
cryptoCurrencyStatus = result.cryptoCurrencyStatus,
|
||||
isSearched = result.isSearched,
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
||||
|
|
@ -322,22 +330,31 @@ internal class SwapModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
currency = initialCurrencyFrom,
|
||||
).getOrNull()
|
||||
val fromPaymentAccountStatus =
|
||||
paymentAccountCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom).getOrNull()
|
||||
val toAccountStatus = initialCurrencyTo?.let { currencyTo ->
|
||||
getAccountCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = currencyTo,
|
||||
).getOrNull()
|
||||
}
|
||||
val toPaymentAccountStatus = initialCurrencyTo?.let { currencyTo ->
|
||||
paymentAccountCryptoCurrencyStatusUseCase(userWalletId, currencyTo).getOrNull()
|
||||
}
|
||||
val fromAccount = fromAccountStatus?.account ?: fromPaymentAccountStatus?.first
|
||||
val fromStatus = fromAccountStatus?.status ?: fromPaymentAccountStatus?.second
|
||||
|
||||
if (fromAccountStatus == null) {
|
||||
if (fromAccount != null && fromStatus != null) {
|
||||
this@SwapModel.fromAccount = fromAccount
|
||||
this@SwapModel.fromAccountStatus = fromStatus
|
||||
this@SwapModel.toAccount = toAccountStatus?.account ?: toPaymentAccountStatus?.first
|
||||
this@SwapModel.toAccountStatus = toAccountStatus?.status ?: toPaymentAccountStatus?.second
|
||||
this@SwapModel.initialFromStatus = fromStatus
|
||||
this@SwapModel.initialToStatus = toAccountStatus?.status ?: toPaymentAccountStatus?.second
|
||||
initTokens(isInitiallyReversed)
|
||||
} else {
|
||||
showAlert()
|
||||
swapRouter.back()
|
||||
} else {
|
||||
fromAccountCurrencyStatus = fromAccountStatus
|
||||
toAccountCurrencyStatus = toAccountStatus
|
||||
initialFromStatus = fromAccountStatus.status
|
||||
initialToStatus = toAccountStatus?.status
|
||||
initTokens(isInitiallyReversed)
|
||||
}
|
||||
} else {
|
||||
val fromStatus = getFromStatus()
|
||||
|
|
@ -405,7 +422,7 @@ internal class SwapModel @Inject constructor(
|
|||
updateTokensState(state)
|
||||
|
||||
val (selectedCurrency, selectedAccount) = run {
|
||||
var selectedAccountCurrency = toAccountCurrencyStatus
|
||||
var selectedAccountCurrency = toAccountStatus
|
||||
|
||||
if (selectedAccountCurrency == null) {
|
||||
val amountSwapCurrency = swapInteractor.getInitialCurrencyToSwap(
|
||||
|
|
@ -415,14 +432,11 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
|
||||
if (amountSwapCurrency != null) {
|
||||
selectedAccountCurrency = AccountCryptoCurrencyStatus(
|
||||
account = amountSwapCurrency.account,
|
||||
status = amountSwapCurrency.cryptoCurrencyStatus,
|
||||
)
|
||||
selectedAccountCurrency = amountSwapCurrency.cryptoCurrencyStatus
|
||||
}
|
||||
}
|
||||
|
||||
selectedAccountCurrency?.status to selectedAccountCurrency?.account
|
||||
selectedAccountCurrency to toAccount
|
||||
}
|
||||
|
||||
val isApplied = applyInitialTokenChoice(
|
||||
|
|
@ -534,7 +548,7 @@ internal class SwapModel @Inject constructor(
|
|||
private fun applyInitialTokenChoice(
|
||||
state: TokensDataStateExpress,
|
||||
selectedCurrency: CryptoCurrencyStatus?,
|
||||
selectedAccount: Account.CryptoPortfolio?,
|
||||
selectedAccount: Account?,
|
||||
isReverseFromTo: Boolean,
|
||||
): Boolean {
|
||||
// exceptional case
|
||||
|
|
@ -555,9 +569,9 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
val (fromAccount, toAccount) = if (canUseFromAccountCurrencyStatus) {
|
||||
if (isOrderReversed.value) {
|
||||
selectedAccount to requireNotNull(fromAccountCurrencyStatus).account
|
||||
selectedAccount to fromAccount
|
||||
} else {
|
||||
requireNotNull(fromAccountCurrencyStatus).account to selectedAccount
|
||||
fromAccount to selectedAccount
|
||||
}
|
||||
} else {
|
||||
null to null
|
||||
|
|
@ -600,9 +614,9 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun startLoadingQuotes(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
|
|
@ -673,9 +687,9 @@ internal class SwapModel @Inject constructor(
|
|||
|
||||
private fun loadQuotesTask(
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
amount: String,
|
||||
reduceBalanceBy: BigDecimal,
|
||||
toProvidersList: List<SwapProvider>,
|
||||
|
|
@ -777,7 +791,7 @@ internal class SwapModel @Inject constructor(
|
|||
selectedFeeType = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL,
|
||||
isReverseSwapPossible = isReverseSwapPossible(),
|
||||
needApplyFCARestrictions = userCountry.needApplyFCARestrictions(),
|
||||
hideFee = tangemPayInput?.isWithdrawal == true,
|
||||
hideFee = isTangemPayWithdrawal(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -971,8 +985,9 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
val fromCurrency = requireNotNull(dataState.fromCryptoCurrency)
|
||||
val fee = getSelectedFee()
|
||||
val isTangemPayWithdrawal = isTangemPayWithdrawal()
|
||||
|
||||
if (fee == null && tangemPayInput?.isWithdrawal != true) {
|
||||
if (fee == null && !isTangemPayWithdrawal) {
|
||||
TangemLogger.e("onSwapClick: fee is null and isWithdrawal is ${tangemPayInput?.isWithdrawal}")
|
||||
showAlert(resourceReference(R.string.swapping_fee_estimation_error_text))
|
||||
modelScope.launch {
|
||||
|
|
@ -994,7 +1009,7 @@ internal class SwapModel @Inject constructor(
|
|||
includeFeeInAmount = lastLoadedQuotesState.preparedSwapConfigState.includeFeeInAmount,
|
||||
fee = fee,
|
||||
expressOperationType = ExpressOperationType.SWAP,
|
||||
isTangemPayWithdrawal = tangemPayInput?.isWithdrawal == true,
|
||||
isTangemPayWithdrawal = isTangemPayWithdrawal,
|
||||
)
|
||||
}.onSuccess { swapTransactionState ->
|
||||
when (swapTransactionState) {
|
||||
|
|
@ -1252,11 +1267,12 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun onTokenSelect(id: String, isSearched: Boolean) {
|
||||
private fun onTokenSelect(account: Account, cryptoCurrencyStatus: CryptoCurrencyStatus, isSearched: Boolean) {
|
||||
val tokens = dataState.tokensDataState ?: return
|
||||
val (foundToken, foundAccount) = getSelectedTokenAndAccount(tokens, id)
|
||||
val foundToken = cryptoCurrencyStatus
|
||||
val foundAccount = account
|
||||
|
||||
foundToken?.currency?.symbol?.let { symbol ->
|
||||
foundToken.currency.symbol.let { symbol ->
|
||||
analyticsEventHandler.send(
|
||||
SwapEvents.ChooseTokenScreenResult(isTokenChosen = true, token = symbol),
|
||||
)
|
||||
|
|
@ -1270,108 +1286,90 @@ internal class SwapModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
if (foundToken != null) {
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val fromAccount: Account.CryptoPortfolio?
|
||||
val toToken: CryptoCurrencyStatus
|
||||
val toAccount: Account.CryptoPortfolio?
|
||||
if (isOrderReversed.value) {
|
||||
fromToken = foundToken
|
||||
fromAccount = foundAccount
|
||||
toToken = initialFromStatus
|
||||
toAccount = fromAccountCurrencyStatus?.account
|
||||
val fromToken: CryptoCurrencyStatus
|
||||
val fromAccount: Account?
|
||||
val toToken: CryptoCurrencyStatus
|
||||
val toAccount: Account?
|
||||
if (isOrderReversed.value) {
|
||||
fromToken = foundToken
|
||||
fromAccount = foundAccount
|
||||
toToken = initialFromStatus
|
||||
toAccount = this.fromAccount
|
||||
|
||||
val newToken = fromToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
} else {
|
||||
fromTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
val newToken = fromToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = true,
|
||||
)
|
||||
} else {
|
||||
fromToken = initialFromStatus
|
||||
fromAccount = fromAccountCurrencyStatus?.account
|
||||
toToken = foundToken
|
||||
toAccount = foundAccount
|
||||
|
||||
val newToken = toToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
} else {
|
||||
toTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
fromTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
|
||||
if (dataState.fromCryptoCurrency != null && dataState.tokensDataState != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toCryptoCurrency = toToken,
|
||||
toAccount = toAccount,
|
||||
selectedProvider = null,
|
||||
)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
if (handleSwapNotSupported(
|
||||
state = tokens,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromAccount = fromAccount,
|
||||
toAccount = toAccount,
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
TangemLogger.i(
|
||||
"updateFeePaidCryptoCurrencyFor: id = ${fromToken.currency.id}, " +
|
||||
"isOrderReversed: ${isOrderReversed.value}",
|
||||
)
|
||||
if ((uiState.sendCardData as? SwapCardState.SwapCardData)?.type is TransactionCardType.ReadOnly) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = fromToken.currency,
|
||||
initialCurrencyTo = toToken.currency,
|
||||
fromNetworkInfo = fromToken.currency.getNetworkInfo(),
|
||||
)
|
||||
}
|
||||
updateFeePaidCryptoCurrencyFor(fromToken)
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = toAccount,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
updateTokensState(tokens)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedTokenAndAccount(
|
||||
tokens: TokensDataStateExpress,
|
||||
id: String,
|
||||
): Pair<CryptoCurrencyStatus?, Account.CryptoPortfolio?> {
|
||||
val accountCryptoCurrencyStatus = if (isOrderReversed.value) {
|
||||
tokens.fromGroup
|
||||
} else {
|
||||
tokens.toGroup
|
||||
}.accountCurrencyList.firstNotNullOfOrNull { accountSwapAvailability ->
|
||||
accountSwapAvailability.currencyList.firstOrNull { accountSwapCurrency ->
|
||||
accountSwapCurrency.cryptoCurrencyStatus.currency.id.value == id
|
||||
fromToken = initialFromStatus
|
||||
fromAccount = this.fromAccount
|
||||
toToken = foundToken
|
||||
toAccount = foundAccount
|
||||
|
||||
val newToken = toToken.currency as? CryptoCurrency.Coin
|
||||
if (newToken != null) {
|
||||
subscribeToCoinBalanceUpdates(
|
||||
userWalletId = userWalletId,
|
||||
coin = newToken,
|
||||
isFromCurrency = false,
|
||||
)
|
||||
} else {
|
||||
toTokenBalanceJobHolder.cancel()
|
||||
}
|
||||
}
|
||||
return accountCryptoCurrencyStatus?.cryptoCurrencyStatus to accountCryptoCurrencyStatus?.account
|
||||
|
||||
if (dataState.fromCryptoCurrency != null && dataState.tokensDataState != null) {
|
||||
isAmountChangedByUser = true
|
||||
}
|
||||
|
||||
dataState = dataState.copy(
|
||||
fromCryptoCurrency = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toCryptoCurrency = toToken,
|
||||
toAccount = toAccount,
|
||||
selectedProvider = null,
|
||||
)
|
||||
swapRouter.openScreen(SwapNavScreen.Main)
|
||||
if (handleSwapNotSupported(
|
||||
state = tokens,
|
||||
fromToken = fromToken,
|
||||
toToken = toToken,
|
||||
fromAccount = fromAccount,
|
||||
toAccount = toAccount,
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
modelScope.launch {
|
||||
TangemLogger.i(
|
||||
"updateFeePaidCryptoCurrencyFor: id = ${fromToken.currency.id}, " +
|
||||
"isOrderReversed: ${isOrderReversed.value}",
|
||||
)
|
||||
if ((uiState.sendCardData as? SwapCardState.SwapCardData)?.type is TransactionCardType.ReadOnly) {
|
||||
uiState = stateBuilder.createInitialLoadingState(
|
||||
initialCurrencyFrom = fromToken.currency,
|
||||
initialCurrencyTo = toToken.currency,
|
||||
fromNetworkInfo = fromToken.currency.getNetworkInfo(),
|
||||
)
|
||||
}
|
||||
updateFeePaidCryptoCurrencyFor(fromToken)
|
||||
startLoadingQuotes(
|
||||
fromToken = fromToken,
|
||||
fromAccount = fromAccount,
|
||||
toToken = toToken,
|
||||
toAccount = toAccount,
|
||||
amount = lastAmount.value,
|
||||
reduceBalanceBy = lastReducedBalanceBy.value,
|
||||
toProvidersList = findSwapProviders(fromToken, toToken),
|
||||
)
|
||||
}
|
||||
updateTokensState(tokens)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
|
|
@ -1923,8 +1921,8 @@ internal class SwapModel @Inject constructor(
|
|||
state: TokensDataStateExpress,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
): Boolean {
|
||||
val selectedCurrency = if (isOrderReversed.value) fromToken else toToken
|
||||
if (isTokenAvailableForSwap(state, selectedCurrency, isOrderReversed.value)) return false
|
||||
|
|
@ -1972,8 +1970,12 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isTangemPayWithdrawal(): Boolean {
|
||||
return tangemPayInput?.isWithdrawal == true || dataState.fromAccount is Account.Payment
|
||||
}
|
||||
|
||||
private fun List<SwapProvider>.filterForTangemPayWithdrawal(): List<SwapProvider> {
|
||||
return if (tangemPayInput?.isWithdrawal == true) {
|
||||
return if (isTangemPayWithdrawal()) {
|
||||
filter { it.type == ExchangeProviderType.CEX }
|
||||
} else {
|
||||
this
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ data class SwapProcessDataState(
|
|||
val fromCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val toCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val feePaidCryptoCurrency: CryptoCurrencyStatus? = null,
|
||||
val fromAccount: Account.CryptoPortfolio? = null,
|
||||
val toAccount: Account.CryptoPortfolio? = null,
|
||||
val fromAccount: Account? = null,
|
||||
val toAccount: Account? = null,
|
||||
// Amount from input
|
||||
val amount: String? = null,
|
||||
val reduceBalanceBy: BigDecimal = BigDecimal.ZERO,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.feature.swap.ui
|
|||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.account.AccountIconUM
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.account.CryptoPortfolioIconConverter
|
||||
import com.tangem.common.ui.account.toUM
|
||||
|
|
@ -177,8 +178,8 @@ internal class StateBuilder(
|
|||
uiStateHolder: SwapStateHolder,
|
||||
fromToken: CryptoCurrencyStatus,
|
||||
toToken: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
mainTokenId: String,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.currency.id.value
|
||||
|
|
@ -241,8 +242,8 @@ internal class StateBuilder(
|
|||
fromToken: CryptoCurrency,
|
||||
toToken: CryptoCurrency,
|
||||
mainTokenId: String,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
toAccount: Account?,
|
||||
): SwapStateHolder {
|
||||
val canSelectSendToken = mainTokenId != fromToken.id.value
|
||||
val canSelectReceiveToken = mainTokenId != toToken.id.value
|
||||
|
|
@ -501,7 +502,7 @@ internal class StateBuilder(
|
|||
swapProvider: SwapProvider,
|
||||
fromToken: TokenSwapInfo,
|
||||
toToken: CryptoCurrencyStatus?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
includeFeeInAmount: IncludeFeeInAmount,
|
||||
expressDataError: ExpressDataError,
|
||||
isReverseSwapPossible: Boolean,
|
||||
|
|
@ -618,7 +619,7 @@ internal class StateBuilder(
|
|||
emptyAmountState: SwapState.EmptyAmountState,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
toTokenStatus: CryptoCurrencyStatus?,
|
||||
toAccount: Account.CryptoPortfolio?,
|
||||
toAccount: Account?,
|
||||
isReverseSwapPossible: Boolean,
|
||||
): SwapStateHolder {
|
||||
if (uiStateHolder.sendCardData !is SwapCardState.SwapCardData) return uiStateHolder
|
||||
|
|
@ -693,7 +694,7 @@ internal class StateBuilder(
|
|||
amountFormatted: String,
|
||||
amountRaw: String,
|
||||
fromToken: CryptoCurrency,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
fromAccount: Account?,
|
||||
minTxAmount: BigDecimal?,
|
||||
): SwapStateHolder {
|
||||
if (uiState.sendCardData !is SwapCardState.SwapCardData) return uiState
|
||||
|
|
@ -1327,30 +1328,37 @@ internal class StateBuilder(
|
|||
return FCA_RESTRICTED_PROVIDER_IDS.contains(providerId)
|
||||
}
|
||||
|
||||
private fun getFromCardAccountTitle(fromAccount: Account.CryptoPortfolio?): AccountTitleUM {
|
||||
private fun getFromCardAccountTitle(fromAccount: Account?): AccountTitleUM {
|
||||
return if (fromAccount != null && isAccountsModeProvider()) {
|
||||
AccountTitleUM.Account(
|
||||
prefixText = resourceReference(R.string.common_from),
|
||||
name = fromAccount.accountName.toUM().value,
|
||||
icon = CryptoPortfolioIconConverter.convert(fromAccount.icon),
|
||||
icon = fromAccount.toIconUM(),
|
||||
)
|
||||
} else {
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_from_title))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToCardAccountTitle(toAccount: Account.CryptoPortfolio?): AccountTitleUM {
|
||||
private fun getToCardAccountTitle(toAccount: Account?): AccountTitleUM {
|
||||
return if (toAccount != null && isAccountsModeProvider()) {
|
||||
AccountTitleUM.Account(
|
||||
prefixText = resourceReference(R.string.common_to),
|
||||
name = toAccount.accountName.toUM().value,
|
||||
icon = CryptoPortfolioIconConverter.convert(toAccount.icon),
|
||||
icon = toAccount.toIconUM(),
|
||||
)
|
||||
} else {
|
||||
AccountTitleUM.Text(resourceReference(R.string.swapping_to_title))
|
||||
}
|
||||
}
|
||||
|
||||
private fun Account.toIconUM(): AccountIconUM {
|
||||
return when (this) {
|
||||
is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(icon)
|
||||
is Account.Payment -> AccountIconUM.Payment
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChangeCardsButtonState(isReverseSwapPossible: Boolean) = if (isReverseSwapPossible) {
|
||||
ChangeCardsButtonState.ENABLED
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue