Updated on 2026-08-14
This commit is contained in:
commit
5705579468
65 changed files with 759 additions and 294 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit 1c95c2ee9aaa0007ea203b1bd9477e874f8211be
|
||||
Subproject commit 498b0bcd0d871ed60c43b5d44f646548a4f11d37
|
||||
|
|
@ -6,7 +6,6 @@ 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.feature.referral.data.ExternalReferralRepository
|
||||
|
|
@ -91,14 +90,6 @@ internal object AccountDomainModule {
|
|||
return GetUnoccupiedAccountIndexUseCase(crudRepository = accountsCRUDRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsAccountsModeEnabledUseCase(
|
||||
multiAccountListSupplier: MultiAccountListSupplier,
|
||||
): IsAccountsModeEnabledUseCase {
|
||||
return IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApplyAccountListSortingUseCase(
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ object TangemBlogUrlBuilder {
|
|||
}
|
||||
|
||||
data object SeedPhraseRiskySolution : Post {
|
||||
override val path: String = "seed-phrase-a-risky-solution"
|
||||
override val path: String = "seed-phrase-faq"
|
||||
}
|
||||
|
||||
data object WhatWalletToChoose : Post {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@
|
|||
"name": "SOLANA_SCALED_UI_AMOUNT_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "HEDERA_ERC20_ENABLED",
|
||||
"version": "5.37"
|
||||
},
|
||||
{
|
||||
"name": "ADD_AND_MANAGE_TOKENS_ENABLED",
|
||||
"version": "undefined"
|
||||
|
|
|
|||
|
|
@ -6,5 +6,13 @@ import com.squareup.moshi.JsonClass
|
|||
@JsonClass(generateAdapter = true)
|
||||
data class DismissPromoBannerRequest(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "isDismissed") val isDismissed: Boolean,
|
||||
)
|
||||
@Json(name = "status") val status: BannerDisplayStatus,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = false)
|
||||
enum class BannerDisplayStatus {
|
||||
@Json(name = "active") ACTIVE,
|
||||
|
||||
@Json(name = "dismissed") DISMISSED,
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +109,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
|
||||
private suspend fun fetchTangemPayAccountStatus(account: Account.Payment): PaymentAccountStatusValue {
|
||||
val prevResult = paymentAccountStatusesStore.getSyncOrNull(account.userWalletId)
|
||||
if (prevResult == null || prevResult.value is PaymentAccountStatusValue.Error) {
|
||||
if (prevResult == null || prevResult.value is PaymentAccountStatusValue.Error.Unavailable) {
|
||||
paymentAccountStatusesStore.store(
|
||||
userWalletId = account.userWalletId,
|
||||
status = AccountStatus.Payment(account = account, value = PaymentAccountStatusValue.Loading),
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
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 any [AccountList] produced for the user's wallets contains at least two
|
||||
* active accounts.
|
||||
*
|
||||
* @property multiAccountListSupplier supplier that provides a list of [AccountList]s for all user wallets
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class IsAccountsModeEnabledUseCase(
|
||||
private val multiAccountListSupplier: MultiAccountListSupplier,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> {
|
||||
return multiAccountListSupplier.invoke()
|
||||
.map { accountsList -> accountsList.map(AccountList::activeAccounts).isModeEnabled() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
suspend fun invokeSync(): Boolean {
|
||||
return multiAccountListSupplier.getSyncOrNull(Unit)
|
||||
?.map(AccountList::activeAccounts)
|
||||
?.isModeEnabled() == true
|
||||
}
|
||||
|
||||
private fun List<Int>.isModeEnabled(): Boolean = any { it >= 2 }
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
package com.tangem.domain.account.usecase
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
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
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class IsAccountsModeEnabledUseCaseTest {
|
||||
|
||||
private val multiAccountListSupplier: MultiAccountListSupplier = mockk()
|
||||
|
||||
private val useCase = IsAccountsModeEnabledUseCase(multiAccountListSupplier = multiAccountListSupplier)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(multiAccountListSupplier)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Invoke {
|
||||
|
||||
@Test
|
||||
fun `returns false when supplier emits empty list`() = runTest {
|
||||
// Arrange
|
||||
every { multiAccountListSupplier.invoke() } returns flowOf(emptyList())
|
||||
|
||||
// Act
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when supplier emits account list with one account`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 1)
|
||||
every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList))
|
||||
|
||||
// Act
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when supplier emits account list with two accounts`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 2)
|
||||
every { multiAccountListSupplier.invoke() } returns flowOf(listOf(accountList))
|
||||
|
||||
// Act
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@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))
|
||||
|
||||
// Act
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class InvokeSync {
|
||||
|
||||
@Test
|
||||
fun `returns false when getSyncOrNull returns null`() = runTest {
|
||||
// Arrange
|
||||
coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns null
|
||||
|
||||
// Act
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when getSyncOrNull returns empty list`() = runTest {
|
||||
// Arrange
|
||||
coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns emptyList()
|
||||
|
||||
// Act
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when getSyncOrNull returns account list with one account`() = runTest {
|
||||
// Arrange
|
||||
val accountList = createAccountList(activeAccounts = 1)
|
||||
coEvery { multiAccountListSupplier.getSyncOrNull(Unit, any()) } returns listOf(accountList)
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when getSyncOrNull returns multiple account lists, one with two accounts`() = runTest {
|
||||
// Arrange
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAccountList(activeAccounts: Int): AccountList = mockk {
|
||||
every { this@mockk.activeAccounts } returns activeAccounts
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,14 @@ internal object AccountStatusUseCaseModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideIsAccountsModeEnabledUseCase(
|
||||
multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
): IsAccountsModeEnabledUseCase {
|
||||
return IsAccountsModeEnabledUseCase(multiAccountStatusListSupplier = multiAccountStatusListSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrencyActionsUseCaseV2(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
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 any [com.tangem.domain.account.models.AccountStatusList] produced for the
|
||||
* user's wallets has more than one [AccountStatus.CryptoPortfolio], or has a [AccountStatus.Payment] with any
|
||||
|
||||
*
|
||||
* @property multiAccountStatusListSupplier supplier that provides a list of
|
||||
* [com.tangem.domain.account.models.AccountStatusList]s for all user wallets
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class IsAccountsModeEnabledUseCase(
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier,
|
||||
) {
|
||||
|
||||
operator fun invoke(): Flow<Boolean> {
|
||||
return multiAccountStatusListSupplier.invoke()
|
||||
.map { accountStatusLists -> accountStatusLists.any { it.accountStatuses.isModeEnabled() } }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
suspend fun invokeSync(): Boolean {
|
||||
return multiAccountStatusListSupplier.getSyncOrNull(Unit)
|
||||
?.any { it.accountStatuses.isModeEnabled() } == true
|
||||
}
|
||||
|
||||
private fun List<AccountStatus>.isModeEnabled(): Boolean {
|
||||
var cryptoPortfolioCount = 0
|
||||
for (status in this) {
|
||||
when {
|
||||
status is AccountStatus.CryptoPortfolio && ++cryptoPortfolioCount > 1 -> return true
|
||||
status is AccountStatus.Payment && status.value.isActivePayment() -> return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun PaymentAccountStatusValue.isActivePayment(): Boolean {
|
||||
return this !is PaymentAccountStatusValue.NotCreated
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.account.models.AccountExpandedState
|
|||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.repository.AccountsExpandedRepository
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,272 @@
|
|||
package com.tangem.domain.account.status.usecase
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
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
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class IsAccountsModeEnabledUseCaseTest {
|
||||
|
||||
private val multiAccountStatusListSupplier: MultiAccountStatusListSupplier = mockk()
|
||||
|
||||
private val useCase = IsAccountsModeEnabledUseCase(multiAccountStatusListSupplier = multiAccountStatusListSupplier)
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(multiAccountStatusListSupplier)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Invoke {
|
||||
|
||||
@Test
|
||||
fun `returns false when supplier emits empty list`() = runTest {
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(emptyList())
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when single crypto portfolio account`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio()),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when two crypto portfolio accounts`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockCryptoPortfolio()),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is Loaded`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.Loaded>())),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is Locked`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.Locked>())),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when payment account is NotCreated`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(PaymentAccountStatusValue.NotCreated)),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is UnderReview`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.UnderReview>())),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is IssuingCard`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.IssuingCard>())),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is Loading`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(PaymentAccountStatusValue.Loading)),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when multiple status lists and one has two crypto portfolios`() = runTest {
|
||||
val statusList1 = createAccountStatusList(statuses = listOf(mockCryptoPortfolio()))
|
||||
val statusList2 = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockCryptoPortfolio()),
|
||||
)
|
||||
every { multiAccountStatusListSupplier.invoke() } returns flowOf(listOf(statusList1, statusList2))
|
||||
|
||||
val actual = useCase.invoke().first()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class InvokeSync {
|
||||
|
||||
@Test
|
||||
fun `returns false when getSyncOrNull returns null`() = runTest {
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns null
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when getSyncOrNull returns empty list`() = runTest {
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns emptyList()
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when single crypto portfolio account`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio()),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when two crypto portfolio accounts`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockCryptoPortfolio()),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is Loaded`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.Loaded>())),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is Locked`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.Locked>())),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns false when payment account is NotCreated`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(PaymentAccountStatusValue.NotCreated)),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when payment account is UnderReview`() = runTest {
|
||||
val statusList = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.UnderReview>())),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns true when multiple status lists and one has loaded payment`() = runTest {
|
||||
val statusList1 = createAccountStatusList(statuses = listOf(mockCryptoPortfolio()))
|
||||
val statusList2 = createAccountStatusList(
|
||||
statuses = listOf(mockCryptoPortfolio(), mockPayment(mockk<PaymentAccountStatusValue.Loaded>())),
|
||||
)
|
||||
coEvery { multiAccountStatusListSupplier.getSyncOrNull(Unit, any()) } returns listOf(statusList1, statusList2)
|
||||
|
||||
val actual = useCase.invokeSync()
|
||||
|
||||
Truth.assertThat(actual).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
private fun mockCryptoPortfolio(): AccountStatus.CryptoPortfolio = mockk()
|
||||
|
||||
private fun mockPayment(value: PaymentAccountStatusValue): AccountStatus.Payment = mockk {
|
||||
every { this@mockk.value } returns value
|
||||
}
|
||||
|
||||
private fun createAccountStatusList(statuses: List<AccountStatus>): AccountStatusList = mockk {
|
||||
every { accountStatuses } returns statuses
|
||||
}
|
||||
}
|
||||
|
|
@ -32,9 +32,11 @@ class AssociateAssetUseCase(
|
|||
val networkCoin = multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWallet.walletId),
|
||||
)
|
||||
?.firstOrNull {
|
||||
?.firstOrNull { cryptoCurrency ->
|
||||
val network = currency.network
|
||||
it.network.id == network.id && it.network.derivationPath == network.derivationPath
|
||||
cryptoCurrency is CryptoCurrency.Coin &&
|
||||
cryptoCurrency.network.id == network.id &&
|
||||
cryptoCurrency.network.derivationPath == network.derivationPath
|
||||
}
|
||||
?: error("Unable to create network coin for currencyID: ${currency.id}")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.features.account.selector
|
||||
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.filterCryptoPortfolio
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.features.feed.components.feed
|
|||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -44,6 +45,7 @@ internal class DefaultFeedComponent(
|
|||
context = child("promoBannersBlockComponent"),
|
||||
params = PromoBannersBlockComponent.Params(
|
||||
placeholder = PromoBannersBlockComponent.Placeholder.FEED,
|
||||
isInitiallyVisibleOnScreen = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -70,6 +72,11 @@ internal class DefaultFeedComponent(
|
|||
contentPadding: PaddingValues,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val isExpanded = bottomSheetState.value == BottomSheetState.EXPANDED
|
||||
LaunchedEffect(isExpanded) {
|
||||
promoBannersBlockComponent?.setVisibleOnScreen(isExpanded)
|
||||
}
|
||||
|
||||
LifecycleStartEffect(Unit) {
|
||||
feedComponentModel.isVisibleOnScreen.value = true
|
||||
onStopOrDispose {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -130,7 +130,10 @@ private fun FeedListContent(
|
|||
feedListCallbacks = state.feedListCallbacks,
|
||||
)
|
||||
|
||||
promoBannersBlockComponent?.Content(modifier = Modifier.padding(horizontal = 16.dp))
|
||||
promoBannersBlockComponent?.Content(
|
||||
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp),
|
||||
)
|
||||
SpacerH(32.dp)
|
||||
|
||||
NewsBlock(
|
||||
news = state.news,
|
||||
|
|
|
|||
|
|
@ -78,8 +78,6 @@ internal fun MarketBlock(marketChart: MarketChartUM?, feedListCallbacks: FeedLis
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
marketChart = currentChart,
|
||||
)
|
||||
|
||||
SpacerH(32.dp)
|
||||
}
|
||||
}
|
||||
null -> Unit
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ dependencies {
|
|||
implementation(projects.core.datasource)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
implementation(projects.domain.appCurrency)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfi
|
|||
import com.tangem.core.ui.components.fields.InputManager
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
import com.tangem.domain.nft.RefreshAllNFTUseCase
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.account.producer.SingleAccountProducer
|
||||
import com.tangem.domain.account.supplier.SingleAccountSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.TokenReceiveConfig
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.iconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.features.onramp.hottokens.portfolio.OnrampAddTokenComponent
|
||||
import com.tangem.features.onramp.hottokens.portfolio.OnrampAddTokenComponent.AddHotCryptoData
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.features.onramp.component.SwapSelectTokensComponent
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import com.tangem.core.ui.decompose.ComposableContentComponent
|
|||
|
||||
interface PromoBannersBlockComponent : ComposableContentComponent {
|
||||
|
||||
fun setVisibleOnScreen(isVisible: Boolean)
|
||||
|
||||
data class Params(
|
||||
val placeholder: Placeholder,
|
||||
val isInitiallyVisibleOnScreen: Boolean = true,
|
||||
)
|
||||
|
||||
enum class Placeholder {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ internal class DefaultPromoBannersBlockComponent @AssistedInject constructor(
|
|||
|
||||
private val model: PromoBannersBlockModel = getOrCreateModel(params)
|
||||
|
||||
override fun setVisibleOnScreen(isVisible: Boolean) {
|
||||
model.setVisibleOnScreen(isVisible)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.uiState.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ internal class PromoBannersBlockModel @Inject constructor(
|
|||
|
||||
private val placeholder: String = params.placeholder.name.lowercase()
|
||||
private val shownBannerIds: MutableSet<Int> = ConcurrentHashMap.newKeySet()
|
||||
private var isVisibleOnScreen: Boolean = params.isInitiallyVisibleOnScreen
|
||||
private var wasCarouselScrolled = false
|
||||
private val savedDisplayIdByWalletId: MutableMap<String, Int> = mutableMapOf()
|
||||
|
||||
|
|
@ -46,6 +47,11 @@ internal class PromoBannersBlockModel @Inject constructor(
|
|||
subscribeOnSelectedWallet()
|
||||
}
|
||||
|
||||
fun setVisibleOnScreen(visible: Boolean) {
|
||||
isVisibleOnScreen = visible
|
||||
uiState.update { it.copy(isVisibleOnScreen = visible) }
|
||||
}
|
||||
|
||||
private fun subscribeOnSelectedWallet() {
|
||||
modelScope.launch {
|
||||
userWalletsListRepository.selectedUserWallet
|
||||
|
|
@ -53,7 +59,6 @@ internal class PromoBannersBlockModel @Inject constructor(
|
|||
.map { it.walletId.stringValue }
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
shownBannerIds.clear()
|
||||
wasCarouselScrolled = false
|
||||
}
|
||||
.collectLatest { walletId -> loadBanners(walletId) }
|
||||
|
|
@ -85,6 +90,8 @@ internal class PromoBannersBlockModel @Inject constructor(
|
|||
userWalletId = walletId,
|
||||
initialPage = initialPage,
|
||||
banners = bannerUMs,
|
||||
isVisibleOnScreen = isVisibleOnScreen,
|
||||
placeholder = params.placeholder,
|
||||
onBannerShown = ::onBannerShown,
|
||||
onCarouselScrolled = ::onCarouselScrolled,
|
||||
onPageChanged = { displayId -> savedDisplayIdByWalletId[walletId] = displayId },
|
||||
|
|
@ -116,6 +123,8 @@ internal class PromoBannersBlockModel @Inject constructor(
|
|||
userWalletId = "",
|
||||
initialPage = 0,
|
||||
banners = persistentListOf(),
|
||||
isVisibleOnScreen = isVisibleOnScreen,
|
||||
placeholder = params.placeholder,
|
||||
onBannerShown = {},
|
||||
onCarouselScrolled = {},
|
||||
onPageChanged = {},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class PromoBannersBlockUM(
|
||||
val userWalletId: String,
|
||||
val initialPage: Int,
|
||||
val banners: ImmutableList<PromoBannerNotificationUM>,
|
||||
val isVisibleOnScreen: Boolean,
|
||||
val placeholder: PromoBannersBlockComponent.Placeholder,
|
||||
val onBannerShown: (displayId: Int) -> Unit,
|
||||
val onCarouselScrolled: (displayId: Int) -> Unit,
|
||||
val onPageChanged: (displayId: Int) -> Unit,
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ internal class DefaultPromoBannersRepository(
|
|||
withContext(dispatchers.io) {
|
||||
val request = DismissPromoBannerRequest(
|
||||
walletId = walletId,
|
||||
isDismissed = true,
|
||||
status = DismissPromoBannerRequest.BannerDisplayStatus.DISMISSED,
|
||||
)
|
||||
tangemTechApi.dismissPromoBannerDisplay(displayId, request).getOrThrow()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import androidx.compose.runtime.key
|
|||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.util.lerp
|
||||
import com.tangem.core.ui.components.SpacerH8
|
||||
import com.tangem.core.ui.components.notifications.Notification
|
||||
import com.tangem.core.ui.components.pager.PagerIndicator
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.promobanners.api.PromoBannersBlockComponent.Placeholder
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerNotificationUM
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannersBlockUM
|
||||
import kotlin.math.ceil
|
||||
|
|
@ -26,23 +28,25 @@ import kotlin.math.floor
|
|||
internal fun PromoBannersBlock(state: PromoBannersBlockUM, modifier: Modifier = Modifier) {
|
||||
if (state.banners.isEmpty()) return
|
||||
|
||||
val containerColor = bannerContainerColor(state.placeholder)
|
||||
|
||||
if (state.banners.size == 1) {
|
||||
val banner = state.banners.first()
|
||||
LaunchedEffect(banner.displayId) {
|
||||
state.onBannerShown(banner.displayId)
|
||||
LaunchedEffect(banner.displayId, state.isVisibleOnScreen) {
|
||||
if (state.isVisibleOnScreen) {
|
||||
state.onBannerShown(banner.displayId)
|
||||
}
|
||||
}
|
||||
SingleBanner(
|
||||
banner = banner,
|
||||
containerColor = containerColor,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
key(state.userWalletId) {
|
||||
BannersCarousel(
|
||||
banners = state.banners,
|
||||
initialPage = state.initialPage,
|
||||
onBannerShown = state.onBannerShown,
|
||||
onCarouselScroll = state.onCarouselScrolled,
|
||||
onPageChange = state.onPageChanged,
|
||||
state = state,
|
||||
containerColor = containerColor,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
|
@ -50,40 +54,40 @@ internal fun PromoBannersBlock(state: PromoBannersBlockUM, modifier: Modifier =
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleBanner(banner: PromoBannerNotificationUM, modifier: Modifier = Modifier) {
|
||||
private fun bannerContainerColor(placeholder: Placeholder): Color = when (placeholder) {
|
||||
Placeholder.MAIN -> TangemTheme.colors.background.primary
|
||||
Placeholder.FEED -> TangemTheme.colors.background.action
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SingleBanner(banner: PromoBannerNotificationUM, containerColor: Color, modifier: Modifier = Modifier) {
|
||||
Notification(
|
||||
config = banner.config,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BannersCarousel(
|
||||
banners: List<PromoBannerNotificationUM>,
|
||||
initialPage: Int,
|
||||
onBannerShown: (Int) -> Unit,
|
||||
onCarouselScroll: (Int) -> Unit,
|
||||
onPageChange: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
private fun BannersCarousel(state: PromoBannersBlockUM, containerColor: Color, modifier: Modifier = Modifier) {
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = initialPage,
|
||||
pageCount = { banners.size },
|
||||
initialPage = state.initialPage,
|
||||
pageCount = { state.banners.size },
|
||||
)
|
||||
|
||||
LaunchedEffect(pagerState, banners) {
|
||||
LaunchedEffect(pagerState, state.banners, state.isVisibleOnScreen) {
|
||||
if (!state.isVisibleOnScreen) return@LaunchedEffect
|
||||
var previousPage = pagerState.currentPage
|
||||
snapshotFlow { pagerState.currentPage }
|
||||
.collect { page ->
|
||||
banners.getOrNull(page)?.let { banner ->
|
||||
onPageChange(banner.displayId)
|
||||
onBannerShown(banner.displayId)
|
||||
if (page == 1) {
|
||||
// one-time event when user scrolls from first to second page,
|
||||
// indicating that he has interacted with the carousel
|
||||
onCarouselScroll(banner.displayId)
|
||||
state.banners.getOrNull(page)?.let { banner ->
|
||||
state.onPageChanged(banner.displayId)
|
||||
state.onBannerShown(banner.displayId)
|
||||
if (previousPage == 0 && page == 1) {
|
||||
state.onCarouselScrolled(banner.displayId)
|
||||
}
|
||||
}
|
||||
previousPage = page
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,8 +96,9 @@ private fun BannersCarousel(
|
|||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SmoothHeightPager(
|
||||
banners = banners,
|
||||
banners = state.banners,
|
||||
pagerState = pagerState,
|
||||
containerColor = containerColor,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
|
|
@ -117,6 +122,7 @@ private fun BannersCarousel(
|
|||
private fun SmoothHeightPager(
|
||||
banners: List<PromoBannerNotificationUM>,
|
||||
pagerState: PagerState,
|
||||
containerColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SubcomposeLayout(modifier = modifier) { constraints ->
|
||||
|
|
@ -136,7 +142,7 @@ private fun SmoothHeightPager(
|
|||
Notification(
|
||||
config = banners[lowerPage].config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}.first().measure(pageConstraints).height
|
||||
|
||||
|
|
@ -145,7 +151,7 @@ private fun SmoothHeightPager(
|
|||
Notification(
|
||||
config = banners[upperPage].config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}.first().measure(pageConstraints).height
|
||||
} else {
|
||||
|
|
@ -164,7 +170,7 @@ private fun SmoothHeightPager(
|
|||
Notification(
|
||||
config = banner.config,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}
|
||||
}.first().measure(
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ interface SendNotificationsComponent {
|
|||
val fee: Fee?,
|
||||
val feeError: GetFeeError?,
|
||||
val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
val isReduceAmountAvailable: Boolean = true,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.utils.parseBigDecimal
|
|||
import com.tangem.core.ui.utils.parseBigDecimalOrNull
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import com.tangem.core.decompose.model.ParamsContainer
|
|||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.supplier.MultiAccountStatusListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -80,6 +80,8 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private val cryptoCurrency = params.cryptoCurrency
|
||||
private val userWalletId = params.userWalletId
|
||||
|
||||
// In "Send with swap" flow, these are addresses in the destination network (not the actual sender addresses).
|
||||
// Self-send validation must be skipped for them, so use only with params.isAllowSelfSend.
|
||||
private val senderAddresses = MutableStateFlow<List<CryptoCurrencyAddress>>(emptyList())
|
||||
|
||||
private val validationJobHolder = JobHolder()
|
||||
|
|
@ -206,7 +208,7 @@ internal class SendDestinationModel @Inject constructor(
|
|||
SendDestinationRecentListTransformer(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
senderAddress = senderAddresses.value.firstOrNull()?.address,
|
||||
isSelfSendAvailable = isSelfSendAvailable,
|
||||
isSelfSendAvailable = params.isAllowSelfSend || isSelfSendAvailable,
|
||||
destinationWalletList = destinationWalletList,
|
||||
txHistoryList = txHistoryList,
|
||||
isAccountsMode = isAccountsMode,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ internal class NotificationsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun checkIfSubtractAvailable() {
|
||||
if (!notificationData.isReduceAmountAvailable) {
|
||||
isAmountSubtractAvailable = false
|
||||
return
|
||||
}
|
||||
val feeCurrencyId = notificationData.feeCryptoCurrencyStatus.currency.id
|
||||
val fee = notificationData.fee
|
||||
isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import com.tangem.core.ui.haptic.VibratorHapticManager
|
|||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import com.tangem.core.ui.haptic.VibratorHapticManager
|
|||
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -92,4 +92,9 @@ dependencies {
|
|||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Test */
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.analytics
|
||||
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
|
||||
|
||||
internal class SwapAmountAnalyticsSender(
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
private var lastSentErrorMessage: String? = null
|
||||
|
||||
fun sendErrorIfNeeded(quotes: List<SwapQuoteUM>, selectedQuote: SwapQuoteUM?) {
|
||||
val errorMessage = resolveErrorMessage(quotes, selectedQuote)
|
||||
if (errorMessage == lastSentErrorMessage) return
|
||||
lastSentErrorMessage = errorMessage
|
||||
if (errorMessage != null) {
|
||||
analyticsEventHandler.send(
|
||||
SendWithSwapAnalyticEvents.SendWithSwapError(
|
||||
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Amount,
|
||||
message = errorMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveErrorMessage(quotes: List<SwapQuoteUM>, selectedQuote: SwapQuoteUM?): String? {
|
||||
if (quotes.isEmpty()) return SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE_NO_PROVIDERS
|
||||
val error = (selectedQuote as? SwapQuoteUM.Error)?.expressError ?: return null
|
||||
return when (error) {
|
||||
is ExpressError.AmountError.TooSmallError -> SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT
|
||||
is ExpressError.AmountError.TooBigError -> SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT
|
||||
else -> "${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=${error.code}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ import com.tangem.features.swap.v2.impl.amount.SwapAmountComponentParams
|
|||
import com.tangem.features.swap.v2.impl.amount.SwapAmountReduceListener
|
||||
import com.tangem.features.swap.v2.impl.amount.SwapAmountUpdateListener
|
||||
import com.tangem.features.swap.v2.impl.amount.analytics.SwapAmountAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.amount.analytics.SwapAmountAnalyticsSender
|
||||
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.SwapQuoteUMConverter
|
||||
|
|
@ -56,6 +57,7 @@ import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
|||
import com.tangem.features.swap.v2.impl.sendviaswap.SendWithSwapRoute
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents.NoticeFixedRate.toAnalyticsRateType
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.Debouncer
|
||||
import com.tangem.utils.coroutines.PeriodicTask
|
||||
|
|
@ -120,7 +122,8 @@ internal class SwapAmountModel @Inject constructor(
|
|||
val rateInfoNavigation: SlotNavigation<ExpressRateType> = SlotNavigation()
|
||||
|
||||
private var isShowBestRateAnimation: Boolean = false
|
||||
private var lastAmountScreenOpenedCurrencyId: CryptoCurrency.ID? = null
|
||||
private var isAmountScreenOpenedSent: Boolean = false
|
||||
private val amountAnalyticsSender = SwapAmountAnalyticsSender(analyticsEventHandler)
|
||||
|
||||
private var autoUpdateSubscriberJob: Job? = null
|
||||
private var navigationJob: Job? = null
|
||||
|
|
@ -149,10 +152,11 @@ internal class SwapAmountModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun onStart() {
|
||||
val isDelayFirst = params !is SwapAmountComponentParams.AmountBlockParams
|
||||
configAmountNavigation()
|
||||
quoteTaskScheduler.scheduleTask(
|
||||
scope = modelScope,
|
||||
task = loadQuotesTask(),
|
||||
task = loadQuotesTask(isDelayFirst = isDelayFirst),
|
||||
)
|
||||
subscribeOnAutoupdateEnabling()
|
||||
}
|
||||
|
|
@ -609,7 +613,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
@Suppress("NullableToStringCall")
|
||||
TangemLogger.e(
|
||||
"""
|
||||
Invalid cryptocurrencies status:
|
||||
Invalid cryptocurrencies status:
|
||||
| Primary -> $primaryStatus
|
||||
| Secondary -> $secondaryStatus
|
||||
""".trimIndent(),
|
||||
|
|
@ -617,7 +621,8 @@ internal class SwapAmountModel @Inject constructor(
|
|||
analyticsEventHandler.send(
|
||||
SendWithSwapAnalyticEvents.SendWithSwapError(
|
||||
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Amount,
|
||||
message = "Invalid cryptocurrencies status: primary=$primaryStatus, secondary=$secondaryStatus",
|
||||
message = "${SendWithSwapAnalyticsErrorMessages.INVALID_CRYPTOCURRENCIES_STATUS}: " +
|
||||
"primary=$primaryStatus, secondary=$secondaryStatus",
|
||||
),
|
||||
)
|
||||
showErrorAlert(errorMessage = null)
|
||||
|
|
@ -626,9 +631,9 @@ internal class SwapAmountModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun sendAmountScreenOpenedIfNeeded(secondaryStatus: CryptoCurrencyStatus) {
|
||||
val currencyId = secondaryStatus.currency.id
|
||||
if (lastAmountScreenOpenedCurrencyId == currencyId) return
|
||||
lastAmountScreenOpenedCurrencyId = currencyId
|
||||
if (params !is SwapAmountComponentParams.AmountParams) return
|
||||
if (isAmountScreenOpenedSent) return
|
||||
isAmountScreenOpenedSent = true
|
||||
|
||||
val content = uiState.value as? SwapAmountUM.Content ?: return
|
||||
|
||||
|
|
@ -779,6 +784,10 @@ internal class SwapAmountModel @Inject constructor(
|
|||
secondaryFiatRateUSD = secondaryFiatRateUSD,
|
||||
),
|
||||
)
|
||||
if (params is SwapAmountComponentParams.AmountParams) {
|
||||
val selectedQuote = (uiState.value as? SwapAmountUM.Content)?.selectedQuote
|
||||
amountAnalyticsSender.sendErrorIfNeeded(quotes, selectedQuote)
|
||||
}
|
||||
feeSelectorReloadTrigger.triggerUpdate()
|
||||
}
|
||||
}
|
||||
|
|
@ -826,10 +835,10 @@ internal class SwapAmountModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun loadQuotesTask(): PeriodicTask<Unit> {
|
||||
private fun loadQuotesTask(isDelayFirst: Boolean = true): PeriodicTask<Unit> {
|
||||
return PeriodicTask(
|
||||
delay = QUOTES_UPDATE_DELAY,
|
||||
isDelayFirst = true,
|
||||
isDelayFirst = isDelayFirst,
|
||||
task = {
|
||||
runCatching { loadQuotes(isSilentReload = true) }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ internal class SwapNotificationsComponent(
|
|||
val fromCryptoCurrencyStatus: CryptoCurrencyStatus? = null,
|
||||
val priceImpact: PriceImpact? = null,
|
||||
val provider: ExpressProvider? = null,
|
||||
val shouldIncludeFeeInBalanceCheck: Boolean = false,
|
||||
val feeValue: BigDecimal? = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
|
|||
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.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -26,6 +27,7 @@ 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")
|
||||
|
|
@ -43,6 +45,7 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
private val params: SwapNotificationsComponent.Params = paramsContainer.require()
|
||||
|
||||
private var notificationData = params.swapNotificationData
|
||||
private var lastSentErrorMessages: Set<String> = emptySet()
|
||||
|
||||
val uiState: StateFlow<ImmutableList<NotificationUM>>
|
||||
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
|
||||
|
|
@ -109,6 +112,8 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
sendErrorAnalyticsIfNeeded(notifications)
|
||||
}
|
||||
|
||||
private suspend fun MutableList<NotificationUM>.addDestinationTagRequiredNotification() {
|
||||
|
|
@ -132,7 +137,12 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
private fun MutableList<NotificationUM>.addInsufficientFundsNotification() {
|
||||
val enteredFromAmount = notificationData.enteredFromAmount ?: return
|
||||
val balance = notificationData.fromCryptoCurrencyStatus?.value?.amount ?: return
|
||||
if (enteredFromAmount > balance) {
|
||||
val totalRequired = if (notificationData.shouldIncludeFeeInBalanceCheck) {
|
||||
enteredFromAmount + (notificationData.feeValue ?: BigDecimal.ZERO)
|
||||
} else {
|
||||
enteredFromAmount
|
||||
}
|
||||
if (totalRequired > balance) {
|
||||
add(SwapNotificationUM.Error.InsufficientFunds)
|
||||
}
|
||||
}
|
||||
|
|
@ -183,4 +193,34 @@ internal class SwapNotificationsModel @Inject constructor(
|
|||
|
||||
add(notification)
|
||||
}
|
||||
|
||||
private fun sendErrorAnalyticsIfNeeded(notifications: List<NotificationUM>) {
|
||||
val currentErrors = notifications.mapNotNull { notification ->
|
||||
when (notification) {
|
||||
is SwapNotificationUM.Error.InsufficientFunds ->
|
||||
SendWithSwapAnalyticsErrorMessages.INSUFFICIENT_BALANCE
|
||||
is SwapNotificationUM.Error.MinimalAmountError ->
|
||||
SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT
|
||||
is SwapNotificationUM.Error.MaximumAmountError ->
|
||||
SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT
|
||||
is SwapNotificationUM.Warning.ExpressGeneralError ->
|
||||
"${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=${notification.expressError.code}"
|
||||
is NotificationUM.Error.DestinationTagRequired ->
|
||||
SendWithSwapAnalyticsErrorMessages.DESTINATION_TAG_REQUIRED
|
||||
else -> null
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
val newErrors = currentErrors - lastSentErrorMessages
|
||||
lastSentErrorMessages = currentErrors
|
||||
|
||||
newErrors.forEach { errorMessage ->
|
||||
analyticsEventHandler.send(
|
||||
SendWithSwapAnalyticEvents.SendWithSwapError(
|
||||
errorScreen = SendWithSwapAnalyticEvents.ErrorScreen.Confirm,
|
||||
message = errorMessage,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.features.swap.v2.impl.sendviaswap.analytics
|
||||
|
||||
internal object SendWithSwapAnalyticsErrorMessages {
|
||||
const val INSUFFICIENT_BALANCE = "Error - Insufficient balance"
|
||||
const val MIN_AMOUNT = "Error - Min amount"
|
||||
const val MAX_AMOUNT = "Error - Max amount"
|
||||
const val EXPRESS_QUOTE_NO_PROVIDERS = "Error - Express quote no providers found"
|
||||
const val EXPRESS_QUOTE = "Error - Express quote"
|
||||
const val DESTINATION_TAG_REQUIRED = "Error - Destination tag required"
|
||||
const val INVALID_CRYPTOCURRENCIES_STATUS = "Error - Invalid cryptocurrencies status"
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import arrow.core.getOrElse
|
|||
import arrow.core.left
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
|
|
@ -22,6 +21,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyByAddressUseCase
|
||||
import com.tangem.domain.express.models.ExpressOperationType
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.models.account.derivationIndex
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -98,7 +98,6 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
|
||||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val swapAlertFactory: SwapAlertFactory,
|
||||
private val appRouter: AppRouter,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
swapTransactionSenderFactory: SwapTransactionSender.Factory,
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -339,7 +338,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
},
|
||||
popBack = appRouter::pop,
|
||||
popBack = {},
|
||||
)
|
||||
},
|
||||
onSendError = { error ->
|
||||
|
|
@ -362,7 +361,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
},
|
||||
popBack = appRouter::pop,
|
||||
popBack = {},
|
||||
)
|
||||
},
|
||||
onSendSuccess = { txHash, timestamp, data ->
|
||||
|
|
@ -430,6 +429,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
|
||||
private fun updateConfirmNotifications() {
|
||||
modelScope.launch {
|
||||
val isFixedRate = amountUM?.swapRateType == ExpressRateType.Fixed
|
||||
val feeCryptoCurrencyStatus =
|
||||
feeUMV2?.feeExtraInfo?.feeCryptoCurrencyStatus ?: params.primaryFeePaidCurrencyStatusFlow.value
|
||||
sendNotificationsUpdateTrigger.triggerUpdate(
|
||||
|
|
@ -446,6 +446,7 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
fee = confirmData.fee,
|
||||
feeError = confirmData.feeError,
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
isReduceAmountAvailable = !isFixedRate,
|
||||
),
|
||||
)
|
||||
swapNotificationsUpdateTrigger.triggerUpdate(
|
||||
|
|
@ -460,6 +461,8 @@ internal class SendWithSwapConfirmModel @Inject constructor(
|
|||
fromCryptoCurrencyStatus = confirmData.fromCryptoCurrencyStatus,
|
||||
priceImpact = confirmData.priceImpact,
|
||||
provider = confirmData.quote?.provider,
|
||||
shouldIncludeFeeInBalanceCheck = isFixedRate && isAmountSubtractAvailable,
|
||||
feeValue = confirmData.fee?.amount?.value,
|
||||
),
|
||||
)
|
||||
uiState.transformerUpdate(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
package com.tangem.features.swap.v2.impl.amount.analytics
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.features.swap.v2.impl.common.entity.SwapQuoteUM
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticEvents
|
||||
import com.tangem.features.swap.v2.impl.sendviaswap.analytics.SendWithSwapAnalyticsErrorMessages
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class SwapAmountAnalyticsSenderTest {
|
||||
|
||||
private val analyticsEventHandler = mockk<AnalyticsEventHandler>(relaxed = true)
|
||||
private val sender = SwapAmountAnalyticsSender(analyticsEventHandler)
|
||||
|
||||
private val testProvider = ExpressProvider(
|
||||
providerId = "test",
|
||||
name = "Test Provider",
|
||||
type = ExpressProviderType.CEX,
|
||||
imageLarge = "",
|
||||
termsOfUse = null,
|
||||
privacyPolicy = null,
|
||||
slippage = null,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty quotes WHEN sendErrorIfNeeded THEN send no providers error`() {
|
||||
val eventSlot = slot<AnalyticsEvent>()
|
||||
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = emptyList(), selectedQuote = null)
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(any()) }
|
||||
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
|
||||
assertThat(event.errorScreen).isEqualTo(SendWithSwapAnalyticEvents.ErrorScreen.Amount)
|
||||
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE_NO_PROVIDERS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN too small error quote WHEN sendErrorIfNeeded THEN send min amount error`() {
|
||||
val errorQuote = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
|
||||
)
|
||||
val eventSlot = slot<AnalyticsEvent>()
|
||||
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(any()) }
|
||||
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
|
||||
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.MIN_AMOUNT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN too big error quote WHEN sendErrorIfNeeded THEN send max amount error`() {
|
||||
val errorQuote = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooBigError(code = 1002, amount = BigDecimal("1000")),
|
||||
)
|
||||
val eventSlot = slot<AnalyticsEvent>()
|
||||
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(any()) }
|
||||
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
|
||||
assertThat(event.message).isEqualTo(SendWithSwapAnalyticsErrorMessages.MAX_AMOUNT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN unknown express error WHEN sendErrorIfNeeded THEN send express quote error with code`() {
|
||||
val errorQuote = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.InternalError(code = 500),
|
||||
)
|
||||
val eventSlot = slot<AnalyticsEvent>()
|
||||
every { analyticsEventHandler.send(capture(eventSlot)) } returns Unit
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(any()) }
|
||||
val event = eventSlot.captured as SendWithSwapAnalyticEvents.SendWithSwapError
|
||||
assertThat(event.message).isEqualTo("${SendWithSwapAnalyticsErrorMessages.EXPRESS_QUOTE}: code=500")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN content quote WHEN sendErrorIfNeeded THEN do not send analytics`() {
|
||||
val contentQuote = mockk<SwapQuoteUM.Content>()
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(contentQuote), selectedQuote = contentQuote)
|
||||
|
||||
verify(exactly = 0) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN same error twice WHEN sendErrorIfNeeded THEN send analytics only once`() {
|
||||
val errorQuote = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
|
||||
)
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
|
||||
verify(exactly = 1) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN error then different error WHEN sendErrorIfNeeded THEN send analytics twice`() {
|
||||
val smallError = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
|
||||
)
|
||||
val bigError = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooBigError(code = 1002, amount = BigDecimal("1000")),
|
||||
)
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(smallError), selectedQuote = smallError)
|
||||
sender.sendErrorIfNeeded(quotes = listOf(bigError), selectedQuote = bigError)
|
||||
|
||||
verify(exactly = 2) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN error then success then same error WHEN sendErrorIfNeeded THEN send analytics twice`() {
|
||||
val errorQuote = SwapQuoteUM.Error(
|
||||
provider = testProvider,
|
||||
expressError = ExpressError.AmountError.TooSmallError(code = 1001, amount = BigDecimal("0.01")),
|
||||
)
|
||||
val contentQuote = mockk<SwapQuoteUM.Content>()
|
||||
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
sender.sendErrorIfNeeded(quotes = listOf(contentQuote), selectedQuote = contentQuote)
|
||||
sender.sendErrorIfNeeded(quotes = listOf(errorQuote), selectedQuote = errorQuote)
|
||||
|
||||
verify(exactly = 2) { analyticsEventHandler.send(any()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.feature.swap.choosetoken.api
|
||||
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
|||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -446,6 +446,7 @@ internal class StateBuilder(
|
|||
selectionType = ProviderState.SelectionType.CLICK,
|
||||
onProviderClick = actions.onProviderClick,
|
||||
needApplyFCARestrictions = needApplyFCARestrictions,
|
||||
permissionState = quoteModel.permissionState,
|
||||
),
|
||||
priceImpact = priceImpact,
|
||||
tosState = createTosState(swapProvider),
|
||||
|
|
@ -1186,6 +1187,7 @@ internal class StateBuilder(
|
|||
isNeedBestRateBadge: Boolean,
|
||||
onProviderClick: (String) -> Unit,
|
||||
needApplyFCARestrictions: Boolean,
|
||||
permissionState: PermissionDataState,
|
||||
): ProviderState {
|
||||
val rate = toTokenInfo.tokenAmount.value.calculateRate(
|
||||
fromTokenInfo.tokenAmount.value,
|
||||
|
|
@ -1200,6 +1202,8 @@ internal class StateBuilder(
|
|||
|
||||
val additionalBadge = when {
|
||||
needApplyFCARestrictions && isFCARestrictedProvider() -> ProviderState.AdditionalBadge.FCAWarningList
|
||||
permissionState is PermissionDataState.PermissionReadyForRequest ->
|
||||
ProviderState.AdditionalBadge.PermissionRequired
|
||||
isRecommended -> ProviderState.AdditionalBadge.Recommended
|
||||
isNeedBestRateBadge && isBestRate && !needApplyFCARestrictions -> ProviderState.AdditionalBadge.BestTrade
|
||||
else -> ProviderState.AdditionalBadge.Empty
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.models.wallet.isLocked
|
|||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.notifications.models.NotificationType
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
|
||||
|
|
@ -44,6 +45,7 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
private val walletDeepLinkActionTrigger: WalletDeepLinkActionTrigger,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
|
|
@ -62,15 +64,23 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
|
||||
scope.launch {
|
||||
val userWalletId = walletId?.let(::UserWalletId)
|
||||
val userWallet = userWalletId?.let { getUserWalletUseCase(userWalletId) }?.getOrNull()
|
||||
val selectedUserWallet = getSelectedWalletSyncUseCase().getOrNull()
|
||||
val userWallet = if (userWalletId != null) {
|
||||
getUserWalletUseCase(userWalletId).getOrNull()
|
||||
} else {
|
||||
selectedUserWallet
|
||||
}
|
||||
// If wallet to select is null or locked, ignore deeplink
|
||||
if (userWallet == null || userWallet.isLocked) {
|
||||
TangemLogger.e("Error on getting user wallet")
|
||||
return@launch
|
||||
}
|
||||
if (selectWalletUseCase(userWalletId).getOrNull() == null) {
|
||||
TangemLogger.e("Error on selecting user wallet")
|
||||
return@launch
|
||||
if (userWalletId != null && selectedUserWallet?.walletId != userWalletId) {
|
||||
val isSelectionFailed = selectWalletUseCase(userWalletId).getOrNull() == null
|
||||
if (isSelectionFailed) {
|
||||
TangemLogger.e("Error on selecting user wallet")
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
|
||||
val cryptoCurrency = findCryptoCurrency(userWallet = userWallet, networkId = networkId, tokenId = tokenId)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
|
|||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
import com.tangem.common.ui.userwallet.converter.WalletIconUMConverter
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -44,6 +43,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
|
|||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsCryptoCurrencyCouldHideUseCase
|
||||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
|||
import com.tangem.domain.common.wallets.error.SelectWalletError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.models.GetUserWalletError
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.tangempay.TangemPayFeatureToggles
|
||||
|
|
@ -41,6 +43,7 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
|
||||
private val appRouter: AppRouter = mockk()
|
||||
private val selectWalletUseCase: SelectWalletUseCase = mockk()
|
||||
private val getSelectedWalletSync: GetSelectedWalletSyncUseCase = mockk()
|
||||
private val cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher = mockk()
|
||||
private val tokenDetailsDeepLinkActionTrigger: TokenDetailsDeepLinkActionTrigger = mockk()
|
||||
private val walletDeepLinkActionTrigger: WalletDeepLinkActionTrigger = mockk()
|
||||
|
|
@ -56,6 +59,11 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
mockkObject(TangemLogger)
|
||||
every { analyticsEventHandler.send(any()) } just Runs
|
||||
every { appRouter.push(any(), any()) } just Runs
|
||||
val userWallet: UserWallet = mockk()
|
||||
every { userWallet.walletId } returns mockk()
|
||||
every { getSelectedWalletSync() } returns Either.Right(
|
||||
value = userWallet
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -477,6 +485,7 @@ class DefaultTokenDetailsDeepLinkHandlerTest {
|
|||
walletBalanceFetcher = walletBalanceFetcher,
|
||||
tangemPayFeatureToggles = tangemPayFeatureToggles,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
getSelectedWalletSyncUseCase = getSelectedWalletSync,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ import com.tangem.core.ui.message.EventMessageAction
|
|||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.message.bottomSheetMessage
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.models.account.AccountId
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
|||
import com.tangem.domain.account.status.usecase.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListGroupingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListSortingUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
|||
import com.tangem.domain.account.status.usecase.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListGroupingUseCase
|
||||
import com.tangem.domain.account.status.usecase.ToggleTokenListSortingUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
|||
import com.tangem.core.ui.utils.parseBigDecimal
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ package com.tangem.feature.wallet.presentation.account
|
|||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.utils.MainExpandedAccountsHolder
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.core.ui.message.EventMessageAction
|
|||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.account.supplier.MultiAccountListSupplier
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.qrscanning.models.QrResultSource
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.core.ui.message.ToastMessage
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import com.tangem.common.ui.account.CryptoPortfolioIconConverter
|
|||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import dagger.assisted.Assisted
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# https://github.com/tangem/tangem-sdk-android/
|
||||
# https://github.com/tangem/vico
|
||||
|
||||
tangemBlockchainSdk = "develop-1491"
|
||||
tangemBlockchainSdk = "develop-1496"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-602"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ internal class WalletManagerFactoryCreator @Inject constructor(
|
|||
private val blockchainSDKLogger: BlockchainSDKLogger,
|
||||
private val isSolanaTxHistoryEnabled: Boolean,
|
||||
private val isSolanaScaledUiAmountEnabled: Boolean,
|
||||
private val isHederaErc20Enabled: Boolean,
|
||||
) {
|
||||
|
||||
fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory {
|
||||
|
|
@ -39,6 +40,7 @@ internal class WalletManagerFactoryCreator @Inject constructor(
|
|||
isPendingTransactionsEnabled = true,
|
||||
isSolanaTxHistoryEnabled = isSolanaTxHistoryEnabled,
|
||||
isSolanaScaledUiAmountEnabled = isSolanaScaledUiAmountEnabled,
|
||||
isHederaErc20Enabled = isHederaErc20Enabled,
|
||||
),
|
||||
blockchainDataStorage = blockchainDataStorage,
|
||||
loggers = listOf(blockchainSDKLogger),
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ internal object BlockchainSDKFactoryModule {
|
|||
isSolanaScaledUiAmountEnabled = featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.SOLANA_SCALED_UI_AMOUNT_ENABLED,
|
||||
),
|
||||
isHederaErc20Enabled = featureTogglesManager.isFeatureEnabled(
|
||||
FeatureToggles.HEDERA_ERC20_ENABLED,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue