diff --git a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt index 1240259f77..34a46c0524 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/AccountDomainModule.kt @@ -6,9 +6,9 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher +import com.tangem.domain.account.supplier.MultiAccountListSupplier import com.tangem.domain.account.tokens.MainAccountTokensMigration import com.tangem.domain.account.usecase.* -import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.feature.referral.data.ExternalReferralRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module @@ -94,13 +94,9 @@ internal object AccountDomainModule { @Provides @Singleton fun provideIsAccountsModeEnabledUseCase( - userWalletsListRepository: UserWalletsListRepository, - accountsCRUDRepository: AccountsCRUDRepository, + multiAccountListSupplier: MultiAccountListSupplier, ): IsAccountsModeEnabledUseCase { - return IsAccountsModeEnabledUseCase( - userWalletsListRepository = userWalletsListRepository, - crudRepository = accountsCRUDRepository, - ) + return IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier) } @Provides diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index 04f3106b33..e0b447a00d 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -7,6 +7,7 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase +import com.tangem.domain.transaction.usecase.IsMemoRequiredUseCase import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.delegate.DefaultUserWalletsSyncDelegate @@ -210,6 +211,14 @@ internal object WalletsDomainModule { return ValidateWalletMemoUseCase(walletAddressServiceRepository = walletAddressServiceRepository) } + @Provides + @Singleton + fun providesIsMemoRequiredUseCase( + walletAddressServiceRepository: WalletAddressServiceRepository, + ): IsMemoRequiredUseCase { + return IsMemoRequiredUseCase(walletAddressServiceRepository = walletAddressServiceRepository) + } + @Provides @Singleton fun providesParseSharedAddressUseCase( diff --git a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json index 5efc71652f..b9a16cd745 100644 --- a/core/config-toggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/config-toggles/src/main/assets/configs/feature_toggles_config.json @@ -80,6 +80,10 @@ "name": "SOLANA_TX_HISTORY_ENABLED", "version": "undefined" }, + { + "name": "SOLANA_SCALED_UI_AMOUNT_ENABLED", + "version": "undefined" + }, { "name": "ADD_AND_MANAGE_TOKENS_ENABLED", "version": "undefined" diff --git a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt index ac2bb1b720..edd3c2fef5 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/producer/DefaultMultiAccountListProducer.kt @@ -4,6 +4,7 @@ import arrow.core.Option import arrow.core.some import com.tangem.domain.account.models.AccountList import com.tangem.domain.account.producer.MultiAccountListProducer +import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.common.wallets.loadAndGet import com.tangem.domain.core.flow.FlowProducerTools @@ -22,7 +23,7 @@ import kotlinx.coroutines.flow.* * @property params params * @property flowProducerTools tools for producing flows * @property userWalletsListRepository repository for getting user wallets - * @property walletAccountListFlowFactory builder to create flows of [AccountList] for each wallet + * @property singleAccountListSupplier supplier for getting [AccountList] per wallet * @property dispatchers coroutine dispatchers provider * [REDACTED_AUTHOR] @@ -31,7 +32,7 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor( @Assisted val params: Unit, override val flowProducerTools: FlowProducerTools, private val userWalletsListRepository: UserWalletsListRepository, - private val walletAccountListFlowFactory: WalletAccountListFlowFactory, + private val singleAccountListSupplier: SingleAccountListSupplier, private val dispatchers: CoroutineDispatcherProvider, ) : MultiAccountListProducer { @@ -44,7 +45,7 @@ internal class DefaultMultiAccountListProducer @AssistedInject constructor( .distinctUntilChanged() .flatMapLatest { ids -> combine( - flows = ids.map(walletAccountListFlowFactory::create), + flows = ids.map(singleAccountListSupplier::invoke), transform = ::listOf, ) } diff --git a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt index 5a3723c4ad..db0d4f8e61 100644 --- a/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/producer/DefaultMultiAccountListProducerTest.kt @@ -2,6 +2,7 @@ package com.tangem.data.account.producer import com.google.common.truth.Truth import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.SingleAccountListSupplier import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.core.flow.FlowProducerTools import com.tangem.domain.models.TokensSortType @@ -28,14 +29,14 @@ import org.junit.jupiter.api.TestInstance class DefaultMultiAccountListProducerTest { private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true) - private val walletAccountListFlowFactory: WalletAccountListFlowFactory = mockk() + private val singleAccountListSupplier: SingleAccountListSupplier = mockk() private val flowProducerTools: FlowProducerTools = mockk() private val producer = DefaultMultiAccountListProducer( params = Unit, flowProducerTools = flowProducerTools, userWalletsListRepository = userWalletsListRepository, - walletAccountListFlowFactory = walletAccountListFlowFactory, + singleAccountListSupplier = singleAccountListSupplier, dispatchers = TestingCoroutineDispatcherProvider(), ) @@ -46,7 +47,7 @@ class DefaultMultiAccountListProducerTest { @AfterEach fun tearDownEach() { - clearMocks(userWalletsListRepository, walletAccountListFlowFactory) + clearMocks(userWalletsListRepository, singleAccountListSupplier) } @Test @@ -56,7 +57,7 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val accountList = AccountList.empty(userWalletId) - every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList) + every { singleAccountListSupplier.invoke(userWalletId) } returns flowOf(accountList) // Act val actual = producer.produce().let(::getEmittedValues) @@ -68,7 +69,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -82,7 +83,7 @@ class DefaultMultiAccountListProducerTest { val updatedAccountList = AccountList.empty(userWalletId = userWalletId, sortType = TokensSortType.NONE) val factoryFlow = MutableStateFlow(null) - every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull() + every { singleAccountListSupplier.invoke(userWalletId) } returns factoryFlow.filterNotNull() // Act (first emission) factoryFlow.value = accountList @@ -101,10 +102,10 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -117,7 +118,7 @@ class DefaultMultiAccountListProducerTest { val accountList = AccountList.empty(userWalletId) val factoryFlow = MutableStateFlow(null) - every { walletAccountListFlowFactory.create(userWalletId) } returns factoryFlow.filterNotNull() + every { singleAccountListSupplier.invoke(userWalletId) } returns factoryFlow.filterNotNull() // Act (first emission) factoryFlow.value = accountList @@ -136,10 +137,10 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -151,7 +152,7 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val exception = RuntimeException("Converter error") - every { walletAccountListFlowFactory.create(userWalletId) } throws exception + every { singleAccountListSupplier.invoke(userWalletId) } throws exception // Act val actual = producer.produceWithFallback().let(::getEmittedValues) @@ -163,7 +164,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -183,7 +184,7 @@ class DefaultMultiAccountListProducerTest { userWalletsListRepository.load() userWalletsListRepository.userWallets } - coVerify(inverse = true) { walletAccountListFlowFactory.create(any()) } + coVerify(inverse = true) { singleAccountListSupplier.invoke(any()) } } @Test @@ -192,7 +193,7 @@ class DefaultMultiAccountListProducerTest { val userWalletsFlow = MutableStateFlow(value = listOf(userWallet)) every { userWalletsListRepository.userWallets } returns userWalletsFlow - every { walletAccountListFlowFactory.create(userWalletId) } returns emptyFlow() + every { singleAccountListSupplier.invoke(userWalletId) } returns emptyFlow() // Act val actual = producer.produce().let(::getEmittedValues) @@ -203,7 +204,7 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) + singleAccountListSupplier.invoke(userWalletId) } } @@ -219,8 +220,8 @@ class DefaultMultiAccountListProducerTest { every { userWalletsListRepository.userWallets } returns userWalletsFlow val accountList = AccountList.empty(userWalletId) - every { walletAccountListFlowFactory.create(userWalletId) } returns flowOf(accountList) - every { walletAccountListFlowFactory.create(userWalletId2) } returns emptyFlow() + every { singleAccountListSupplier.invoke(userWalletId) } returns flowOf(accountList) + every { singleAccountListSupplier.invoke(userWalletId2) } returns emptyFlow() // Act val actual = producer.produce().let(::getEmittedValues) @@ -231,8 +232,8 @@ class DefaultMultiAccountListProducerTest { coVerifySequence { userWalletsListRepository.load() userWalletsListRepository.userWallets - walletAccountListFlowFactory.create(userWalletId) - walletAccountListFlowFactory.create(userWalletId2) + singleAccountListSupplier.invoke(userWalletId) + singleAccountListSupplier.invoke(userWalletId2) } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt index 0f48c011ce..09f8f901ec 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -12,6 +12,7 @@ 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.OrderData import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.OnboardingRepository @@ -21,7 +22,11 @@ import com.tangem.security.DeviceSecurityInfoProvider import com.tangem.security.isSecurityExposed import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.logging.TangemLogger +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import javax.inject.Inject +import kotlin.time.Duration.Companion.minutes private const val TAG = "PaymentAccountStatusFetcher" @@ -147,53 +152,103 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( } private suspend fun proceedWithOrderId(account: Account.Payment, orderId: String): PaymentAccountStatusValue { + // Step 1: Check KYC status first + val customerInfo = onboardingRepository.getCustomerInfo(account.userWalletId).fold( + ifLeft = { error -> + logger.e("proceedWithOrderId KYC check ${account.userWalletId} error: $error") + return error.mapToPaymentAccountStatus() + }, + ifRight = { it }, + ) + + logger.i("proceedWithOrderId ${account.userWalletId} kycStatus: ${customerInfo.kycStatus}") + + when (customerInfo.kycStatus) { + KycStatus.PENDING, + KycStatus.INIT, + KycStatus.REJECTED, + -> return customerInfo.mapToPaymentAccountStatus(account.userWalletId) + KycStatus.APPROVED -> Unit // proceed to order check + } + + // Step 2: Check order status return customerOrderRepository.getOrderData(userWalletId = account.userWalletId, orderId = orderId).fold( ifLeft = { error -> logger.e("proceedWithOrderId ${account.userWalletId} orderId: $orderId error: $error") error.mapToPaymentAccountStatus() }, ifRight = { orderData -> - logger.i("proceedWithOrderId $account.userWalletId: $orderId status: ${orderData.status}") + logger.i("proceedWithOrderId ${account.userWalletId}: $orderId status: ${orderData.status}") when (orderData.status) { - // Kyc is passed and user waits for order creation -> no need to get customer info + OrderStatus.CANCELED -> handleCanceledOrder(account, orderData) + OrderStatus.COMPLETED -> handleCompletedOrder(account) + OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable OrderStatus.NEW, OrderStatus.PROCESSING, - -> PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL) - - OrderStatus.CANCELED -> { - onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) - .fold( - ifLeft = { - PaymentAccountStatusValue.Error.CardIssueFailed( - customerId = orderData.customerId, - ) - }, - ifRight = { customerInfo -> - if (customerInfo.kycStatus == KycStatus.REJECTED) { - customerInfo.mapToPaymentAccountStatus(account.userWalletId) - } else { - PaymentAccountStatusValue.Error.CardIssueFailed( - customerId = orderData.customerId, - ) - } - }, - ) + -> { + paymentAccountStatusesStore.store( + userWalletId = account.userWalletId, + status = AccountStatus.Payment( + account = account, + value = PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL), + ), + ) + // Start polling for terminal state + pollOrderStatus(account = account, orderId = orderId) } - OrderStatus.COMPLETED -> { - // Order was completed -> clear order id and get customer info - onboardingRepository.clearOrderId(account.userWalletId) - onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) - .fold( - ifLeft = { it.mapToPaymentAccountStatus() }, - ifRight = { it.mapToPaymentAccountStatus(account.userWalletId) }, - ) - } - OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable } }, ) } + private suspend fun pollOrderStatus(account: Account.Payment, orderId: String): PaymentAccountStatusValue { + while (currentCoroutineContext().isActive) { + delay(1.minutes) + + val result = customerOrderRepository.getOrderData( + userWalletId = account.userWalletId, + orderId = orderId, + ) + + result.fold( + ifLeft = { error -> + logger.e("pollOrderStatus ${account.userWalletId} orderId: $orderId error: $error") + // Continue polling on transient errors + }, + ifRight = { orderData -> + logger.i("pollOrderStatus ${account.userWalletId}: $orderId status: ${orderData.status}") + when (orderData.status) { + OrderStatus.CANCELED -> return handleCanceledOrder(account, orderData) + OrderStatus.COMPLETED -> return handleCompletedOrder(account) + OrderStatus.NEW, + OrderStatus.PROCESSING, + OrderStatus.UNKNOWN, + -> Unit // Continue polling + } + }, + ) + } + + return PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL) + } + + private suspend fun handleCanceledOrder( + account: Account.Payment, + orderData: OrderData, + ): PaymentAccountStatusValue { + onboardingRepository.clearOrderId(account.userWalletId) + return PaymentAccountStatusValue.Error.CardIssueFailed(orderData.customerId) + } + + private suspend fun handleCompletedOrder(account: Account.Payment): PaymentAccountStatusValue { + onboardingRepository.clearOrderId(account.userWalletId) + return onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId) + .fold( + ifLeft = { it.mapToPaymentAccountStatus() }, + ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus(account.userWalletId) }, + ) + } + private fun CustomerInfo.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue { val cardInfo = this.cardInfo val productInstance = this.productInstance diff --git a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt index ab66eca0fd..9775180441 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/models/AccountList.kt @@ -41,8 +41,8 @@ data class AccountList private constructor( get() = accounts.first { it is Account.CryptoPortfolio && it.isMainAccount } as Account.CryptoPortfolio /** Returns true if more accounts can be added (the maximum number of accounts has not been reached) */ - val canAddMoreAccounts: Boolean - get() = accounts.size < MAX_ACCOUNTS_COUNT + val canAddMoreCryptoAccounts: Boolean + get() = accounts.filterIsInstance().size < MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT /** Returns the number of active accounts in the list */ val activeAccounts: Int @@ -102,7 +102,7 @@ data class AccountList private constructor( return accounts.flatMap { account -> when (account) { is Account.CryptoPortfolio -> account.cryptoCurrencies - is Account.Payment -> TODO("[REDACTED_JIRA]") + is Account.Payment -> emptyList() } } } @@ -151,6 +151,11 @@ data class AccountList private constructor( override fun toString(): String = "$tag: The number of accounts must not exceed 20" } + data object ExceedsMaxPaymentAccountsCount : Error { + override fun toString(): String = + "$tag: The number of payment accounts must not exceed $MAX_PAYMENT_ACCOUNTS_COUNT" + } + @Serializable data object DuplicateAccountIds : Error { override fun toString(): String = "$tag: Account list contains duplicate account IDs" @@ -169,7 +174,8 @@ data class AccountList private constructor( companion object { - const val MAX_ACCOUNTS_COUNT = 20 + const val MAX_PAYMENT_ACCOUNTS_COUNT = 1 + const val MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT = 20 const val MAX_ARCHIVED_ACCOUNTS_COUNT = 1000 private const val MAX_MAIN_ACCOUNTS_COUNT = 1 @@ -191,7 +197,11 @@ data class AccountList private constructor( ): Either = either { ensure(accounts.isNotEmpty()) { Error.EmptyAccountsList } - ensure(accounts.size <= MAX_ACCOUNTS_COUNT) { Error.ExceedsMaxAccountsCount } + val paymentAccounts = accounts.filterIsInstance() + ensure(paymentAccounts.size <= MAX_PAYMENT_ACCOUNTS_COUNT) { Error.ExceedsMaxPaymentAccountsCount } + + val cryptoAccounts = accounts.filterIsInstance() + ensure(cryptoAccounts.size <= MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT) { Error.ExceedsMaxAccountsCount } val mainAccountsCount = accounts.mainAccountsCount() ensure(mainAccountsCount == MAX_MAIN_ACCOUNTS_COUNT) { diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt index 94e6e051af..735d837692 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt @@ -1,65 +1,35 @@ package com.tangem.domain.account.usecase -import arrow.core.Option -import arrow.core.getOrElse -import com.tangem.domain.account.repository.AccountsCRUDRepository -import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.common.wallets.loadAndGet -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.isMultiCurrency -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.MultiAccountListSupplier +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map /** * Use case to determine if the accounts mode is enabled. - * Accounts mode is considered enabled if there are at least two accounts in any of the user wallets that support - * multiple currencies. + * Accounts mode is considered enabled if any [AccountList] produced for the user's wallets contains at least two + * active accounts. * - * @property crudRepository repository to perform CRUD operations on accounts. - * @property userWalletsListRepository repository to get the list of user wallets. + * @property multiAccountListSupplier supplier that provides a list of [AccountList]s for all user wallets * [REDACTED_AUTHOR] */ class IsAccountsModeEnabledUseCase( - private val crudRepository: AccountsCRUDRepository, - private val userWalletsListRepository: UserWalletsListRepository, + private val multiAccountListSupplier: MultiAccountListSupplier, ) { - @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(): Flow { - return userWalletsListRepository.loadAndGet() - .flatMapLatest { userWallets -> - val totalAccountsCountList = getTotalAccountsCountList(userWallets) - - combine(flows = totalAccountsCountList) { it.toList().isModeEnabled() } - } - .onEmpty { emit(false) } + return multiAccountListSupplier.invoke() + .map { accountsList -> accountsList.map(AccountList::activeAccounts).isModeEnabled() } .distinctUntilChanged() } suspend fun invokeSync(): Boolean { - return userWalletsListRepository.userWallets.value.orEmpty() - .map { userWallet -> - // If the wallet does not support multiple currencies, we consider its account count as 0 - if (!userWallet.isMultiCurrency) return@map 0 - - crudRepository.getTotalActiveAccountsCountSync(userWalletId = userWallet.walletId).getOrZero() - } - .isModeEnabled() + return multiAccountListSupplier.getSyncOrNull(Unit) + ?.map(AccountList::activeAccounts) + ?.isModeEnabled() == true } - private fun getTotalAccountsCountList(userWallets: List): List> { - return userWallets - .map { userWallet -> - // If the wallet does not support multiple currencies, we consider its account count as 0 - if (!userWallet.isMultiCurrency) return@map flowOf(0) - - crudRepository.getTotalActiveAccountsCount(userWalletId = userWallet.walletId) - .map { maybeCount -> maybeCount.getOrZero() } - } - } - - private fun Option.getOrZero(): Int = getOrElse { 0 } - private fun List.isModeEnabled(): Boolean = any { it >= 2 } } \ No newline at end of file diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt index a242546b21..2457408d52 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/models/AccountListTest.kt @@ -45,8 +45,8 @@ internal class AccountListTest { val fullAccountList = MockAccounts.fullAccountList // Act & Assert - Truth.assertThat(accountList.canAddMoreAccounts).isTrue() - Truth.assertThat(fullAccountList.canAddMoreAccounts).isFalse() + Truth.assertThat(accountList.canAddMoreCryptoAccounts).isTrue() + Truth.assertThat(fullAccountList.canAddMoreCryptoAccounts).isFalse() } @Test diff --git a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt index 894d4ec39d..71e7b32d89 100644 --- a/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt +++ b/domain/account/src/test/kotlin/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCaseTest.kt @@ -1,15 +1,12 @@ package com.tangem.domain.account.usecase -import arrow.core.none -import arrow.core.some import com.google.common.truth.Truth -import com.tangem.domain.account.repository.AccountsCRUDRepository -import com.tangem.domain.common.wallets.UserWalletsListRepository -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency -import io.mockk.* -import kotlinx.coroutines.flow.MutableStateFlow +import com.tangem.domain.account.models.AccountList +import com.tangem.domain.account.supplier.MultiAccountListSupplier +import io.mockk.clearMocks +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest @@ -18,21 +15,16 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance -@Suppress("UnusedFlow") @TestInstance(TestInstance.Lifecycle.PER_CLASS) class IsAccountsModeEnabledUseCaseTest { - private val accountsCRUDRepository: AccountsCRUDRepository = mockk() - private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxUnitFun = true) + private val multiAccountListSupplier: MultiAccountListSupplier = mockk() - private val useCase = IsAccountsModeEnabledUseCase( - crudRepository = accountsCRUDRepository, - userWalletsListRepository = userWalletsListRepository, - ) + private val useCase = IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier) @AfterEach fun tearDown() { - clearMocks(userWalletsListRepository, accountsCRUDRepository) + clearMocks(multiAccountListSupplier) } @Nested @@ -40,90 +32,55 @@ class IsAccountsModeEnabledUseCaseTest { inner class Invoke { @Test - fun `returns false when loadAndGet emits one wallet with isMultiCurrency false`() = runTest { + fun `returns false when supplier emits empty list`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = false) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) + every { multiAccountListSupplier.invoke() } returns flowOf(emptyList()) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - } - - verify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCount(any()) } } @Test - fun `returns true when loadAndGet emits one wallet with isMultiCurrency true`() = runTest { + fun `returns false when supplier emits account list with one account`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(2.some()) - - // Act - val actual = useCase.invoke().first() - - // Assert - Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) - } - } - - @Test - fun `returns false when loadAndGet emits one wallet with isMultiCurrency true and None counts`() = runTest { - // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) } returns flowOf(none()) + val accountList = createAccountList(activeAccounts = 1) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList)) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet.walletId) - } } @Test - fun `returns true when loadAndGet emits two wallets, one isMultiCurrency false, one true`() = runTest { + fun `returns true when supplier emits account list with two accounts`() = runTest { // Arrange - val wallet1 = createUserWallet(isMultiCurrency = false) - val wallet2 = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(wallet1, wallet2)) - every { accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId) } returns flowOf(2.some()) + val accountList = createAccountList(activeAccounts = 2) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList)) // Act val actual = useCase.invoke().first() // Assert Truth.assertThat(actual).isTrue() + } - coVerifyOrder { - userWalletsListRepository.load() - userWalletsListRepository.userWallets - accountsCRUDRepository.getTotalActiveAccountsCount(wallet2.walletId) - } + @Test + fun `returns true when supplier emits multiple account lists, one with two accounts`() = runTest { + // Arrange + val accountList1 = createAccountList(activeAccounts = 1) + val accountList2 = createAccountList(activeAccounts = 2) + every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList1, accountList2)) - verify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCount(wallet1.walletId) } + // Act + val actual = useCase.invoke().first() + + // Assert + Truth.assertThat(actual).isTrue() } } @@ -132,109 +89,71 @@ class IsAccountsModeEnabledUseCaseTest { inner class InvokeSync { @Test - fun `returns false when getUserWalletsSync returns empty list`() = runTest { + fun `returns false when getSyncOrNull returns null`() = runTest { // Arrange - every { userWalletsListRepository.userWallets.value } returns emptyList() + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns null // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isFalse() - - verifyOrder { - userWalletsListRepository.userWallets.value - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(any()) } } @Test - fun `returns false when getUserWalletsSync returns one wallet with isMultiCurrency false`() = runTest { + fun `returns false when getSyncOrNull returns empty list`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = false) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns emptyList() // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isFalse() - - verifyOrder { - userWalletsListRepository.userWallets.value - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(any()) } } @Test - fun `returns true when getUserWalletsSync returns one wallet with isMultiCurrency true`() = runTest { + fun `returns false when getSyncOrNull returns account list with one account`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) + val accountList = createAccountList(activeAccounts = 1) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList) - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns 2.some() + // Act + val actual = useCase.invokeSync() + + // Assert + Truth.assertThat(actual).isFalse() + } + + @Test + fun `returns true when getSyncOrNull returns account list with two accounts`() = runTest { + // Arrange + val accountList = createAccountList(activeAccounts = 2) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList) // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) - } } @Test - fun `returns false when getUserWalletsSync returns multi wallet with None counts`() = runTest { + fun `returns true when getSyncOrNull returns multiple account lists, one with two accounts`() = runTest { // Arrange - val wallet = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) } returns none() - - // Act - val actual = useCase.invokeSync() - - // Assert - Truth.assertThat(actual).isFalse() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet.walletId) - } - } - - @Test - fun `returns true when getUserWalletsSync returns multi and single wallets`() = runTest { - // Arrange - val wallet1 = createUserWallet(isMultiCurrency = false) - val wallet2 = createUserWallet(isMultiCurrency = true) - - every { userWalletsListRepository.userWallets.value } returns listOf(wallet1, wallet2) - coEvery { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId) } returns 2.some() + val accountList1 = createAccountList(activeAccounts = 1) + val accountList2 = createAccountList(activeAccounts = 2) + coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList1, accountList2) // Act val actual = useCase.invokeSync() // Assert Truth.assertThat(actual).isTrue() - - coVerifyOrder { - userWalletsListRepository.userWallets.value - accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet2.walletId) - } - - coVerify(inverse = true) { accountsCRUDRepository.getTotalActiveAccountsCountSync(wallet1.walletId) } } } - private fun createUserWallet(isMultiCurrency: Boolean): UserWallet = mockk { - every { this@mockk.walletId } returns UserWalletId(stringValue = "011") - every { this@mockk.isMultiCurrency } returns isMultiCurrency + private fun createAccountList(activeAccounts: Int): AccountList = mockk { + every { this@mockk.activeAccounts } returns activeAccounts } } \ No newline at end of file diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt index 1c341528d0..a2bf732b26 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/producer/DefaultSingleAccountStatusListProducer.kt @@ -363,7 +363,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo private fun createLoadingAccountStatusList(accountList: AccountList): AccountStatusList { return AccountStatusList( userWalletId = accountList.userWalletId, - accountStatuses = accountList.accounts.map { account -> + accountStatuses = accountList.accounts.mapNotNull { account -> when (account) { is Account.CryptoPortfolio -> { val currencyStatuses = account.cryptoCurrencies.map { @@ -380,7 +380,7 @@ internal class DefaultSingleAccountStatusListProducer @AssistedInject constructo priceChangeLce = lceLoading(), ) } - is Account.Payment -> TODO("[REDACTED_JIRA]") + is Account.Payment -> null } }, totalAccounts = accountList.totalAccounts, diff --git a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt index 36a47dc929..aa119597d6 100644 --- a/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt +++ b/domain/account/status/src/main/java/com/tangem/domain/account/status/usecase/RecoverCryptoPortfolioUseCase.kt @@ -46,7 +46,7 @@ class RecoverCryptoPortfolioUseCase( val accountList = getAccountList(userWalletId = accountId.userWalletId) - ensure(accountList.canAddMoreAccounts) { + ensure(accountList.canAddMoreCryptoAccounts) { raise(Error.AccountListRequirementsNotMet(cause = AccountList.Error.ExceedsMaxAccountsCount)) } diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt index f6526e3509..726fb69b1b 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/WalletNFTCollections.kt @@ -3,7 +3,7 @@ package com.tangem.domain.nft.models import com.tangem.domain.models.account.Account data class WalletNFTCollections( - val collections: Map>, + val collections: Map>, ) { val flattenCollections by lazy { collections.values.flatten() } } \ No newline at end of file diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt index f8432faa75..90a58d011c 100644 --- a/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/GetNFTCollectionsUseCase.kt @@ -18,14 +18,18 @@ class GetNFTCollectionsUseCase( @OptIn(ExperimentalCoroutinesApi::class) operator fun invoke(userWalletId: UserWalletId): Flow { return singleAccountListSupplier(userWalletId) - .mapLatest { statusList -> statusList.accounts.mapNotNull(::flowOfNFTCollections) } + .mapLatest { statusList -> + statusList.accounts.filterIsInstance().mapNotNull(::flowOfNFTCollections) + } .flatMapLatest { flows -> combine(flows) { WalletNFTCollections(it.toMap()) } } } - private fun flowOfNFTCollections(account: Account): Flow>>? { - val currencies = (account as? Account.CryptoPortfolio)?.cryptoCurrencies.orEmpty() + private fun flowOfNFTCollections( + account: Account.CryptoPortfolio, + ): Flow>>? { + val currencies = account.cryptoCurrencies if (currencies.isEmpty()) return null diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt index 1cdfec9931..ccaee70b62 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletBalanceFetcher.kt @@ -160,33 +160,30 @@ class WalletBalanceFetcher internal constructor( paymentAccountRefactorEnabled: Boolean, ) { coroutineScope { - val errorDeferreds = fetchingSources.map { source -> - async { - when (source) { - is WalletFetchingSource.Balance -> { - balanceFetchingOperations.fetchAll( - userWalletId = userWalletId, - currencies = currencies, - sources = source.sources, - ).mapKeys { (fetchingSource, _) -> fetchingSource.name } - } - is WalletFetchingSource.TangemPay -> { - fetchPaymentAccount(userWalletId, paymentAccountRefactorEnabled) - .leftOrNull() - ?.let { error -> mapOf(FetchErrorFormatter.TANGEM_PAY_SOURCE_NAME to error) } - .orEmpty() - } + // Fetch balance sources in parallel + val balanceErrors = fetchingSources.filterIsInstance() + .map { source -> + async { + balanceFetchingOperations.fetchAll( + userWalletId = userWalletId, + currencies = currencies, + sources = source.sources, + ).mapKeys { (fetchingSource, _) -> fetchingSource.name } } } - } + .awaitAll() + .fold(emptyMap()) { acc, map -> acc + map } - val errors = errorDeferreds.awaitAll().fold(emptyMap()) { acc, map -> acc + map } - - check(errors.isEmpty()) { - val message = FetchErrorFormatter.formatWalletErrors(userWalletId, errors) + check(balanceErrors.isEmpty()) { + val message = FetchErrorFormatter.formatWalletErrors(userWalletId, balanceErrors) TangemLogger.e(message) message } + + // Fetch TangemPay separately — may run long-polling, so it must not block balance error checking + if (fetchingSources.any { it is WalletFetchingSource.TangemPay }) { + fetchPaymentAccount(userWalletId, paymentAccountRefactorEnabled) + } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt index 69a2e52b58..56d00faf1e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/wallet/WalletFetchingSource.kt @@ -14,7 +14,7 @@ sealed class WalletFetchingSource { /** * TangemPay account fetching source. - * Handled separately from standard balance sources via [PaymentAccountStatusFetcher]. + * Handled separately from standard balance sources via [com.tangem.domain.pay.flow.PaymentAccountStatusFetcher]. */ data object TangemPay : WalletFetchingSource() diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt new file mode 100644 index 0000000000..410f8fe048 --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/IsMemoRequiredUseCase.kt @@ -0,0 +1,20 @@ +package com.tangem.domain.transaction.usecase + +import com.tangem.domain.models.network.Network +import com.tangem.domain.transaction.WalletAddressServiceRepository + +class IsMemoRequiredUseCase( + private val walletAddressServiceRepository: WalletAddressServiceRepository, +) { + + suspend operator fun invoke(network: Network, destinationAddress: String): Boolean { + return try { + walletAddressServiceRepository.isMemoRequired( + network = network, + destinationAddress = destinationAddress, + ) + } catch (_: Throwable) { + false + } + } +} \ No newline at end of file diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt index 50a4eaf8b6..d47f9fd8d3 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/AccountCreateEditModel.kt @@ -33,7 +33,6 @@ import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents.Compa import com.tangem.features.account.analytics.WalletSettingsAccountAnalyticEvents import com.tangem.features.account.createedit.entity.AccountCreateEditUM import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder -import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.toggleProgress import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateButton import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.updateColorSelect @@ -173,7 +172,7 @@ internal class AccountCreateEditModel @Inject constructor( val name = state.account.name.toDomain().getOrNull() ?: return val icon = CryptoPortfolioIconConverter.convertBack(state.account.portfolioIcon) val isNewName = name != params.account.accountName - val isNewIcon = icon != params.account.portfolioIcon + val isNewIcon = icon != params.account.icon val derivationIndex = params.account.derivationIndex.value analyticsEventHandler.send(AccountSettingsAnalyticEvents.ButtonSave(name, icon, derivationIndex)) @@ -258,7 +257,7 @@ internal class AccountCreateEditModel @Inject constructor( is AccountCreateEditComponent.Params.Create -> isValidName is AccountCreateEditComponent.Params.Edit -> { val oldName = params.account.accountName.toUM() - val oldIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon) + val oldIcon = CryptoPortfolioIconConverter.convert(params.account.icon) val isNewName = this.account.name.trim() != oldName val isNewIcon = this.account.portfolioIcon != oldIcon diff --git a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt index b98da2e8b9..23eb2bd18c 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/createedit/entity/AccountCreateEditUMBuilder.kt @@ -7,7 +7,6 @@ import com.tangem.core.res.R import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList -import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.CryptoPortfolioIcon import com.tangem.features.account.AccountCreateEditComponent import kotlinx.collections.immutable.toImmutableList @@ -37,10 +36,8 @@ internal class AccountCreateEditUMBuilder( ) is AccountCreateEditComponent.Params.Edit -> AccountCreateEditUM.Account( name = params.account.accountName.toUM(), - portfolioIcon = CryptoPortfolioIconConverter.convert(params.account.portfolioIcon), - derivationInfo = createAccountDerivationInfo( - index = (params.account as Account.CryptoPortfolio).derivationIndex.value, - ), + portfolioIcon = CryptoPortfolioIconConverter.convert(params.account.icon), + derivationInfo = createAccountDerivationInfo(index = params.account.derivationIndex.value), inputPlaceholder = resourceReference(R.string.account_form_placeholder_edit_account), onNameChange = onNameChange, ) @@ -50,7 +47,7 @@ internal class AccountCreateEditUMBuilder( fun initColorsUM(onColorSelect: (CryptoPortfolioIcon.Color) -> Unit): AccountCreateEditUM.Colors { val selected: CryptoPortfolioIcon.Color = when (params) { is AccountCreateEditComponent.Params.Create -> createIcon.color - is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.color + is AccountCreateEditComponent.Params.Edit -> params.account.icon.color } return AccountCreateEditUM.Colors( selected = selected, @@ -62,7 +59,7 @@ internal class AccountCreateEditUMBuilder( fun initIconsUM(onIconSelect: (CryptoPortfolioIcon.Icon) -> Unit): AccountCreateEditUM.Icons { val selected: CryptoPortfolioIcon.Icon = when (params) { is AccountCreateEditComponent.Params.Create -> createIcon.value - is AccountCreateEditComponent.Params.Edit -> params.account.portfolioIcon.value + is AccountCreateEditComponent.Params.Edit -> params.account.icon.value } return AccountCreateEditUM.Icons( selected = selected, @@ -85,13 +82,6 @@ internal class AccountCreateEditUMBuilder( } internal companion object { - - val Account.portfolioIcon: CryptoPortfolioIcon - get() = when (this) { - is Account.CryptoPortfolio -> this.icon - is Account.Payment -> TODO("[REDACTED_JIRA]") - } - fun AccountCreateEditUM.updateColorSelect(color: CryptoPortfolioIcon.Color): AccountCreateEditUM { val newIcon = this.account.portfolioIcon.copy( color = color, diff --git a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt index d9b4788e38..f21cbe265f 100644 --- a/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt +++ b/features/account/impl/src/main/java/com/tangem/features/account/details/AccountDetailsModel.kt @@ -21,7 +21,6 @@ import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.account.AccountDetailsComponent import com.tangem.features.account.analytics.AccountSettingsAnalyticEvents -import com.tangem.features.account.createedit.entity.AccountCreateEditUMBuilder.Companion.portfolioIcon import com.tangem.features.account.details.entity.AccountDetailsUM import com.tangem.features.account.details.entity.AccountDetailsUM.ArchiveMode import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -164,7 +163,7 @@ internal class AccountDetailsModel @Inject constructor( ?.isMultiCurrency == true return AccountDetailsUM( accountName = account.accountName.toUM().value, - accountIcon = CryptoPortfolioIconConverter.convert(account.portfolioIcon), + accountIcon = CryptoPortfolioIconConverter.convert(account.icon), onCloseClick = { router.pop() }, onAccountEditClick = { onEditAccountClick(account) }, onManageTokensClick = { onManageTokensClick(account) }, diff --git a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AvailableToAddData.kt b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AvailableToAddData.kt index d5e89d19f7..278f2d75a9 100644 --- a/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AvailableToAddData.kt +++ b/features/common-features/api/src/main/java/com/tangem/features/commonfeatures/api/addtoportfolio/AvailableToAddData.kt @@ -28,7 +28,7 @@ data class AvailableToAddWallet( @Serializable data class AvailableToAddAccount( - val account: AccountStatus, + val account: AccountStatus.CryptoPortfolio, val availableNetworks: Set, val addedNetworks: Set, ) { diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt index dec0e1f72b..4582cc2853 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddToPortfolioModel.kt @@ -16,7 +16,6 @@ import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2 import com.tangem.domain.markets.GetTokenMarketCryptoCurrency import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet @@ -325,10 +324,7 @@ internal class AddToPortfolioModel @Inject constructor( network: TokenMarketInfo.Network, account: AvailableToAddAccount, ): CryptoCurrency? { - val accountIndex = when (val accountStatus = account.account) { - is AccountStatus.CryptoPortfolio -> accountStatus.account.derivationIndex - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + val accountIndex = account.account.account.derivationIndex return getTokenMarketCryptoCurrency( userWalletId = userWallet.walletId, tokenMarketParams = addToPortfolioManager.token, diff --git a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddTokenModel.kt b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddTokenModel.kt index 1ebff809de..4bb4f90351 100644 --- a/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddTokenModel.kt +++ b/features/common-features/impl/src/main/java/com/tangem/features/commonfeatures/impl/addtoportfolio/model/AddTokenModel.kt @@ -11,7 +11,6 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.message.ToastMessage import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase -import com.tangem.domain.models.account.Account import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase import com.tangem.features.commonfeatures.impl.addtoportfolio.AddTokenComponent import com.tangem.features.commonfeatures.impl.addtoportfolio.model.AddTokenUiBuilder.Companion.toggleProgress @@ -105,11 +104,8 @@ internal class AddTokenModel @Inject constructor( if (status == null) { processError(error = null) } else { - when (account) { - is Account.CryptoPortfolio -> if (!account.isMainAccount) { - analyticsEventHandler.send(analyticsEventBuilder.addToNotMainAccount()) - } - is Account.Payment -> TODO("[REDACTED_JIRA]") + if (!account.isMainAccount) { + analyticsEventHandler.send(analyticsEventBuilder.addToNotMainAccount()) } analyticsEventHandler.send( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt index 0b21fb11f3..107fdc4acb 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt @@ -276,15 +276,12 @@ internal class MarketsPortfolioDelegate @AssistedInject constructor( ) } - private fun Account.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader( + private fun Account.CryptoPortfolio.toAccountPortfolioHeader(): PortfolioHeader = PortfolioHeader( id = this.accountId.value, state = AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, - icon = when (this) { - is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) - is Account.Payment -> TODO("[REDACTED_JIRA]") - }, + icon = CryptoPortfolioIconConverter.convert(this.icon), ), ) @@ -335,7 +332,7 @@ private data class Portfolio( private data class AccountWithAdded( val addedCurrency: List, - val accountStatus: AccountStatus, + val accountStatus: AccountStatus.CryptoPortfolio, ) private data class SettingsBox( diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt index bdd662e7f2..8c816de944 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsModel.kt @@ -9,6 +9,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.DesignFeatureToggles import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -54,6 +55,7 @@ internal class NewsDetailsModel @Inject constructor( private val markArticleAsViewedUseCase: MarkArticleAsViewedUseCase, private val toggleArticleLikedUseCase: ToggleArticleLikedUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val designFeatureToggles: DesignFeatureToggles, paramsContainer: ParamsContainer, ) : Model() { @@ -71,6 +73,7 @@ internal class NewsDetailsModel @Inject constructor( dispatchers = dispatchers, observeNewsDetailsUseCase = observeNewsDetailsUseCase, prefetchedIds = params.preselectedArticlesId.toSet(), + isRedesignEnabled = designFeatureToggles.isRedesignEnabled, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt index 54ad5c4ec6..7ec0b5f531 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/details/NewsDetailsPaginationManager.kt @@ -20,12 +20,14 @@ internal class NewsDetailsPaginationManager( currentCategoryIds: Provider>, modelScope: CoroutineScope, prefetchedIds: Set, + isRedesignEnabled: Boolean, ) : NewsListBatchFlowManager( getNewsListBatchFlowUseCase = getNewsListBatchFlowUseCase, currentLanguage = currentLanguage, currentCategoryIds = currentCategoryIds, modelScope = modelScope, dispatchers = dispatchers, + isRedesignEnabled = isRedesignEnabled, ) { private val _cachedPrefetchedIds = MutableStateFlow(prefetchedIds) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt index 22ba4a7be4..9542f5be8a 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt @@ -4,6 +4,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.ui.DesignFeatureToggles import com.tangem.core.ui.components.chip.entity.ChipUM import com.tangem.domain.news.model.NewsListConfig import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase @@ -34,6 +35,7 @@ internal class NewsListModel @Inject constructor( private val getNewsCategoriesUseCase: GetNewsCategoriesUseCase, private val getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val designFeatureToggles: DesignFeatureToggles, paramsContainer: ParamsContainer, ) : Model() { @@ -58,6 +60,7 @@ internal class NewsListModel @Inject constructor( }, modelScope = modelScope, dispatchers = dispatchers, + isRedesignEnabled = designFeatureToggles.isRedesignEnabled, ) } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt index ca5f6d7382..a6e5d5a2fd 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/statemanager/NewsListBatchFlowManager.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch @Suppress("LongParameterList") internal open class NewsListBatchFlowManager( + private val isRedesignEnabled: Boolean, getNewsListBatchFlowUseCase: GetNewsListBatchFlowUseCase, private val currentLanguage: Provider, private val currentCategoryIds: Provider>, @@ -30,7 +31,13 @@ internal open class NewsListBatchFlowManager( ) { private val actionsFlow = MutableSharedFlow>() private val converter by lazy { - ShortArticleToArticleConfigUMConverter(null) + ShortArticleToArticleConfigUMConverter( + isTrending = if (isRedesignEnabled) { + null + } else { + false + }, + ) } private val batchFlow = getNewsListBatchFlowUseCase( diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt index 8931a13343..a4f71b8172 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt @@ -17,8 +17,8 @@ import com.tangem.core.ui.message.EventMessageAction import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier import com.tangem.domain.managetokens.GetSupportedNetworksUseCase -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.network.Network import com.tangem.features.managetokens.component.AddCustomTokenMode import com.tangem.features.managetokens.component.CustomTokenSelectorComponent @@ -214,23 +214,14 @@ internal class CustomTokenSelectorModel @Inject constructor( this.account.derivationIndex.value.toLong() == accountNode val accounts = singleAccountStatusListSupplier(mode.userWalletId) - .first().accountStatuses + .first().accountStatuses.filterCryptoPortfolio() - val accountStatus = accounts.find { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> account.sameNodeAndNotMain() - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - } - - accountStatus?.account + accounts + .find { account -> account.sameNodeAndNotMain() } + ?.account } - val accountName = when (account) { - is Account.CryptoPortfolio -> account.accountName - is Account.Payment -> TODO("[REDACTED_JIRA]") - null -> null - } + val accountName = account?.accountName if (accountName == null) { onDerivationPathSelected(derivationPath, null) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt index 6a2f95803d..80e74ee49c 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/collections/entity/transformer/UpdateDataStateTransformer.kt @@ -95,15 +95,12 @@ internal class UpdateDataStateTransformer( createNFTsUM(mainAccountCollection).toPersistentList() } - private fun Account.toAccountPortfolioUM(): NFTCollectionPortfolioUM = NFTCollectionPortfolioUM( + private fun Account.CryptoPortfolio.toAccountPortfolioUM(): NFTCollectionPortfolioUM = NFTCollectionPortfolioUM( id = this.accountId.value, title = AccountTitleUM.Account( prefixText = TextReference.EMPTY, name = this.accountName.toUM().value, - icon = when (this) { - is Account.CryptoPortfolio -> CryptoPortfolioIconConverter.convert(this.icon) - is Account.Payment -> TODO("[REDACTED_JIRA]") - }, + icon = CryptoPortfolioIconConverter.convert(this.icon), ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt index d49f018ad2..a317ada8d2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/hottokens/model/HotCryptoPortfolioDataLoader.kt @@ -8,6 +8,7 @@ import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.account.filterCryptoPortfolio import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -42,14 +43,10 @@ internal class HotCryptoPortfolioDataLoader @Inject constructor( .invokeSync(userWalletId, hotCryptoCurrencies) .getOrNull() .orEmpty() - val accountsWithHotCrypto = walletAccounts.accountStatuses.map { accountStatus -> - val account: AccountStatus.CryptoPortfolio = when (accountStatus) { - is AccountStatus.CryptoPortfolio -> accountStatus - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - val addedHotCrypto = mapOfAddedCurrencies[account.account].orEmpty() + val accountsWithHotCrypto = walletAccounts.accountStatuses.filterCryptoPortfolio().map { accountStatus -> + val addedHotCrypto = mapOfAddedCurrencies[accountStatus.account].orEmpty() HotCryptoPortfolioData.Account( - account = account, + account = accountStatus, addedHotCrypto = addedHotCrypto, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt index cd1e38a700..dc0066f473 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/swap/availablepairs/model/AvailableSwapPairsModel.kt @@ -247,20 +247,16 @@ internal class AvailableSwapPairsModel @Inject constructor( val (appCurrency, isBalanceHidden) = appCurrencyAndBalanceHiding val filterByQueryAccountList: Map> = accountList + .filterCryptoPortfolio() .associate { accountStatus -> - when (accountStatus) { - is AccountStatus.CryptoPortfolio -> { - val statuses = accountStatus.tokenList.flattenCurrencies() - .filterNot { status -> - status.currency.network.rawId == selectedStatus?.currency?.network?.rawId && - status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress - } - .filterByQuery(query = query) - - accountStatus.account to statuses + val statuses = accountStatus.tokenList.flattenCurrencies() + .filterNot { status -> + status.currency.network.rawId == selectedStatus?.currency?.network?.rawId && + status.currency.id.contractAddress == selectedStatus.currency.id.contractAddress } - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + .filterByQuery(query = query) + + accountStatus.account to statuses } .filterValues { it.isNotEmpty() } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt index a7ca52c790..797dfec9df 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/entity/transformer/SetLoadingAccountTokenListTransformer.kt @@ -21,12 +21,9 @@ internal class SetLoadingAccountTokenListTransformer( private val accountListItemConverter = LoadingAccountTokenItemConverter(appCurrency) override fun transform(prevState: TokenListUM): TokenListUM { - val totalTokensCount = accountList.sumOf { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> account.tokenList.flattenCurrencies().size - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } - } + val totalTokensCount = accountList + .filterCryptoPortfolio() + .sumOf { account -> account.tokenList.flattenCurrencies().size } return prevState.copy( availableItems = persistentListOf(), @@ -40,13 +37,10 @@ internal class SetLoadingAccountTokenListTransformer( ) } else { TokenListUMData.TokenList( - tokensList = accountList.flatMap { account -> - when (account) { - is AccountStatus.CryptoPortfolio -> LoadingTokenListItemConverter.convertList( - account.tokenList.flattenCurrencies().map(CryptoCurrencyStatus::currency), - ) - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + tokensList = accountList.filterCryptoPortfolio().flatMap { account -> + LoadingTokenListItemConverter.convertList( + account.tokenList.flattenCurrencies().map(CryptoCurrencyStatus::currency), + ) }.toPersistentList(), totalTokensCount = totalTokensCount, ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 5b62caa0f6..0b7676b1e6 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -18,7 +18,6 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.exchange.RampStateManager import com.tangem.domain.models.TotalFiatBalance 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.currency.CryptoCurrencyStatus import com.tangem.domain.settings.usercountry.GetUserCountryUseCase @@ -240,13 +239,8 @@ internal class OnrampTokenListModel @Inject constructor( ): Map> = accountStatuses.asSequence() .filterCryptoPortfolio() .associate { accountStatus -> - when (accountStatus) { - is AccountStatus.CryptoPortfolio -> { - val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query) - accountStatus.account to filteredList - } - is AccountStatus.Payment -> TODO("[REDACTED_JIRA]") - } + val filteredList = accountStatus.tokenList.flattenCurrencies().filterByQuery(query = query) + accountStatus.account to filteredList }.filter { (_, value) -> value.isNotEmpty() } private fun List.filterByQuery(query: String): List { diff --git a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt index 3dac39dddb..b3d7fdc50a 100644 --- a/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt +++ b/features/referral/domain/src/main/java/com/tangem/feature/referral/domain/ReferralInteractorImpl.kt @@ -4,9 +4,9 @@ import com.tangem.common.core.TangemSdkError import com.tangem.domain.account.producer.SingleAccountProducer import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase import com.tangem.domain.account.supplier.SingleAccountSupplier -import com.tangem.domain.models.account.Account import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.DerivationIndex +import com.tangem.domain.models.account.derivationIndex import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.walletmanager.WalletManagersFacade @@ -40,20 +40,13 @@ internal class ReferralInteractorImpl( val account = singleAccountSupplier.getSyncOrNull( params = SingleAccountProducer.Params(accountId = accountId), - ) - ?: error("Account not found: $accountId") - - val accountIndex = when (account) { - is Account.CryptoPortfolio -> account.derivationIndex - is Account.Payment -> TODO("[REDACTED_JIRA]") - } + ) ?: error("Account not found: $accountId") val cryptoCurrency = getCryptoCurrency( userWalletId = accountId.userWalletId, tokenData = tokenData, - accountIndex = accountIndex, - ) - ?: error("Failed to create crypto currency") + accountIndex = account.derivationIndex, + ) ?: error("Failed to create crypto currency") manageCryptoCurrenciesUseCase( accountId = accountId, diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt index 5ec0bf75e3..b6c92acf3a 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/SendNotificationsComponent.kt @@ -33,7 +33,7 @@ interface SendNotificationsComponent { val callback: ModelCallback, ) { data class NotificationData( - val destinationAddress: String, + val destinationAddress: String?, val memo: String?, val amountValue: BigDecimal, val reduceAmountBy: BigDecimal, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt index cd6ae47ea5..62dc80100b 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/notifications/model/NotificationsModel.kt @@ -34,7 +34,6 @@ import com.tangem.domain.tokens.GetCurrencyCheckUseCase import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase import com.tangem.domain.utils.convertToSdkAmount import com.tangem.features.send.v2.api.SendNotificationsComponent import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData @@ -69,7 +68,6 @@ internal class NotificationsModel @Inject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase, private val validateTransactionUseCase: ValidateTransactionUseCase, - private val validateWalletMemoUseCase: ValidateWalletMemoUseCase, private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase, private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase, private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger, @@ -302,7 +300,7 @@ internal class NotificationsModel @Inject constructor( } private suspend fun MutableList.addWarningNotifications( - destinationAddress: String, + destinationAddress: String?, memo: String?, enteredAmount: BigDecimal, sendingAmount: BigDecimal, @@ -311,14 +309,18 @@ internal class NotificationsModel @Inject constructor( isFeeCoverage: Boolean, currencyCheck: CryptoCurrencyCheck, ) { - val validationError = validateTransactionUseCase( - userWalletId = userWalletId, - amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus), - fee = fee, - memo = memo, - destination = destinationAddress, - network = cryptoCurrencyStatus.currency.network, - ).leftOrNull() + val validationError = if (destinationAddress != null) { + validateTransactionUseCase( + userWalletId = userWalletId, + amount = enteredAmount.convertToSdkAmount(cryptoCurrencyStatus), + fee = fee, + memo = memo, + destination = destinationAddress, + network = cryptoCurrencyStatus.currency.network, + ).leftOrNull() + } else { + null + } addRentExemptionNotification( rentWarning = currencyCheck.rentWarning, @@ -352,10 +354,6 @@ internal class NotificationsModel @Inject constructor( params.callback.onAmountReduceTo(reduceTo) }, ) - addDestinationTagRequiredNotification( - isMemoRequired = currencyCheck.isMemoRequired, - memo = memo, - ) addHighFeeWarningNotification( enteredAmountValue = enteredAmount, cryptoCurrencyStatus = cryptoCurrencyStatus, @@ -376,21 +374,6 @@ internal class NotificationsModel @Inject constructor( addTronNetworkFeesNotification() } - private suspend fun MutableList.addDestinationTagRequiredNotification( - isMemoRequired: Boolean, - memo: String?, - ) { - if (!isMemoRequired || contains(NotificationUM.Error.DestinationTagRequired)) return - val isMemoInvalid = memo.isNullOrEmpty() || validateWalletMemoUseCase( - userWalletId = userWalletId, - cryptoCurrency = currency, - memo = memo, - ).isLeft() - if (isMemoInvalid) { - add(NotificationUM.Error.DestinationTagRequired) - } - } - private suspend fun MutableList.addTronNetworkFeesNotification() { val cryptoCurrency = cryptoCurrencyStatus.currency val isTronToken = cryptoCurrency is CryptoCurrency.Token && diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt index 23ec462f60..ecc249a62b 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/SwapAmountModel.kt @@ -215,13 +215,13 @@ internal class SwapAmountModel @Inject constructor( } } - uiState.update { amountUM -> - if (amountUM !is SwapAmountUM.Content) return@update amountUM - amountUM.copy( + uiState.transformerUpdate( + SwapAmountChangeAmountTypeTransformer( selectedAmountType = selectedAmountType, swapRateType = newSwapRateType, - ) - } + isBalanceHidden = params.isBalanceHidingFlow.value, + ), + ) startLoadingQuotesTask(isSilentReload = false) } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt new file mode 100644 index 0000000000..c1fde5d967 --- /dev/null +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/amount/model/transformers/SwapAmountChangeAmountTypeTransformer.kt @@ -0,0 +1,51 @@ +package com.tangem.features.swap.v2.impl.amount.model.transformers + +import com.tangem.domain.express.models.ExpressRateType +import com.tangem.domain.swap.models.SwapAmountType +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountFieldUM +import com.tangem.features.swap.v2.impl.amount.entity.SwapAmountUM +import com.tangem.features.swap.v2.impl.amount.model.converter.SwapAmountUpdateSubtitleConverter +import com.tangem.utils.transformer.Transformer + +internal class SwapAmountChangeAmountTypeTransformer( + private val selectedAmountType: SwapAmountType, + private val swapRateType: ExpressRateType, + private val isBalanceHidden: Boolean, +) : Transformer { + + override fun transform(prevState: SwapAmountUM): SwapAmountUM { + if (prevState !is SwapAmountUM.Content) return prevState + + val subtitleConverter = SwapAmountUpdateSubtitleConverter( + selectedAmountType = selectedAmountType, + isBalanceHidden = isBalanceHidden, + ) + + val newPrimaryAmount = (prevState.primaryAmount as? SwapAmountFieldUM.Content)?.let { field -> + subtitleConverter.updateSubtitles( + field = field, + cryptoCurrencyStatus = prevState.primaryCryptoCurrencyStatus, + isAmountEmpty = true, + ) + } ?: prevState.primaryAmount + + val newSecondaryAmount = if (prevState.secondaryCryptoCurrencyStatus != null) { + (prevState.secondaryAmount as? SwapAmountFieldUM.Content)?.let { field -> + subtitleConverter.updateSubtitles( + field = field, + cryptoCurrencyStatus = prevState.secondaryCryptoCurrencyStatus, + isAmountEmpty = true, + ) + } ?: prevState.secondaryAmount + } else { + prevState.secondaryAmount + } + + return prevState.copy( + selectedAmountType = selectedAmountType, + swapRateType = swapRateType, + primaryAmount = newPrimaryAmount, + secondaryAmount = newSecondaryAmount, + ) + } +} \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt index ecd9813de0..580b1c629a 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/SwapNotificationsComponent.kt @@ -6,6 +6,7 @@ import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.domain.express.models.ExpressError +import com.tangem.domain.express.models.ExpressProvider import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWalletId @@ -51,6 +52,7 @@ internal class SwapNotificationsComponent( val enteredFromAmount: BigDecimal? = null, val fromCryptoCurrencyStatus: CryptoCurrencyStatus? = null, val priceImpact: PriceImpact? = null, + val provider: ExpressProvider? = null, ) } } \ No newline at end of file diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt index 7a265478bb..f69bf413d1 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/notifications/model/SwapNotificationsModel.kt @@ -1,15 +1,14 @@ package com.tangem.features.swap.v2.impl.notifications.model -import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.format.bigdecimal.crypto import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.express.models.ExpressError -import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase -import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.domain.transaction.usecase.IsMemoRequiredUseCase import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger import com.tangem.features.swap.v2.impl.amount.entity.PriceImpact import com.tangem.features.swap.v2.impl.notifications.DefaultSwapNotificationsUpdateTrigger @@ -17,6 +16,7 @@ import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent.Params.SwapNotificationData import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateListener import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM +import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -26,16 +26,17 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import java.math.BigDecimal import javax.inject.Inject +@Suppress("LongParameterList") @ModelScoped internal class SwapNotificationsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener, private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger, private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, - private val validateTransactionUseCase: ValidateTransactionUseCase, + private val isMemoRequiredUseCase: IsMemoRequiredUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, paramsContainer: ParamsContainer, ) : Model() { @@ -80,24 +81,50 @@ internal class SwapNotificationsModel @Inject constructor( .isNotEmpty() swapNotificationsUpdateTrigger.callbackHasError(hasErrorNotification) uiState.value = notifications.toImmutableList() + + val fromCurrency = notificationData.fromCryptoCurrency + val toCurrency = notificationData.toCryptoCurrencyStatus?.currency + val provider = notificationData.provider + if (fromCurrency != null && toCurrency != null && provider != null) { + if (notifications.any { it is SwapNotificationUM.Warning.HighPriceImpact }) { + analyticsEventHandler.send( + SendWithSwapAnalyticEvents.HighPriceImpact( + sendToken = fromCurrency.symbol, + receiveToken = toCurrency.symbol, + sendBlockchain = fromCurrency.network.name, + receiveBlockchain = toCurrency.network.name, + providerName = provider.name, + ), + ) + } + if (notifications.any { it is SwapNotificationUM.Warning.TradeTooHigh }) { + analyticsEventHandler.send( + SendWithSwapAnalyticEvents.TradeTooLarge( + sendToken = fromCurrency.symbol, + receiveToken = toCurrency.symbol, + sendBlockchain = fromCurrency.network.name, + receiveBlockchain = toCurrency.network.name, + providerName = provider.name, + ), + ) + } + } } private suspend fun MutableList.addDestinationTagRequiredNotification() { val toCryptoCurrencyStatus = notificationData.toCryptoCurrencyStatus ?: return - val userWalletId = notificationData.userWalletId ?: return val destinationAddress = notificationData.destinationAddress if (destinationAddress.isEmpty()) return - val validationError = validateTransactionUseCase( - amount = BigDecimal.ZERO.convertToSdkAmount(toCryptoCurrencyStatus), - fee = null, - memo = notificationData.memo, - destination = destinationAddress, - userWalletId = userWalletId, - network = toCryptoCurrencyStatus.currency.network, - ).leftOrNull() - - if (validationError is BlockchainSdkError.DestinationTagRequired) { + val isMemoRequired = if (notificationData.memo.isNullOrEmpty()) { + isMemoRequiredUseCase( + network = toCryptoCurrencyStatus.currency.network, + destinationAddress = destinationAddress, + ) + } else { + false + } + if (isMemoRequired) { add(NotificationUM.Error.DestinationTagRequired) } } diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt index fb20d25a67..f753ef6f26 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/analytics/SendWithSwapAnalyticEvents.kt @@ -133,6 +133,41 @@ internal sealed class SendWithSwapAnalyticEvents( ), ) + class HighPriceImpact( + val sendToken: String, + val receiveToken: String, + val sendBlockchain: String, + val receiveBlockchain: String, + val providerName: String, + ) : SendWithSwapAnalyticEvents( + event = "Notice - High price impact", + params = mapOf( + SEND_TOKEN to sendToken, + RECEIVE_TOKEN to receiveToken, + "Send Blockchain" to sendBlockchain, + "Receive Blockchain" to receiveBlockchain, + PROVIDER to providerName, + ), + ) + + class TradeTooLarge( + val sendToken: String, + val receiveToken: String, + val sendBlockchain: String, + val receiveBlockchain: String, + val providerName: String, + + ) : SendWithSwapAnalyticEvents( + event = "Notice - Trade too large", + params = mapOf( + SEND_TOKEN to sendToken, + RECEIVE_TOKEN to receiveToken, + "Send Blockchain" to sendBlockchain, + "Receive Blockchain" to receiveBlockchain, + PROVIDER to providerName, + ), + ) + enum class ErrorScreen { Amount, Confirm, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt index d5728b155e..8e24591f1f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/SendWithSwapConfirmComponent.kt @@ -11,7 +11,6 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.appcurrency.model.AppCurrency 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.UserWallet import com.tangem.domain.swap.models.SwapDirection @@ -113,10 +112,11 @@ internal class SendWithSwapConfirmComponent @AssistedInject constructor( appCurrency = params.appCurrency, callback = model, notificationData = SendNotificationsComponent.Params.NotificationData( - destinationAddress = when (val currency = model.primaryCurrencyStatus.currency) { - is CryptoCurrency.Token -> currency.contractAddress - is CryptoCurrency.Coin -> "0" - }, + /** + * Null when destination is unknown at this point (e.g. CEX swap — address is only known + * after receiving exchange-data). For DEX / DEX_BRIDGE, the address is known upfront. + */ + destinationAddress = null, memo = null, amountValue = model.confirmData.enteredFromAmount.orZero(), reduceAmountBy = model.confirmData.reduceAmountBy.orZero(), diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt index 2359164b49..3c71e14050 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SendWithSwapConfirmModel.kt @@ -436,10 +436,11 @@ internal class SendWithSwapConfirmModel @Inject constructor( feeUMV2?.feeExtraInfo?.feeCryptoCurrencyStatus ?: params.primaryFeePaidCurrencyStatusFlow.value sendNotificationsUpdateTrigger.triggerUpdate( data = NotificationData( - destinationAddress = when (val currency = primaryCurrencyStatus.currency) { - is CryptoCurrency.Token -> currency.contractAddress - is CryptoCurrency.Coin -> "0" - }, + /** + * Null when destination is unknown at this point (e.g. CEX swap — address is only known + * after receiving exchange-data). For DEX / DEX_BRIDGE, the address is known upfront. + */ + destinationAddress = null, memo = null, amountValue = confirmData.enteredFromAmount.orZero(), reduceAmountBy = confirmData.reduceAmountBy, @@ -460,6 +461,7 @@ internal class SendWithSwapConfirmModel @Inject constructor( enteredFromAmount = confirmData.enteredFromAmount, fromCryptoCurrencyStatus = confirmData.fromCryptoCurrencyStatus, priceImpact = confirmData.priceImpact, + provider = confirmData.quote?.provider, ), ) uiState.transformerUpdate( diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt index 080ab91574..9f9d5ff8da 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/model/SwapTransactionSender.kt @@ -91,7 +91,7 @@ internal class SwapTransactionSender @AssistedInject constructor( val feeValue = confirmData.fee?.amount?.value ?: return val destination = confirmData.enteredDestination ?: return - val (amount, currencyStatus) = when (confirmData.amountType) { + val (swapDataRequestAmount, swapDataRequestCurrency) = when (confirmData.amountType) { SwapAmountType.From -> { val amountValue = confirmData.enteredFromAmount ?: return val subtracted = FeeCalculationUtils.checkAndCalculateSubtractedAmount( @@ -109,10 +109,15 @@ internal class SwapTransactionSender @AssistedInject constructor( } } + val fromTransactionAmount = when (confirmData.amountType) { + SwapAmountType.From -> swapDataRequestAmount + SwapAmountType.To -> confirmData.enteredFromAmount ?: return + } + val swapData = getSwapDataUseCase( userWallet = userWallet, fromCryptoCurrencyStatus = fromStatus, - amount = amount.toStringWithRightOffset(currencyStatus.currency.decimals), + amount = swapDataRequestAmount.toStringWithRightOffset(swapDataRequestCurrency.currency.decimals), amountType = confirmData.amountType, toCryptoCurrency = toStatus.currency, toAddress = destination, @@ -124,7 +129,7 @@ internal class SwapTransactionSender @AssistedInject constructor( ).getOrElse { error -> onExpressError(error); return } createAndSendCexTransaction( - fromAmount = amount, + fromAmount = fromTransactionAmount, fromStatus = fromStatus, fromAccount = fromAccount, toStatus = toStatus, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt index 1688c9b88a..7005c6acdf 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/confirm/ui/SendWithSwapConfirmContent.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.unit.dp import com.tangem.common.ui.notifications.NotificationUM import com.tangem.common.ui.notifications.notifications import com.tangem.core.ui.components.SpacerH16 -import com.tangem.core.ui.extensions.* import com.tangem.features.send.v2.api.FeeSelectorBlockComponent import com.tangem.features.send.v2.api.SendNotificationsComponent import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationBlockComponent diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt index 8f73c36ee7..344a698f34 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/AllowPermissionsHandlerImpl.kt @@ -1,9 +1,11 @@ package com.tangem.feature.swap.domain +import java.util.Collections.synchronizedSet + class AllowPermissionsHandlerImpl : AllowPermissionsHandler { // todo maybe need to save in store - private val allowPermissionsInProgress = mutableSetOf() + private val allowPermissionsInProgress = synchronizedSet(mutableSetOf()) override fun addAddressToInProgress(tokenAddress: String) { allowPermissionsInProgress.add(tokenAddress) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index b90ffae536..9365e7893f 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -72,11 +72,7 @@ import com.tangem.utils.logging.TangemLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.* import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode @@ -1301,7 +1297,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) ?.filterIsInstance() ?.firstOrNull { it.network.id == network.id && it.network.derivationPath == network.derivationPath } - ?: error("Unable to create network coin with ID: ${network.id}") + ?: currenciesRepository.createCoinCurrency(network) } private suspend fun createEmptyAmountState(): SwapState { diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index ab15afbae5..26b53125e0 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -12,6 +12,7 @@ import javax.inject.Singleton internal class SwapDomainModule { @Provides + @Singleton fun provideAllowPermissionsHandler(): AllowPermissionsHandler { return AllowPermissionsHandlerImpl() } diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 47be410214..288c865b8e 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -75,6 +75,7 @@ import com.tangem.feature.swap.analytics.SwapEvents import com.tangem.feature.swap.choosetoken.api.ChooseTokenBridge import com.tangem.feature.swap.component.SwapFeeSelectorBlockComponent import com.tangem.feature.swap.converters.SwapTransactionErrorStateConverter +import com.tangem.feature.swap.domain.AllowPermissionsHandler import com.tangem.feature.swap.domain.SwapInteractor import com.tangem.feature.swap.domain.TransactionFeeResult import com.tangem.feature.swap.domain.TxFeeSealedState @@ -89,6 +90,7 @@ import com.tangem.feature.swap.router.SwapNavScreen import com.tangem.feature.swap.router.SwapRouter import com.tangem.feature.swap.ui.StateBuilder import com.tangem.feature.swap.utils.formatToUIRepresentation +import com.tangem.feature.swap.utils.getContractAddress import com.tangem.features.approval.api.GiveApprovalComponent import com.tangem.features.approval.api.GiveApprovalFeatureToggles import com.tangem.features.send.v2.api.entity.FeeSelectorUM @@ -146,6 +148,7 @@ internal class SwapModel @Inject constructor( private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles, private val messageSender: UiMessageSender, private val paymentAccountCryptoCurrencyStatusUseCase: GetPaymentAccountCryptoCurrencyStatusUseCase, + private val allowPermissionsHandler: AllowPermissionsHandler, chooseTokenBridgeFactory: ChooseTokenBridge.Factory, giveApprovalFeatureToggles: GiveApprovalFeatureToggles, ) : Model() { @@ -260,6 +263,10 @@ internal class SwapModel @Inject constructor( } override fun onApproveDone() { + val fromContractAddress = dataState.fromCryptoCurrency?.currency?.getContractAddress() + if (fromContractAddress != null) { + allowPermissionsHandler.addAddressToInProgress(fromContractAddress) + } approvalSlotNavigation.dismiss() updateWalletBalance() uiState = stateBuilder.loadingPermissionState(uiState) @@ -812,8 +819,8 @@ internal class SwapModel @Inject constructor( analyticsEventHandler.send( SwapEvents.HighPriceImpact( sendToken = fromToken.currency.symbol, - receiveToken = toToken.currency.network.name, - sendBlockchain = fromToken.currency.symbol, + receiveToken = toToken.currency.symbol, + sendBlockchain = fromToken.currency.network.name, receiveBlockchain = toToken.currency.network.name, providerName = provider.name, ), @@ -823,8 +830,8 @@ internal class SwapModel @Inject constructor( analyticsEventHandler.send( SwapEvents.TradeTooLarge( sendToken = fromToken.currency.symbol, - receiveToken = toToken.currency.network.name, - sendBlockchain = fromToken.currency.symbol, + receiveToken = toToken.currency.symbol, + sendBlockchain = fromToken.currency.network.name, receiveBlockchain = toToken.currency.network.name, providerName = provider.name, ), diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt index a0ef72bd20..3093dcb07b 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/utils/SwapUtils.kt @@ -5,6 +5,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.simple +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount import com.tangem.feature.swap.presentation.R @@ -52,4 +53,11 @@ internal fun getExpressErrorTitle(expressDataError: ExpressDataError): TextRefer internal fun SwapAmount.formatToUIRepresentation(): String { return value.format { simple(decimals = decimals) } +} + +internal fun CryptoCurrency.getContractAddress(): String { + return when (this) { + is CryptoCurrency.Token -> this.contractAddress + is CryptoCurrency.Coin -> "0" + } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt index 2bc39f10d0..df07d56fe8 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/accounts/viewmodel/TesterAccountsViewModel.kt @@ -204,7 +204,7 @@ internal class TesterAccountsViewModel @Inject constructor( var nextIndex = accountList.totalAccounts @Suppress("LoopWithTooManyJumpStatements") // never mind for Tester Menu - while (accountList.canAddMoreAccounts) { + while (accountList.canAddMoreCryptoAccounts) { val derivationIndex = DerivationIndex(nextIndex).getOrNull() ?: break val newAccount = Account.CryptoPortfolio.invoke( @@ -246,7 +246,7 @@ internal class TesterAccountsViewModel @Inject constructor( withContext(dispatchers.default) { val updatedAccountList = AccountList.invoke( userWalletId = accountList.userWalletId, - accounts = if (possibleToArchive > AccountList.MAX_ACCOUNTS_COUNT - 1) { + accounts = if (possibleToArchive > AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT - 1) { listOf(accountList.mainAccount) } else { accountList.accounts.subList(fromIndex = 0, toIndex = accountList.accounts.size - possibleToArchive) diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt index 56be825aca..c53c4fab07 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/AccountItemsDelegate.kt @@ -97,7 +97,7 @@ internal class AccountItemsDelegate @Inject constructor( add(header) addAll(accounts.map(::mapAccount).applySortingOrder(order = accountsOrder)) - val isAddAccountEnabled = accounts.size < AccountList.MAX_ACCOUNTS_COUNT + val isAddAccountEnabled = accounts.size < AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT val shouldShowDescription = accounts.size > 1 val isArchivedAccountsEnabled = accountStatusList.accountStatuses.size != accountStatusList.totalAccounts @@ -165,7 +165,7 @@ internal class AccountItemsDelegate @Inject constructor( title = resourceReference(R.string.account_add_limit_dialog_title), message = resourceReference( id = R.string.account_add_limit_dialog_description, - formatArgs = wrappedList(AccountList.MAX_ACCOUNTS_COUNT.toString()), + formatArgs = wrappedList(AccountList.MAX_CRYPTO_PORTFOLIO_ACCOUNTS_COUNT.toString()), ), firstActionBuilder = { firstAction }, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt index 8c1f641c38..9997dc0bcb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayHideOnboardingStateTransformer.kt @@ -4,6 +4,7 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM +import com.tangem.features.tangempay.entity.TangemPayMainUM internal class TangemPayHideOnboardingStateTransformer( userWalletId: UserWalletId, @@ -11,7 +12,7 @@ internal class TangemPayHideOnboardingStateTransformer( override fun transform(prevState: WalletState): WalletState { return if (prevState is WalletState.MultiCurrency.Content) { - prevState.copy(tangemPayState = TangemPayState.Empty) + prevState.copy(tangemPayState = TangemPayState.Empty, tangemPayMainUM = TangemPayMainUM.Empty) } else { prevState } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index 7dce45b0c8..f1841ba8e2 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1487" +tangemBlockchainSdk = "develop-1491" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-602" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt index 8383c43b2e..3de6c03bd1 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt @@ -24,6 +24,7 @@ internal class WalletManagerFactoryCreator @Inject constructor( private val blockchainDataStorage: BlockchainDataStorage, private val blockchainSDKLogger: BlockchainSDKLogger, private val isSolanaTxHistoryEnabled: Boolean, + private val isSolanaScaledUiAmountEnabled: Boolean, ) { fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory { @@ -37,6 +38,7 @@ internal class WalletManagerFactoryCreator @Inject constructor( isYieldSupplyEnabled = true, isPendingTransactionsEnabled = true, isSolanaTxHistoryEnabled = isSolanaTxHistoryEnabled, + isSolanaScaledUiAmountEnabled = isSolanaScaledUiAmountEnabled, ), blockchainDataStorage = blockchainDataStorage, loggers = listOf(blockchainSDKLogger), diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index fbcf86e62b..9bdbd36f05 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -100,6 +100,9 @@ internal object BlockchainSDKFactoryModule { isSolanaTxHistoryEnabled = featureTogglesManager.isFeatureEnabled( FeatureToggles.SOLANA_TX_HISTORY_ENABLED, ), + isSolanaScaledUiAmountEnabled = featureTogglesManager.isFeatureEnabled( + FeatureToggles.SOLANA_SCALED_UI_AMOUNT_ENABLED, + ), ) } } \ No newline at end of file