Updated on 2026-08-14
This commit is contained in:
parent
665d56c9e8
commit
7d461390e0
19 changed files with 387 additions and 623 deletions
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.nft.*
|
||||
|
|
@ -25,15 +24,11 @@ internal object NFTDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun providesGetNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -5,17 +5,14 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import com.tangem.data.common.wallet.WalletServerBinder
|
||||
import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
|
||||
import com.tangem.datasource.api.common.response.fold
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.common.response.isNetworkError
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.converters.WalletIdBodyConverter
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import com.tangem.datasource.api.tangemTech.models.SeedPhraseNotificationDTO.Status
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
|
|
@ -24,7 +21,6 @@ import com.tangem.datasource.local.preferences.utils.getObjectMap
|
|||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncOrNull
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
|
|
@ -52,10 +48,7 @@ internal class DefaultWalletsRepository(
|
|||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val seedPhraseNotificationVisibilityStore: RuntimeStateStore<SeedPhraseNotificationsStatuses>,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val authProvider: AuthProvider,
|
||||
private val walletServerBinder: WalletServerBinder,
|
||||
private val appsFlyerStore: AppsFlyerStore,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val moshi: com.squareup.moshi.Moshi,
|
||||
) : WalletsRepository {
|
||||
|
||||
|
|
@ -368,59 +361,36 @@ internal class DefaultWalletsRepository(
|
|||
|
||||
override suspend fun associateWallets(applicationId: String, wallets: List<UserWallet>) =
|
||||
withContext(dispatchers.io) {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
val associateApplicationIdWithWallets: suspend () -> ApiResponse<Unit> = {
|
||||
tangemTechApi.associateApplicationIdWithWalletsV2(
|
||||
applicationId = applicationId,
|
||||
body = AssociateApplicationIdWithWalletsBody(
|
||||
walletIds = wallets.map { it.walletId.stringValue }.distinct(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val apiResponse = associateApplicationIdWithWallets()
|
||||
|
||||
if (apiResponse is ApiResponse.Success) return@withContext
|
||||
|
||||
if (apiResponse is ApiResponse.Error &&
|
||||
apiResponse.cause.isNetworkError(HttpException.Code.BAD_REQUEST)
|
||||
) {
|
||||
val errorBody = (apiResponse.cause as? HttpException)?.errorBody
|
||||
?: error("Bad Request must have error body")
|
||||
|
||||
val adapter = moshi.adapter(AssociateAppWithWalletsErrorResponse::class.java)
|
||||
val errorResponse = adapter.fromJson(errorBody)
|
||||
?: error("Cannot parse error body: $errorBody")
|
||||
|
||||
errorResponse.missingWalletIds
|
||||
.map {
|
||||
async { createWallet(userWalletId = UserWalletId(it)) }
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
associateApplicationIdWithWallets().getOrThrow()
|
||||
}
|
||||
} else {
|
||||
val conversionData = appsFlyerStore.get()
|
||||
val publicKeys = authProvider.getCardsPublicKeys()
|
||||
val walletsBody = wallets.map { userWallet ->
|
||||
WalletIdBodyConverter.convert(
|
||||
userWallet = userWallet,
|
||||
conversionData = conversionData,
|
||||
publicKeys = if (userWallet is UserWallet.Cold) {
|
||||
publicKeys.filterKeys {
|
||||
userWallet.cardsInWallet.contains(it)
|
||||
}
|
||||
} else {
|
||||
emptyMap()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
val associateApplicationIdWithWallets: suspend () -> ApiResponse<Unit> = {
|
||||
tangemTechApi.associateApplicationIdWithWalletsV2(
|
||||
applicationId = applicationId,
|
||||
body = walletsBody,
|
||||
).getOrThrow()
|
||||
body = AssociateApplicationIdWithWalletsBody(
|
||||
walletIds = wallets.map { it.walletId.stringValue }.distinct(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
val apiResponse = associateApplicationIdWithWallets()
|
||||
|
||||
if (apiResponse is ApiResponse.Success) return@withContext
|
||||
|
||||
if (apiResponse is ApiResponse.Error &&
|
||||
apiResponse.cause.isNetworkError(HttpException.Code.BAD_REQUEST)
|
||||
) {
|
||||
val errorBody = (apiResponse.cause as? HttpException)?.errorBody
|
||||
?: error("Bad Request must have error body")
|
||||
|
||||
val adapter = moshi.adapter(AssociateAppWithWalletsErrorResponse::class.java)
|
||||
val errorResponse = adapter.fromJson(errorBody)
|
||||
?: error("Cannot parse error body: $errorBody")
|
||||
|
||||
errorResponse.missingWalletIds
|
||||
.map {
|
||||
async { createWallet(userWalletId = UserWalletId(it)) }
|
||||
}
|
||||
.awaitAll()
|
||||
|
||||
associateApplicationIdWithWallets().getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ import com.tangem.data.wallets.cold.DefaultColdMapDerivationsRepository
|
|||
import com.tangem.data.wallets.derivations.DefaultDerivationsRepository
|
||||
import com.tangem.data.wallets.hot.DefaultHotMapDerivationsRepository
|
||||
import com.tangem.data.wallets.hot.DefaultHotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.wallets.derivations.ColdMapDerivationsRepository
|
||||
import com.tangem.domain.wallets.derivations.DerivationsRepository
|
||||
|
|
@ -43,10 +41,7 @@ internal object WalletsDataModule {
|
|||
tangemTechApi: TangemTechApi,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
authProvider: AuthProvider,
|
||||
walletServerBinder: WalletServerBinder,
|
||||
appsFlyerStore: AppsFlyerStore,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): WalletsRepository {
|
||||
return DefaultWalletsRepository(
|
||||
|
|
@ -55,10 +50,7 @@ internal object WalletsDataModule {
|
|||
userWalletsListRepository = userWalletsListRepository,
|
||||
seedPhraseNotificationVisibilityStore = RuntimeStateStore(defaultValue = emptyMap()),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = authProvider,
|
||||
walletServerBinder = walletServerBinder,
|
||||
appsFlyerStore = appsFlyerStore,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,149 +5,149 @@ import androidx.datastore.preferences.core.Preferences
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.common.wallet.WalletServerBinder
|
||||
import com.tangem.datasource.api.common.AuthProvider
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.PromocodeActivationBody
|
||||
import com.tangem.datasource.api.tangemTech.models.PromocodeActivationResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletResponse
|
||||
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.models.errors.ActivatePromoCodeError
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
* Tests for [DefaultWalletsRepository]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultWalletsRepositoryTest {
|
||||
private lateinit var repository: DefaultWalletsRepository
|
||||
private val preferencesDataStore = mockk<DataStore<Preferences>>(relaxed = true)
|
||||
|
||||
private val preferencesDataStore: DataStore<Preferences> = mockk(relaxed = true)
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val walletServerBinder: WalletServerBinder = mockk()
|
||||
|
||||
private val appPreferenceStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = preferencesDataStore,
|
||||
)
|
||||
private lateinit var tangemTechApi: TangemTechApi
|
||||
private lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
private lateinit var walletServerBinder: WalletServerBinder
|
||||
private lateinit var appsFlyerStore: AppsFlyerStore
|
||||
|
||||
private val repository = DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
walletServerBinder = walletServerBinder,
|
||||
moshi = mockk(),
|
||||
)
|
||||
|
||||
private val testWalletId = UserWalletId("1234567890abcdef")
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
tangemTechApi = mockk()
|
||||
walletServerBinder = mockk()
|
||||
appsFlyerStore = mockk()
|
||||
dispatchers = TestingCoroutineDispatcherProvider()
|
||||
repository = DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = mockk(),
|
||||
walletServerBinder = walletServerBinder,
|
||||
appsFlyerStore = appsFlyerStore,
|
||||
accountsFeatureToggles = mockk(),
|
||||
moshi = mockk(),
|
||||
)
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
clearMocks(tangemTechApi, preferencesDataStore)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN local storage has value WHEN isNotificationsEnabled THEN should return local value`() = runTest {
|
||||
// GIVEN
|
||||
val expectedPreferences = """{"${testWalletId.stringValue}":true}"""
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns expectedPreferences
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class IsNotificationsEnabled {
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
@Test
|
||||
fun `should return local value when local storage has value`() = runTest {
|
||||
// Arrange
|
||||
val expectedPreferences = """{"${testWalletId.stringValue}":true}"""
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns expectedPreferences
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
// Act
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return false when local storage is empty`() = runTest {
|
||||
// Arrange
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
|
||||
// Act
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
|
||||
// Assert
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN local storage is empty WHEN isNotificationsEnabled THEN should return false`() = runTest {
|
||||
// GIVEN
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class SetNotificationsEnabled {
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
@Test
|
||||
fun `should update local storage when enabled status`() = runTest {
|
||||
// Arrange
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isFalse()
|
||||
// Act
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = true)
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should update local storage when disabled status`() = runTest {
|
||||
// Arrange
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// Act
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = false)
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN enabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest {
|
||||
// GIVEN
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class GetWalletsInfo {
|
||||
|
||||
// WHEN
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = true)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN disabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest {
|
||||
// GIVEN
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = false)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
@Test
|
||||
fun `should return converted wallets and update cache when updateCache is true`() = runTest {
|
||||
// Arrange
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(
|
||||
id = wallet2Id,
|
||||
notifyStatus = false,
|
||||
),
|
||||
WalletResponse(id = wallet1Id, notifyStatus = true),
|
||||
WalletResponse(id = wallet2Id, notifyStatus = false),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
// Act
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
|
||||
// THEN
|
||||
// Assert
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
|
|
@ -158,24 +158,20 @@ class DefaultWalletsRepositoryTest {
|
|||
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
@Test
|
||||
fun `should return converted wallets without updating cache when updateCache is false`() = runTest {
|
||||
// Arrange
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(id = wallet1Id, notifyStatus = true),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
|
||||
// WHEN
|
||||
// Act
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = false)
|
||||
|
||||
// THEN
|
||||
// Assert
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
|
|
@ -183,152 +179,126 @@ class DefaultWalletsRepositoryTest {
|
|||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 0) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val card1PublicKey = "card1_public_key"
|
||||
val card2PublicKey = "card2_public_key"
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class AssociateWallets {
|
||||
|
||||
val userWallets = listOf(
|
||||
mockk<UserWallet.Cold> {
|
||||
every { cardsInWallet } returns setOf(card1PublicKey)
|
||||
every { walletId } returns UserWalletId(wallet1Id)
|
||||
every { name } returns "Wallet 1"
|
||||
},
|
||||
mockk<UserWallet.Cold> {
|
||||
every { cardsInWallet } returns setOf(card2PublicKey)
|
||||
every { walletId } returns UserWalletId(wallet2Id)
|
||||
every { name } returns "Wallet 2"
|
||||
},
|
||||
)
|
||||
@Test
|
||||
fun `should convert and send to API V2`() = runTest {
|
||||
// Arrange
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
|
||||
val publicKeys = mapOf(
|
||||
card1PublicKey to "public_key_1",
|
||||
card2PublicKey to "public_key_2",
|
||||
)
|
||||
|
||||
val authProvider = mockk<AuthProvider> {
|
||||
coEvery { getCardsPublicKeys() } returns publicKeys
|
||||
}
|
||||
|
||||
val accountsFeatureToggles = mockk<AccountsFeatureToggles> {
|
||||
every { isFeatureEnabled } returns false
|
||||
}
|
||||
|
||||
repository = DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsListRepository = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = dispatchers,
|
||||
authProvider = authProvider,
|
||||
walletServerBinder = walletServerBinder,
|
||||
appsFlyerStore = appsFlyerStore,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
moshi = mockk(),
|
||||
)
|
||||
|
||||
coEvery { appsFlyerStore.get() } returns null
|
||||
|
||||
coEvery {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
eq(applicationId),
|
||||
any(),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.associateWallets(applicationId, userWallets)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
applicationId = eq(applicationId),
|
||||
body = match { body ->
|
||||
body.size == 2 &&
|
||||
body.any {
|
||||
it.walletId == wallet1Id &&
|
||||
it.cards!!.any { card -> card.cardPublicKey == "public_key_1" } &&
|
||||
it.name == "Wallet 1"
|
||||
} &&
|
||||
body.any {
|
||||
it.walletId == wallet2Id &&
|
||||
it.cards!!.any { card -> card.cardPublicKey == "public_key_2" } &&
|
||||
it.name == "Wallet 2"
|
||||
}
|
||||
val userWallets = listOf(
|
||||
mockk<UserWallet.Cold> {
|
||||
every { walletId } returns UserWalletId(wallet1Id)
|
||||
},
|
||||
mockk<UserWallet.Cold> {
|
||||
every { walletId } returns UserWalletId(wallet2Id)
|
||||
},
|
||||
)
|
||||
|
||||
coEvery {
|
||||
tangemTechApi.associateApplicationIdWithWalletsV2(eq(applicationId), any())
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// Act
|
||||
repository.associateWallets(applicationId, userWallets)
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.associateApplicationIdWithWalletsV2(
|
||||
applicationId = eq(applicationId),
|
||||
body = match { body ->
|
||||
body.walletIds.size == 2 &&
|
||||
body.walletIds.contains(wallet1Id) &&
|
||||
body.walletIds.contains(wallet2Id)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid data WHEN activatePromoCode THEN returns Right with status and calls API`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
val promoCode = "PROMO123"
|
||||
val address = "bc1qexampleaddress"
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Success(
|
||||
PromocodeActivationResponse(status = "activated"),
|
||||
)
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ActivatePromoCode {
|
||||
|
||||
// WHEN
|
||||
val result = repository.activatePromoCode(
|
||||
userWalletId = walletId,
|
||||
promoCode = promoCode,
|
||||
bitcoinAddress = address
|
||||
)
|
||||
|
||||
// THEN
|
||||
var right: String? = null
|
||||
var left: ActivatePromoCodeError? = null
|
||||
result.fold({ left = it }, { right = it })
|
||||
assertThat(left).isNull()
|
||||
assertThat(right).isEqualTo("activated")
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.activatePromoCode(
|
||||
match { it is PromocodeActivationBody && it.promoCode == promoCode && it.address == address },
|
||||
@Test
|
||||
fun `should return Right with status when API returns success`() = runTest {
|
||||
// Arrange
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
val promoCode = "PROMO123"
|
||||
val address = "bc1qexampleaddress"
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Success(
|
||||
PromocodeActivationResponse(status = "activated"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN NOT_FOUND error WHEN activatePromoCode THEN returns Left InvalidPromoCode`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns
|
||||
ApiResponse.Error(
|
||||
// Act
|
||||
val result = repository.activatePromoCode(
|
||||
userWalletId = walletId,
|
||||
promoCode = promoCode,
|
||||
bitcoinAddress = address,
|
||||
)
|
||||
|
||||
// Assert
|
||||
var right: String? = null
|
||||
var left: ActivatePromoCodeError? = null
|
||||
result.fold({ left = it }, { right = it })
|
||||
assertThat(left).isNull()
|
||||
assertThat(right).isEqualTo("activated")
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.activatePromoCode(
|
||||
match<PromocodeActivationBody> { it.promoCode == promoCode && it.address == address },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return Left InvalidPromoCode when API returns NOT_FOUND`() = runTest {
|
||||
// Arrange
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Error(
|
||||
HttpException(code = HttpException.Code.NOT_FOUND, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
|
||||
// WHEN
|
||||
val result = repository.activatePromoCode(userWalletId = walletId, promoCode = "PROMO", bitcoinAddress = "addr")
|
||||
// Act
|
||||
val result = repository.activatePromoCode(
|
||||
userWalletId = walletId,
|
||||
promoCode = "PROMO",
|
||||
bitcoinAddress = "addr",
|
||||
)
|
||||
|
||||
// THEN
|
||||
var error: ActivatePromoCodeError? = null
|
||||
result.fold({ error = it }, { })
|
||||
assertThat(error).isEqualTo(ActivatePromoCodeError.InvalidPromoCode)
|
||||
}
|
||||
// Assert
|
||||
var error: ActivatePromoCodeError? = null
|
||||
result.fold({ error = it }, { })
|
||||
assertThat(error).isEqualTo(ActivatePromoCodeError.InvalidPromoCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN CONFLICT error WHEN activatePromoCode THEN returns Left PromocodeAlreadyUsed`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns
|
||||
ApiResponse.Error(
|
||||
@Test
|
||||
fun `should return Left PromocodeAlreadyUsed when API returns CONFLICT`() = runTest {
|
||||
// Arrange
|
||||
val walletId = UserWalletId("1234567890abcdef")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns ApiResponse.Error(
|
||||
HttpException(code = HttpException.Code.CONFLICT, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
|
||||
// WHEN
|
||||
val result = repository.activatePromoCode(userWalletId = walletId, promoCode = "PROMO", bitcoinAddress = "addr")
|
||||
// Act
|
||||
val result = repository.activatePromoCode(
|
||||
userWalletId = walletId,
|
||||
promoCode = "PROMO",
|
||||
bitcoinAddress = "addr",
|
||||
)
|
||||
|
||||
// THEN
|
||||
var error: ActivatePromoCodeError? = null
|
||||
result.fold({ error = it }, { })
|
||||
assertThat(error).isEqualTo(ActivatePromoCodeError.PromocodeAlreadyUsed)
|
||||
// Assert
|
||||
var error: ActivatePromoCodeError? = null
|
||||
result.fold({ error = it }, { })
|
||||
assertThat(error).isEqualTo(ActivatePromoCodeError.PromocodeAlreadyUsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.domain.nft
|
||||
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
|
@ -8,52 +7,42 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.nft.models.NFTCollections
|
||||
import com.tangem.domain.nft.models.WalletNFTCollections
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
class GetNFTCollectionsUseCase(
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val nftRepository: NFTRepository,
|
||||
private val singleAccountListSupplier: SingleAccountListSupplier,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) {
|
||||
|
||||
@Deprecated("Use invokeForAccounts instead")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<List<NFTCollections>> =
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
invokeForAccounts(userWalletId).map { it.flattenCollections }
|
||||
} else {
|
||||
currenciesRepository
|
||||
.getWalletCurrenciesUpdates(userWalletId)
|
||||
.flatMapLatest {
|
||||
nftCollections(userWalletId, it)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun invokeForAccounts(userWalletId: UserWalletId): Flow<WalletNFTCollections> {
|
||||
fun Account.flowOfNFTCollections(): Flow<Pair<Account, List<NFTCollections>>>? {
|
||||
val currencies = (this as? Account.CryptoPortfolio)?.cryptoCurrencies.orEmpty()
|
||||
if (currencies.isEmpty()) return null
|
||||
return nftCollections(userWalletId = userWalletId, cryptoCurrencies = currencies.toList())
|
||||
.map { nfts -> this to nfts }
|
||||
}
|
||||
|
||||
operator fun invoke(userWalletId: UserWalletId): Flow<WalletNFTCollections> {
|
||||
return singleAccountListSupplier(userWalletId)
|
||||
.mapLatest { statusList -> statusList.accounts.mapNotNull { it.flowOfNFTCollections() } }
|
||||
.flatMapLatest { flows -> combine(flows) { WalletNFTCollections(it.toMap()) } }
|
||||
.mapLatest { statusList -> statusList.accounts.mapNotNull(::flowOfNFTCollections) }
|
||||
.flatMapLatest { flows ->
|
||||
combine(flows) { WalletNFTCollections(it.toMap()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun nftCollections(
|
||||
private fun flowOfNFTCollections(account: Account): Flow<Pair<Account, List<NFTCollections>>>? {
|
||||
val currencies = (account as? Account.CryptoPortfolio)?.cryptoCurrencies.orEmpty()
|
||||
|
||||
if (currencies.isEmpty()) return null
|
||||
|
||||
return getNftCollections(userWalletId = account.userWalletId, cryptoCurrencies = currencies.toList())
|
||||
.map { nfts -> account to nfts }
|
||||
}
|
||||
|
||||
private fun getNftCollections(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<List<NFTCollections>> {
|
||||
val networks = cryptoCurrencies
|
||||
.map { cryptoCurrency -> cryptoCurrency.network }
|
||||
.map(CryptoCurrency::network)
|
||||
.distinct()
|
||||
|
||||
if (networks.isEmpty()) return flowOf(emptyList())
|
||||
|
||||
return nftRepository.observeCollections(userWalletId, networks)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,34 +5,23 @@
|
|||
<ID>BooleanPropertyNaming:NFTAssetUM.kt$NFTAssetUM$val showAllTraitsButton: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:NFTAssetUM.kt$NFTAssetUM.BlockItem$val showInfoButton: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:NFTAssetUM.kt$NFTAssetUM.Rarity.Content$val showDivider: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:NFTCollectionsModel.kt$NFTCollectionsModel$val assetsFulfillQuery = if (query.isEmpty()) { true } else { when (val assets = it.assets) { is NFTCollection.Assets.Empty, is NFTCollection.Assets.Failed, is NFTCollection.Assets.Loading, -> false is NFTCollection.Assets.Value -> { assets.items.any { asset -> asset.name?.lowercase()?.contains(query.lowercase()) == true } } } }</ID>
|
||||
<ID>BooleanPropertyNaming:NFTCollectionsModel.kt$NFTCollectionsModel$val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true</ID>
|
||||
<ID>BooleanPropertyNaming:UpdateDataStateTransformer.kt$UpdateDataStateTransformer$val custom = derivationPath is Network.DerivationPath.Custom</ID>
|
||||
<ID>MultilineLambdaItParameter:ChangeCollectionExpandedStateTransformer.kt$ChangeCollectionExpandedStateTransformer${ val collectionId = collection.collectionIdProvider() if (it.id == collectionId && it is NFTCollectionUM) { if (!it.isExpanded) { onFirstExpanded() } it.copy(isExpanded = !it.isExpanded) } else { it } }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsContent.kt${ key(it.id) { NFTCollectionWarning( modifier = Modifier .padding(top = TangemTheme.dimens.spacing16), state = it, ) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsModel.kt$NFTCollectionsModel${ ChangeCollectionExpandedStateTransformer( collection = collection, collectionIdProvider = collectionIdProvider, onFirstExpanded = { onFirstExpanded(collection) }, ).transform(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsModel.kt$NFTCollectionsModel${ UpdateDataStateTransformer( nftCollections = listOf(), isAccountMode = isAccountMode, walletNFTCollections = nftCollections.copy(collections = filteredNFTs), onReceiveClick = { params.onReceiveClick() }, onRetryClick = ::onRefresh, onExpandCollectionClick = ::onExpandCollectionClick, onRetryAssetsClick = ::onRetryAssetsClick, onAssetClick = { asset, collection -> params.onAssetClick(asset, collection) }, initialSearchBarFactory = ::getInitialSearchBar, collectionIdProvider = collectionIdProvider, ).transform(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsModel.kt$NFTCollectionsModel${ UpdateDataStateTransformer( nftCollections = nftCollections.filter(query), onReceiveClick = { params.onReceiveClick() }, onRetryClick = ::onRefresh, onExpandCollectionClick = ::onExpandCollectionClick, onRetryAssetsClick = ::onRetryAssetsClick, onAssetClick = { asset, collection -> params.onAssetClick(asset, collection) }, initialSearchBarFactory = ::getInitialSearchBar, collectionIdProvider = collectionIdProvider, ).transform(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsModel.kt$NFTCollectionsModel${ it.copy( content = when (val content = it.content) { is NFTCollections.Content.Collections -> content.copy( collections = content.collections.orEmpty().filter { val assetsFulfillQuery = if (query.isEmpty()) { true } else { when (val assets = it.assets) { is NFTCollection.Assets.Empty, is NFTCollection.Assets.Failed, is NFTCollection.Assets.Loading, -> false is NFTCollection.Assets.Value -> { assets.items.any { asset -> asset.name?.lowercase()?.contains(query.lowercase()) == true } } } } val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true collectionFulfillQuery || assetsFulfillQuery }, ) is NFTCollections.Content.Error -> it.content }, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTCollectionsModel.kt$NFTCollectionsModel${ val assetsFulfillQuery = if (query.isEmpty()) { true } else { when (val assets = it.assets) { is NFTCollection.Assets.Empty, is NFTCollection.Assets.Failed, is NFTCollection.Assets.Loading, -> false is NFTCollection.Assets.Value -> { assets.items.any { asset -> asset.name?.lowercase()?.contains(query.lowercase()) == true } } } } val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true collectionFulfillQuery || assetsFulfillQuery }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTDetailsUMFactory.kt$NFTDetailsUMFactory${ NFTAssetUM.BlockItem( title = stringReference(it.name), value = it.value, showInfoButton = false, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTDetailsUMFactory.kt$NFTDetailsUMFactory${ NFTAssetUM.Media.Content( url = it, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTReceiveModel.kt$NFTReceiveModel${ ShowReceiveBottomSheetTransformer( network = network, networkAddress = value.address, onDismissBottomSheet = ::onReceiveBottomSheetDismiss, onCopyClick = { text -> onCopyClick(text, network) }, onShareClick = { text -> onShareClick(text, network) }, ).transform(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTReceiveModel.kt$NFTReceiveModel${ UpdateDataStateTransformer( networks = filteredNetworks, onNetworkClick = ::onNetworkClick, ).transform(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:NFTReceiveModel.kt$NFTReceiveModel${ it.copy( bottomSheetConfig = it.bottomSheetConfig?.copy(isShown = false), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:UpdateDataStateTransformer.kt$UpdateDataStateTransformer${ NFTCollectionUM( id = it.collectionIdProvider(), networkIconId = getActiveIconRes(it.network.rawId), name = it.name.orEmpty(), description = TextReference.PluralRes( R.plurals.nft_collections_count, it.count, wrappedList(it.count), ), logoUrl = it.logoUrl, assets = it.transformAssets(), onExpandClick = { onExpandCollectionClick(it) }, isExpanded = it.isExpanded(state), ) }</ID>
|
||||
<ID>NoNameShadowing:NFTCollectionsModel.kt$NFTCollectionsModel${ val assetsFulfillQuery = if (query.isEmpty()) { true } else { when (val assets = it.assets) { is NFTCollection.Assets.Empty, is NFTCollection.Assets.Failed, is NFTCollection.Assets.Loading, -> false is NFTCollection.Assets.Value -> { assets.items.any { asset -> asset.name?.lowercase()?.contains(query.lowercase()) == true } } } } val collectionFulfillQuery = query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true collectionFulfillQuery || assetsFulfillQuery }</ID>
|
||||
<ID>NullableBooleanCheck:UpdateDataStateTransformer.kt$UpdateDataStateTransformer$(state.content as? NFTCollectionsUM.Content) ?.collections ?.filterIsInstance<NFTCollectionUM>() ?.firstOrNull { it.id == this.collectionIdProvider() } ?.isExpanded ?: false</ID>
|
||||
<ID>NullableToStringCall:NFTCollectionsContent.kt$${item2?.id}</ID>
|
||||
<ID>NullableToStringCall:NFTCollectionsModel.kt$NFTCollectionsModel$${network.derivationPath.value}</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:NFTDetailsModel.kt$NFTDetailsModel$_state</ID>
|
||||
<ID>ReusedModifierInstance:NFTCollectionsContent.kt$Box( modifier = modifier .fillMaxSize() .padding(bottom = bottomPadding), ) { Text( modifier = Modifier .align(Alignment.Center), text = stringResourceSafe(id = R.string.nft_empty_search), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, textAlign = TextAlign.Center, ) }</ID>
|
||||
<ID>ReusedModifierInstance:NFTCollectionsLoading.kt$Card( modifier = modifier .fillMaxWidth() .padding( top = TangemTheme.dimens.spacing16, ), shape = RoundedCornerShape(TangemTheme.dimens.radius16), colors = CardDefaults.cardColors( containerColor = TangemTheme.colors.background.primary, contentColor = TangemTheme.colors.text.primary1, disabledContainerColor = TangemTheme.colors.background.primary, disabledContentColor = TangemTheme.colors.text.primary1, ), ) { Column { repeat(SHIMMER_ITEMS_COUNT) { CollectionPlaceholder() } } }</ID>
|
||||
<ID>ReusedModifierInstance:NFTDetailsAsset.kt$Column( modifier = modifier .verticalScroll(scrollState) .padding( start = TangemTheme.dimens.spacing16, top = TangemTheme.dimens.spacing16, end = TangemTheme.dimens.spacing16, bottom = bottomPadding, ) .fillMaxSize(), ) { NFTDetailsLogo( state = state.media, modifier = Modifier .aspectRatio(1f), ) NFTDetailsInfoGroup( modifier = Modifier .padding(top = TangemTheme.dimens.spacing12), state = state.topInfo, onReadMoreClick = onReadMoreClick, ) NFTDetailsBlocksGroup( modifier = Modifier .padding(top = TangemTheme.dimens.spacing12), items = state.traits, title = resourceReference(R.string.nft_details_traits), action = if (state.showAllTraitsButton) { { NFTBlocksGroupAction( text = resourceReference(R.string.common_see_all), startIcon = { }, onClick = onSeeAllTraitsClick, ) } } else { null }, ) NFTDetailsBlocksGroup( modifier = Modifier .padding(top = TangemTheme.dimens.spacing12), items = state.baseInfoItems, title = resourceReference(R.string.nft_details_base_information), action = { NFTBlocksGroupAction( text = resourceReference(R.string.common_explore), startIcon = { NFTBlocksGroupActionIcon(iconRes = R.drawable.ic_compass_24) }, onClick = onExploreClick, ) }, ) }</ID>
|
||||
<ID>ReusedModifierInstance:NFTDetailsInfoGroup.kt$Column( modifier = modifier .padding( start = TangemTheme.dimens.spacing12, end = TangemTheme.dimens.spacing12, ), ) { TextShimmer( modifier = Modifier .width(TangemTheme.dimens.size158), style = TangemTheme.typography.head, textSizeHeight = true, ) TextShimmer( modifier = Modifier .padding(top = TangemTheme.dimens.spacing4) .width(TangemTheme.dimens.size90), style = TangemTheme.typography.caption2, textSizeHeight = true, ) }</ID>
|
||||
<ID>ReusedModifierInstance:NFTDetailsInfoGroup.kt$Column( modifier = modifier .padding( start = TangemTheme.dimens.spacing12, end = TangemTheme.dimens.spacing12, ), verticalArrangement = Arrangement.SpaceAround, ) { Text( text = state.cryptoPrice.resolveReference(), style = TangemTheme.typography.head, color = TangemTheme.colors.text.primary1, ) Text( modifier = Modifier .padding(top = TangemTheme.dimens.spacing4) .flicker(state.isFlickering), text = state.fiatPrice.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, ) }</ID>
|
||||
<ID>UnsafeCallOnNullableType:DefaultNFTComponent.kt$DefaultNFTComponent$portfolioFetcher!!</ID>
|
||||
<ID>UseEmptyCounterpart:NFTCollectionsModel.kt$NFTCollectionsModel$listOf()</ID>
|
||||
<ID>UseEmptyCounterpart:UpdateDataStateTransformer.kt$UpdateDataStateTransformer$listOf()</ID>
|
||||
<ID>UseOrEmpty:UpdateDataStateTransformer.kt$UpdateDataStateTransformer$walletNFTCollections.collections.values.firstOrNull() ?: listOf()</ID>
|
||||
</CurrentIssues>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ 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.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.nft.FetchNFTCollectionAssetsUseCase
|
||||
import com.tangem.domain.nft.GetNFTCollectionsUseCase
|
||||
|
|
@ -33,7 +32,6 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
|
||||
private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase,
|
||||
private val refreshAllNFTUseCase: RefreshAllNFTUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
|
@ -66,51 +64,21 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
}
|
||||
|
||||
init {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
subscribeToNFTCollectionsNew()
|
||||
} else {
|
||||
subscribeToNFTCollections()
|
||||
}
|
||||
subscribeToNFTCollections()
|
||||
}
|
||||
|
||||
private fun subscribeToNFTCollections() {
|
||||
combine(
|
||||
flow = getNFTCollectionsUseCase(params.userWalletId),
|
||||
flow2 = searchManager.query.distinctUntilChanged(),
|
||||
) { nftCollections, query ->
|
||||
_state.update {
|
||||
UpdateDataStateTransformer(
|
||||
nftCollections = nftCollections.filter(query),
|
||||
onReceiveClick = {
|
||||
params.onReceiveClick()
|
||||
},
|
||||
onRetryClick = ::onRefresh,
|
||||
onExpandCollectionClick = ::onExpandCollectionClick,
|
||||
onRetryAssetsClick = ::onRetryAssetsClick,
|
||||
onAssetClick = { asset, collection ->
|
||||
params.onAssetClick(asset, collection)
|
||||
},
|
||||
initialSearchBarFactory = ::getInitialSearchBar,
|
||||
collectionIdProvider = collectionIdProvider,
|
||||
).transform(it)
|
||||
}
|
||||
}
|
||||
.onStart { onRefresh() }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun subscribeToNFTCollectionsNew() {
|
||||
combine(
|
||||
flow = getNFTCollectionsUseCase.invokeForAccounts(params.userWalletId),
|
||||
flow2 = searchManager.query.distinctUntilChanged(),
|
||||
flow3 = isAccountsModeEnabledUseCase(),
|
||||
) { nftCollections, query, isAccountMode ->
|
||||
val filteredNFTs = nftCollections.collections
|
||||
.mapValues { (_, nfts) -> nfts.filter(query) }
|
||||
|
||||
_state.update {
|
||||
_state.update { stateUM ->
|
||||
UpdateDataStateTransformer(
|
||||
nftCollections = listOf(),
|
||||
nftCollections = emptyList(),
|
||||
isAccountMode = isAccountMode,
|
||||
walletNFTCollections = nftCollections.copy(collections = filteredNFTs),
|
||||
onReceiveClick = {
|
||||
|
|
@ -124,22 +92,22 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
},
|
||||
initialSearchBarFactory = ::getInitialSearchBar,
|
||||
collectionIdProvider = collectionIdProvider,
|
||||
).transform(it)
|
||||
).transform(stateUM)
|
||||
}
|
||||
}
|
||||
.onStart { onRefresh() }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun List<NFTCollections>.filter(query: String): List<NFTCollections> = map {
|
||||
it.copy(
|
||||
content = when (val content = it.content) {
|
||||
private fun List<NFTCollections>.filter(query: String): List<NFTCollections> = map { collections ->
|
||||
collections.copy(
|
||||
content = when (val content = collections.content) {
|
||||
is NFTCollections.Content.Collections -> content.copy(
|
||||
collections = content.collections.orEmpty().filter {
|
||||
val assetsFulfillQuery = if (query.isEmpty()) {
|
||||
collections = content.collections.orEmpty().filter { collection: NFTCollection ->
|
||||
val isAssetsFulfillQuery = if (query.isEmpty()) {
|
||||
true
|
||||
} else {
|
||||
when (val assets = it.assets) {
|
||||
when (val assets = collection.assets) {
|
||||
is NFTCollection.Assets.Empty,
|
||||
is NFTCollection.Assets.Failed,
|
||||
is NFTCollection.Assets.Loading,
|
||||
|
|
@ -152,13 +120,13 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
val collectionFulfillQuery =
|
||||
query.isEmpty() || it.name?.lowercase()?.contains(query.lowercase()) == true
|
||||
val isCollectionFulfillQuery =
|
||||
query.isEmpty() || collection.name?.lowercase()?.contains(query.lowercase()) == true
|
||||
|
||||
collectionFulfillQuery || assetsFulfillQuery
|
||||
isCollectionFulfillQuery || isAssetsFulfillQuery
|
||||
},
|
||||
)
|
||||
is NFTCollections.Content.Error -> it.content
|
||||
is NFTCollections.Content.Error -> collections.content
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -198,12 +166,12 @@ internal class NFTCollectionsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onExpandCollectionClick(collection: NFTCollection) {
|
||||
_state.update {
|
||||
_state.update { stateUM ->
|
||||
ChangeCollectionExpandedStateTransformer(
|
||||
collection = collection,
|
||||
collectionIdProvider = collectionIdProvider,
|
||||
onFirstExpanded = { onFirstExpanded(collection) },
|
||||
).transform(it)
|
||||
).transform(stateUM)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.decompose.navigation.inner.InnerRouter
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.models.PortfolioId
|
||||
import com.tangem.features.account.PortfolioFetcher
|
||||
import com.tangem.features.account.PortfolioSelectorComponent
|
||||
|
|
@ -53,7 +52,6 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
|
||||
private val portfolioSelectorController: PortfolioSelectorController,
|
||||
portfolioFetcherFactory: PortfolioFetcher.Factory,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) : NFTComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val stackNavigation = StackNavigation<NFTRoute>()
|
||||
|
|
@ -66,14 +64,10 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
private val initialRoute: NFTRoute = NFTRoute.Collections(params.userWalletId)
|
||||
private val currentRoute = MutableStateFlow(initialRoute)
|
||||
private val onReceiveClickJob = JobHolder()
|
||||
private val portfolioFetcher: PortfolioFetcher? = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
portfolioFetcherFactory.create(
|
||||
mode = PortfolioFetcher.Mode.Wallet(params.userWalletId),
|
||||
scope = componentScope,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
private val portfolioFetcher: PortfolioFetcher = portfolioFetcherFactory.create(
|
||||
mode = PortfolioFetcher.Mode.Wallet(params.userWalletId),
|
||||
scope = componentScope,
|
||||
)
|
||||
private val bottomSheetNavigation: SlotNavigation<Unit> = SlotNavigation()
|
||||
private val portfolioSelectorCallback = object : PortfolioSelectorComponent.BottomSheetCallback {
|
||||
override val onDismiss: () -> Unit = { bottomSheetNavigation.dismiss() }
|
||||
|
|
@ -83,7 +77,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
source = bottomSheetNavigation,
|
||||
serializer = Unit.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = { configuration, context -> bottomSheetChild(context) },
|
||||
childFactory = { _, context -> bottomSheetChild(context) },
|
||||
)
|
||||
|
||||
private val childStack = childStack(
|
||||
|
|
@ -146,17 +140,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
params = NFTCollectionsComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
onBackClick = ::onChildBack,
|
||||
onReceiveClick = {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
onReceiveClick(route)
|
||||
} else {
|
||||
innerRouter.push(
|
||||
NFTRoute.Receive(
|
||||
portfolioId = PortfolioId(route.userWalletId),
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
onReceiveClick = { onReceiveClick(route) },
|
||||
onAssetClick = { asset, collection ->
|
||||
innerRouter.push(
|
||||
NFTRoute.Details(
|
||||
|
|
@ -170,7 +154,6 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
)
|
||||
|
||||
private fun onReceiveClick(route: NFTRoute.Collections) = componentScope.launch {
|
||||
val portfolioFetcher = requireNotNull(portfolioFetcher)
|
||||
portfolioSelectorController.selectAccount(null)
|
||||
portfolioFetcher.updateMode(mode = PortfolioFetcher.Mode.Wallet(route.userWalletId))
|
||||
val portfolioData = portfolioFetcher.data.first()
|
||||
|
|
@ -245,7 +228,7 @@ internal class DefaultNFTComponent @AssistedInject constructor(
|
|||
portfolioSelectorComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = PortfolioSelectorComponent.Params(
|
||||
portfolioFetcher = portfolioFetcher!!,
|
||||
portfolioFetcher = portfolioFetcher,
|
||||
controller = portfolioSelectorController,
|
||||
bsCallback = portfolioSelectorCallback,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
<CurrentIssues>
|
||||
<ID>BooleanPropertyNaming:AddStakingNotificationsTransformer.kt$AddStakingNotificationsTransformer$val showNotification = sendingAmount + feeAmount > balance</ID>
|
||||
<ID>BooleanPropertyNaming:AmountCurrencyChangeStateTransformer.kt$AmountCurrencyChangeStateTransformer$private val value: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:StakingModel.kt$StakingModel$val noBalanceState = balanceState == null</ID>
|
||||
<ID>BooleanPropertyNaming:StakingUiState.kt$StakingStates.InitialInfoState.Data$val showBanner: Boolean</ID>
|
||||
<ID>BooleanPropertyNaming:StakingUiState.kt$StakingUiState$val showColdWalletInteractionIcon: Boolean</ID>
|
||||
<ID>CastNullableToNonNullableType:SetApprovalBottomSheetInProgressTransformer.kt$SetApprovalBottomSheetInProgressTransformer$as</ID>
|
||||
|
|
@ -14,28 +13,13 @@
|
|||
<ID>MultilineLambdaItParameter:StakingFeeBlock.kt${ if (it == FeeState.Error) { Text( text = DASH_SIGN, color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.body1, ) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingFeeBlock.kt${ if (it == FeeState.Loading) { RectangleShimmer( radius = TangemTheme.dimens.radius3, modifier = Modifier.size( height = TangemTheme.dimens.size24, width = TangemTheme.dimens.size90, ), ) } }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingInfoNotificationsFactory.kt$StakingInfoNotificationsFactory${ it.type == BalanceType.PREPARING || it.type == BalanceType.STAKED || it.type == BalanceType.LOCKED }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingModel.kt$StakingModel${ EnterAmountBoundary( amount = it, fiatRate = status.value.fiatRate.orZero(), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingModel.kt$StakingModel${ Timber.e(it) false }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingModel.kt$StakingModel${ isBalanceHiddenFlow.value = it.isBalanceHidden stateController.update( transformer = HideBalanceStateTransformer( isBalanceHidden = it.isBalanceHidden, cryptoCurrencyStatus = cryptoCurrencyStatus, appCurrency = appCurrency, ), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingModel.kt$StakingModel${ stateController.update( SetFeeToTonInitializeBottomSheetTransformer( appCurrencyProvider = Provider { appCurrency }, feeCryptoCurrencyStatus = feeCryptoCurrencyStatus, fee = it.normal, isFeeApproximate = false, ), ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:StakingStateController.kt$StakingStateController${ it.copy( showColdWalletInteractionIcon = userWallet is UserWallet.Cold, ) }</ID>
|
||||
<ID>NullCheckOnMutableProperty:StakingModel.kt$StakingModel$if (feeCryptoCurrencyStatus != null && fee != null) { getBalanceNotEnoughForFeeWarningUseCase( fee = fee, userWalletId = userWalletId, tokenStatus = cryptoCurrencyStatus, coinStatus = feeCryptoCurrencyStatus ?: cryptoCurrencyStatus, ).getOrNull() } else { null }</ID>
|
||||
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$networkId</ID>
|
||||
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$tokenId</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:StakingFeeBlock.kt$FeeBlockPreviewProvider$contentState</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:StakingModel.kt$StakingModel$isAmountSubtractAvailable</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:StakingStateController.kt$StakingStateController$uiState</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:StakingModel.kt$StakingModel$runCatching</ID>
|
||||
<ID>UnnecessaryEventHandlerParameter:StakingInitialInfoContent.kt$onClick: (BalanceState) -> Unit</ID>
|
||||
<ID>UnnecessaryLet:StakingModel.kt$StakingModel$let(::add)</ID>
|
||||
<ID>UnnecessaryLet:StakingTosText.kt$let { onTextClick(PRIVACY_POLICY_URL) }</ID>
|
||||
<ID>UnnecessaryLet:StakingTosText.kt$let { onTextClick(TERMS_OF_USE_URL) }</ID>
|
||||
<ID>UnsafeCallOnNullableType:StakingModel.kt$StakingModel$tonAccountInitializeTransaction!!</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var actionsJobHolder: JobHolder = JobHolder()</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var approvalJobHolder: JobHolder = JobHolder()</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var feeJobHolder: JobHolder = JobHolder()</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var sendTransactionJobHolder = JobHolder()</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var stakingStateRouter: StakingStateRouter = StakingStateRouter( appRouter = appRouter, stateController = stateController, analyticsEventsHandler = analyticsEventHandler, )</ID>
|
||||
<ID>VarCouldBeVal:StakingModel.kt$StakingModel$private var stepChangesJobHolder = JobHolder()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.blockchain.common.transaction.Fee
|
|||
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||
import com.tangem.common.getValidatorsCount
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer
|
||||
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
|
||||
import com.tangem.common.ui.amountScreen.models.AmountState
|
||||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
|
|
@ -28,7 +27,6 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -113,7 +111,6 @@ internal class StakingModel @Inject constructor(
|
|||
private val stateController: StakingStateController,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
|
|
@ -149,7 +146,6 @@ internal class StakingModel @Inject constructor(
|
|||
@DelayedWork private val coroutineScope: CoroutineScope,
|
||||
private val innerRouter: InnerStakingRouter,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
appRouter: AppRouter,
|
||||
) : Model(), StakingClickIntents {
|
||||
|
||||
|
|
@ -158,7 +154,7 @@ internal class StakingModel @Inject constructor(
|
|||
|
||||
private val params = paramsContainer.require<StakingComponent.Params>()
|
||||
|
||||
private var stakingStateRouter: StakingStateRouter = StakingStateRouter(
|
||||
private val stakingStateRouter: StakingStateRouter = StakingStateRouter(
|
||||
appRouter = appRouter,
|
||||
stateController = stateController,
|
||||
analyticsEventsHandler = analyticsEventHandler,
|
||||
|
|
@ -237,6 +233,7 @@ internal class StakingModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("PropertyUsedBeforeDeclaration")
|
||||
private val transactionSender: StakingTransactionSender by lazy(LazyThreadSafetyMode.NONE) {
|
||||
stakingOperationsFactory.createTransactionSender(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
|
|
@ -298,17 +295,17 @@ internal class StakingModel @Inject constructor(
|
|||
override fun onNextClick(balanceState: BalanceState?) {
|
||||
modelScope.launch {
|
||||
val isInitialInfoStep = value.currentStep == StakingStep.InitialInfo
|
||||
val noBalanceState = balanceState == null
|
||||
val isBalanceAbsent = balanceState == null
|
||||
val hasNoYieldBalanceData = cryptoCurrencyStatus.value.stakingBalance !is StakingBalance.Data.StakeKit
|
||||
|
||||
when {
|
||||
isInitialInfoStep && noBalanceState && integration.areAllTargetsFull && hasNoYieldBalanceData -> {
|
||||
isInitialInfoStep && isBalanceAbsent && integration.areAllTargetsFull && hasNoYieldBalanceData -> {
|
||||
stakingEventFactory.createStakingValidatorsUnavailableAlert()
|
||||
return@launch
|
||||
}
|
||||
isInitialInfoStep && noBalanceState -> {
|
||||
isInitialInfoStep && isBalanceAbsent -> {
|
||||
val list = buildList {
|
||||
SetConfirmationStateInitTransformer(
|
||||
val setConfirmationStateInitTransformer = SetConfirmationStateInitTransformer(
|
||||
isEnter = true,
|
||||
isExplicitExit = false,
|
||||
balanceState = null,
|
||||
|
|
@ -316,13 +313,17 @@ internal class StakingModel @Inject constructor(
|
|||
stakingApproval = stakingApproval,
|
||||
stakingAllowance = stakingAllowance,
|
||||
integration = integration,
|
||||
).let(::add)
|
||||
)
|
||||
|
||||
add(setConfirmationStateInitTransformer)
|
||||
|
||||
if (integration.isPartialAmountDisabled) {
|
||||
ValidatorSelectChangeTransformer(
|
||||
val validatorSelectChangeTransformer = ValidatorSelectChangeTransformer(
|
||||
selectedTarget = integration.preferredTargets.firstOrNull(),
|
||||
integration = integration,
|
||||
).let(::add)
|
||||
SetAmountDataTransformer(
|
||||
)
|
||||
|
||||
val setAmountDataTransformer = SetAmountDataTransformer(
|
||||
clickIntents = this@StakingModel,
|
||||
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
|
||||
userWalletProvider = Provider { userWallet },
|
||||
|
|
@ -330,15 +331,25 @@ internal class StakingModel @Inject constructor(
|
|||
isBalanceHidden = isBalanceHiddenFlow.value,
|
||||
isAccountsModeEnabled = isAccountsModeEnabled,
|
||||
account = account,
|
||||
).let(::add)
|
||||
AmountMaxValueStateTransformer(
|
||||
)
|
||||
|
||||
val amountMaxValueStateTransformer = AmountMaxValueStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
actionType = uiState.value.actionType,
|
||||
integration = integration,
|
||||
).let(::add)
|
||||
)
|
||||
|
||||
addAll(
|
||||
listOf(
|
||||
validatorSelectChangeTransformer,
|
||||
setAmountDataTransformer,
|
||||
amountMaxValueStateTransformer,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
stateController.updateAll(*list.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
|
@ -900,7 +911,7 @@ internal class StakingModel @Inject constructor(
|
|||
AmountReduceByStateTransformer(
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
minimumTransactionAmount = minimumTransactionAmount,
|
||||
value = AmountReduceByTransformer.ReduceByData(
|
||||
value = ReduceByData(
|
||||
reduceAmountBy = reduceAmountBy,
|
||||
reduceAmountByDiff = reduceAmountByDiff,
|
||||
),
|
||||
|
|
@ -953,7 +964,7 @@ internal class StakingModel @Inject constructor(
|
|||
task = PeriodicTask(
|
||||
delay = ALLOWANCE_UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
getAllowanceUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = cryptoCurrencyStatus.currency,
|
||||
|
|
@ -1073,17 +1084,20 @@ internal class StakingModel @Inject constructor(
|
|||
network = cryptoCurrencyStatus.currency.network,
|
||||
memo = null,
|
||||
)
|
||||
tonAccountInitializeTransaction = transaction.getOrElse {
|
||||
|
||||
val initialTransaction = transaction.getOrElse {
|
||||
stateController.update(
|
||||
SetFeeErrorToTonInitializeBottomSheetTransformer(),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
tonAccountInitializeTransaction = initialTransaction
|
||||
|
||||
val transactionFee = getFeeUseCase(
|
||||
userWallet = userWallet,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
transactionData = tonAccountInitializeTransaction!!,
|
||||
transactionData = initialTransaction,
|
||||
)
|
||||
|
||||
transactionFee.fold(
|
||||
|
|
@ -1092,12 +1106,12 @@ internal class StakingModel @Inject constructor(
|
|||
SetFeeErrorToTonInitializeBottomSheetTransformer(),
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { fee ->
|
||||
stateController.update(
|
||||
SetFeeToTonInitializeBottomSheetTransformer(
|
||||
appCurrencyProvider = Provider { appCurrency },
|
||||
feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
|
||||
fee = it.normal,
|
||||
fee = fee.normal,
|
||||
isFeeApproximate = false,
|
||||
),
|
||||
)
|
||||
|
|
@ -1190,41 +1204,20 @@ internal class StakingModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getAccountCurrencyStatusUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.cryptoCurrency,
|
||||
).conflate().distinctUntilChanged()
|
||||
.filter {
|
||||
value.currentStep == StakingStep.InitialInfo || isTopHeatupCase()
|
||||
}.onEach { (maybeAccount, maybeStatus) ->
|
||||
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
|
||||
account = maybeAccount
|
||||
onDataLoaded(maybeStatus)
|
||||
}.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
} else {
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrencyId,
|
||||
isSingleWalletWithTokens = false,
|
||||
).conflate().distinctUntilChanged()
|
||||
.filter {
|
||||
value.currentStep == StakingStep.InitialInfo || isTopHeatupCase()
|
||||
}
|
||||
.onEach { maybeStatus ->
|
||||
maybeStatus.fold(
|
||||
ifRight = { onDataLoaded(it) },
|
||||
ifLeft = { error ->
|
||||
stakingEventFactory.createGenericErrorAlert(error.toString())
|
||||
stateController.update(
|
||||
SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
|
||||
)
|
||||
},
|
||||
)
|
||||
}.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
getAccountCurrencyStatusUseCase(
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.cryptoCurrency,
|
||||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.filter { value.currentStep == StakingStep.InitialInfo || isTopHeatupCase() }
|
||||
.onEach { (maybeAccount, maybeStatus) ->
|
||||
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
|
||||
account = maybeAccount
|
||||
onDataLoaded(maybeStatus)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun onDataLoaded(status: CryptoCurrencyStatus) {
|
||||
|
|
@ -1241,12 +1234,11 @@ internal class StakingModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
feeCryptoCurrencyStatus =
|
||||
getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull()
|
||||
minimumTransactionAmount =
|
||||
getMinimumTransactionAmountSyncUseCase(userWalletId, status).getOrNull()?.let {
|
||||
feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull()
|
||||
minimumTransactionAmount = getMinimumTransactionAmountSyncUseCase(userWalletId, status).getOrNull()
|
||||
?.let { amount ->
|
||||
EnterAmountBoundary(
|
||||
amount = it,
|
||||
amount = amount,
|
||||
fiatRate = status.value.fiatRate.orZero(),
|
||||
)
|
||||
}
|
||||
|
|
@ -1265,11 +1257,11 @@ internal class StakingModel @Inject constructor(
|
|||
getBalanceHidingSettingsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
isBalanceHiddenFlow.value = it.isBalanceHidden
|
||||
.onEach { settings ->
|
||||
isBalanceHiddenFlow.value = settings.isBalanceHidden
|
||||
stateController.update(
|
||||
transformer = HideBalanceStateTransformer(
|
||||
isBalanceHidden = it.isBalanceHidden,
|
||||
isBalanceHidden = settings.isBalanceHidden,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
|
|
@ -1403,8 +1395,8 @@ internal class StakingModel @Inject constructor(
|
|||
val isAccountInitializedNewValue = checkAccountInitializedUseCase.invoke(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrencyStatus.currency.network,
|
||||
).getOrElse {
|
||||
Timber.e(it)
|
||||
).getOrElse { throwable ->
|
||||
Timber.e(throwable)
|
||||
false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,17 +13,8 @@
|
|||
<ID>BooleanPropertyNaming:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$val showProviderLink = getShowProviderLink(notification, statusModel)</ID>
|
||||
<ID>BooleanPropertyNaming:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$val showProviderLink = getShowProviderLink(notification, transaction.status)</ID>
|
||||
<ID>BooleanPropertyNaming:TokenDetailsTopAppBar.kt$var showDropdownMenu by rememberSaveable { mutableStateOf(false) }</ID>
|
||||
<ID>MultilineLambdaItParameter:DefaultTokenDetailsDeepLinkHandler.kt$DefaultTokenDetailsDeepLinkHandler${ val isNetwork = it.network.backendId.equals(networkId, ignoreCase = true) val isCurrency = it.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true val isDefaultDerivation = it.network.derivationPath is Network.DerivationPath.Card val isCustomDerivation = derivationPath?.equals(it.network.derivationPath.value) == true val isCorrectDerivation = isDefaultDerivation || isCustomDerivation isNetwork && isCurrency && isCorrectDerivation }</ID>
|
||||
<ID>MultilineLambdaItParameter:ExpressStatusFactory.kt$ExpressStatusFactory${ when (it) { is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden else -> false } }</ID>
|
||||
<ID>MultilineLambdaItParameter:OnrampStatusFactory.kt$OnrampStatusFactory${ Timber.e("Couldn't update onramp status. $it") onrampTx }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ Timber.e(it.cause?.localizedMessage.orEmpty()) "" }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ analyticsEventsHandler.send( TokenReceiveAnalyticsEvent.ButtonShareAddress(cryptoCurrency.symbol), ) shareManager.shareText(text = it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ analyticsEventsHandler.send(TokenReceiveAnalyticsEvent.ButtonCopyAddress(cryptoCurrency.symbol)) clipboardManager.setText(text = it, isSensitive = true) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ analyticsExceptionHandler.sendException( event = ExceptionAnalyticsEvent( exception = it, params = mapOf( "blockchainId" to cryptoCurrency.network.id.rawId.value, "networkId" to cryptoCurrency.network.backendId, ), ), ) Timber.e( /* t = */ it, /* message = */ "Unable to get wallet manager for user wallet %s and network %s", /* ...args = */ userWalletId, cryptoCurrency.network, ) false }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(it)) Timber.e(it) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ internalUiState.value = stateFactory.getStateWithUpdatedHidden( isBalanceHidden = it.isBalanceHidden, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ sendButtonsEvents(it.states) internalUiState.value = stateFactory.getManageButtonsState(actions = it.states) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsModel.kt$TokenDetailsModel${ val updatedState = stateFactory.getStateWithNotifications(it) notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications) internalUiState.value = updatedState }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsScreen.kt${ Notification( modifier = itemModifier.animateItem(), config = it.config, iconTint = when (it) { is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent is TokenDetailsNotification.UsedOutdatedData -> TangemTheme.colors.text.attention else -> null }, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenDetailsTopAppBar.kt${ TangemDropdownItem( item = it, dismissParent = { showDropdownMenu = false }, ) }</ID>
|
||||
<ID>MultilineLambdaItParameter:TokenStakingBlock.kt${ when (it) { is StakingBlockUM.TemporaryUnavailable -> StakingTemporaryUnavailableBlock() is StakingBlockUM.Loading -> StakingLoading() is StakingBlockUM.Staked -> StakingBalanceBlock( state = it, isBalanceHidden = isBalanceHidden, ) is StakingBlockUM.StakeAvailable -> StakingAvailableContent( state = it, ) } }</ID>
|
||||
|
|
@ -31,21 +22,15 @@
|
|||
<ID>NamedArguments:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$createStateInfo( transaction, toCryptoCurrency, fromCryptoCurrency, toFiatAmount, fromFiatAmount, )</ID>
|
||||
<ID>NestedScopeFunctions:TokenDetailsBalanceSelectStateConverter.kt$TokenDetailsBalanceSelectStateConverter$let { cryptoCurrencyStatus.value.fiatRate?.multiply(it) }</ID>
|
||||
<ID>NullableBooleanCheck:TokenDetailsSwapTransactionsStateConverter.kt$TokenDetailsSwapTransactionsStateConverter$transaction.status?.hasLongTime ?: false</ID>
|
||||
<ID>NullableToStringCall:DefaultTokenDetailsDeepLinkHandler.kt$DefaultTokenDetailsDeepLinkHandler$$networkId</ID>
|
||||
<ID>NullableToStringCall:DefaultTokenDetailsDeepLinkHandler.kt$DefaultTokenDetailsDeepLinkHandler$$tokenId</ID>
|
||||
<ID>NullableToStringCall:TokenDetailsStakingInfoConverter.kt$TokenDetailsStakingInfoConverter$$stakingCryptoAmount</ID>
|
||||
<ID>NullableToStringCall:TokenDetailsStakingInfoConverter.kt$TokenDetailsStakingInfoConverter$$stakingEntryInfo</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:ExpressStatusBottomSheetStateProvider.kt$ExpressStatusBottomSheetStateProvider$network</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:ExpressStatusBottomSheetStateProvider.kt$ExpressStatusBottomSheetStateProvider$token</ID>
|
||||
<ID>PropertyUsedBeforeDeclaration:TokenDetailsModel.kt$TokenDetailsModel$uiState</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:ExchangeStatusFactory.kt$ExchangeStatusFactory$runCatching</ID>
|
||||
<ID>SuspendFunSwallowedCancellation:TokenDetailsModel.kt$TokenDetailsModel$runCatching</ID>
|
||||
<ID>UnnecessaryLet:TokenDetailsModel.kt$TokenDetailsModel$let { internalUiState.value = stateFactory.getStateWithErrorDialog(message) }</ID>
|
||||
<ID>UnnecessaryLet:TokenDetailsSkeletonStateConverter.kt$TokenDetailsSkeletonStateConverter$let(::add)</ID>
|
||||
<ID>UnnecessaryLet:TokenDetailsStateFactory.kt$TokenDetailsStateFactory$let(::add)</ID>
|
||||
<ID>UseEmptyCounterpart:TokenDetailsAnalyticsEvent.kt$TokenDetailsAnalyticsEvent$mapOf()</ID>
|
||||
<ID>UseEmptyCounterpart:TokenDetailsAnalyticsEvent.kt$TokenDetailsAnalyticsEvent.Notice$mapOf()</ID>
|
||||
<ID>UseOrEmpty:ExchangeStatusFactory.kt$ExchangeStatusFactory$savedTransactions ?.flatMap { setOf(it.fromCryptoCurrency.id, it.toCryptoCurrency.id) } ?.toSet() ?.getQuotesOrEmpty() ?: emptySet()</ID>
|
||||
<ID>VarCouldBeVal:TokenDetailsModel.kt$TokenDetailsModel$private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.tangem.common.routing.deeplink.DeeplinkConst.TRANSACTION_ID_KEY
|
|||
import com.tangem.common.routing.deeplink.DeeplinkConst.TYPE_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.WALLET_ID_KEY
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
|
@ -18,7 +17,10 @@ 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.notifications.models.NotificationType
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.FetchCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
|
|
@ -40,7 +42,6 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
@Assisted private val isFromOnNewIntent: Boolean,
|
||||
private val appRouter: AppRouter,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
|
||||
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val tokenDetailsDeepLinkActionTrigger: TokenDetailsDeepLinkActionTrigger,
|
||||
|
|
@ -48,7 +49,6 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val walletBalanceFetcher: WalletBalanceFetcher,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
) : TokenDetailsDeepLinkHandler {
|
||||
|
|
@ -142,12 +142,12 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
if (userWallet.isMultiCurrency) {
|
||||
val derivationPath = queryParams[DERIVATION_PATH_KEY]
|
||||
|
||||
getCryptoCurrencies(userWalletId = userWallet.walletId)?.firstOrNull {
|
||||
val isNetwork = it.network.backendId.equals(networkId, ignoreCase = true)
|
||||
val isCurrency = it.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true
|
||||
getCryptoCurrencies(userWalletId = userWallet.walletId)?.firstOrNull { currency ->
|
||||
val isNetwork = currency.network.backendId.equals(networkId, ignoreCase = true)
|
||||
val isCurrency = currency.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true
|
||||
|
||||
val isDefaultDerivation = it.network.derivationPath is Network.DerivationPath.Card
|
||||
val isCustomDerivation = derivationPath?.equals(it.network.derivationPath.value) == true
|
||||
val isDefaultDerivation = currency.network.derivationPath is Network.DerivationPath.Card
|
||||
val isCustomDerivation = derivationPath?.equals(currency.network.derivationPath.value) == true
|
||||
val isCorrectDerivation = isDefaultDerivation || isCustomDerivation
|
||||
isNetwork && isCurrency && isCorrectDerivation
|
||||
}
|
||||
|
|
@ -156,14 +156,9 @@ internal class DefaultTokenDetailsDeepLinkHandler @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun getCryptoCurrencies(userWalletId: UserWalletId): List<CryptoCurrency>? {
|
||||
return if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
?.toList()
|
||||
} else {
|
||||
getCryptoCurrenciesUseCase(userWalletId = userWalletId).getOrNull()
|
||||
}
|
||||
return multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
|
||||
)?.toList()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -2,24 +2,20 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.right
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
|
||||
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
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.card.common.util.cardTypesResolver
|
||||
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.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.ExpressStateFactory
|
||||
|
|
@ -43,10 +39,8 @@ internal class ExpressTransactionsModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
expressStatusFactory: ExpressStatusFactory.Factory,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val router: InnerTokenDetailsRouter,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val expressTransactionsEventListener: ExpressTransactionsEventListener,
|
||||
) : Model(), ExpressTransactionsClickIntents {
|
||||
|
|
@ -163,22 +157,10 @@ internal class ExpressTransactionsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||
.onEach { account = it.account }
|
||||
.map { it.status.right() }
|
||||
} else {
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
|
||||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
|
||||
)
|
||||
}
|
||||
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||
.onEach { account = it.account }
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeCurrencyStatus ->
|
||||
maybeCurrencyStatus.onRight { status -> cryptoCurrencyStatus = status }
|
||||
}
|
||||
.onEach { cryptoCurrencyStatus = it.status }
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
.saveIn(marketPriceJobHolder)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
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.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -105,12 +104,11 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions")
|
||||
@Suppress("LongParameterList", "LargeClass", "TooManyFunctions", "PropertyUsedBeforeDeclaration")
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TokenDetailsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
|
|
@ -148,7 +146,6 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
|
||||
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
|
||||
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val getAccountCryptoCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
|
||||
private val yieldSupplyGetRewardsBalanceUseCase: YieldSupplyGetRewardsBalanceUseCase,
|
||||
|
|
@ -177,7 +174,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var account: Account.CryptoPortfolio? = null
|
||||
private var isBalanceLoadedEventSent = false
|
||||
private var expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
|
||||
private val expressTxStatusTaskScheduler = SingleTaskScheduler<PersistentList<ExpressTransactionStateUM>>()
|
||||
|
||||
/** Transaction id to check for status */
|
||||
private val waitForFirstExpressStatusEmmit = MutableStateFlow(false)
|
||||
|
|
@ -265,21 +262,13 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
private fun initButtons() {
|
||||
// we need also init buttons before start all loading to avoid buttons blocking
|
||||
modelScope.launch {
|
||||
val currentCryptoCurrencyStatus = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getAccountCryptoCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
)
|
||||
.onSome { account = it.account }
|
||||
.getOrNull()
|
||||
?.status
|
||||
} else {
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = false,
|
||||
).getOrNull()
|
||||
}
|
||||
val currentCryptoCurrencyStatus = getAccountCryptoCurrencyStatusUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
)
|
||||
.onSome { account = it.account }
|
||||
.getOrNull()
|
||||
?.status
|
||||
|
||||
currentCryptoCurrencyStatus?.let { status ->
|
||||
cryptoCurrencyStatus = status
|
||||
|
|
@ -296,9 +285,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
|
||||
private fun handleBalanceHiding() {
|
||||
getBalanceHidingSettingsUseCase()
|
||||
.onEach {
|
||||
.onEach { settings ->
|
||||
internalUiState.value = stateFactory.getStateWithUpdatedHidden(
|
||||
isBalanceHidden = it.isBalanceHidden,
|
||||
isBalanceHidden = settings.isBalanceHidden,
|
||||
)
|
||||
}
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -311,9 +300,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
)
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
sendButtonsEvents(it.states)
|
||||
internalUiState.value = stateFactory.getManageButtonsState(actions = it.states)
|
||||
.onEach { state ->
|
||||
sendButtonsEvents(state.states)
|
||||
internalUiState.value = stateFactory.getManageButtonsState(actions = state.states)
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(modelScope)
|
||||
|
|
@ -345,8 +334,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.onEach {
|
||||
val updatedState = stateFactory.getStateWithNotifications(it)
|
||||
.onEach { warnings ->
|
||||
val updatedState = stateFactory.getStateWithNotifications(warnings)
|
||||
notificationsAnalyticsSender.send(internalUiState.value, updatedState.notifications)
|
||||
internalUiState.value = updatedState
|
||||
}
|
||||
|
|
@ -356,18 +345,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun subscribeOnCurrencyStatusUpdates() {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||
.onEach { account = it.account }
|
||||
.map { it.status.right() }
|
||||
} else {
|
||||
getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet(
|
||||
userWalletId = userWalletId,
|
||||
currencyId = cryptoCurrency.id,
|
||||
isSingleWalletWithTokens = userWallet is UserWallet.Cold &&
|
||||
userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(),
|
||||
)
|
||||
}
|
||||
getAccountCryptoCurrencyStatusUseCase(userWalletId, cryptoCurrency)
|
||||
.onEach { account = it.account }
|
||||
.map { it.status.right() }
|
||||
.distinctUntilChanged()
|
||||
.onEach { maybeCurrencyStatus ->
|
||||
internalUiState.value = stateFactory.getCurrencyLoadedBalanceState(maybeCurrencyStatus)
|
||||
|
|
@ -402,7 +382,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
isDelayFirst = false,
|
||||
delay = EXPRESS_STATUS_UPDATE_DELAY,
|
||||
task = {
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
expressStatusFactory.getUpdatedExpressStatuses(internalUiState.value.expressTxs)
|
||||
}
|
||||
},
|
||||
|
|
@ -508,10 +488,10 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
.mapLeft {
|
||||
.mapLeft { throwable ->
|
||||
analyticsExceptionHandler.sendException(
|
||||
event = ExceptionAnalyticsEvent(
|
||||
exception = it,
|
||||
exception = throwable,
|
||||
params = mapOf(
|
||||
"blockchainId" to cryptoCurrency.network.id.rawId.value,
|
||||
"networkId" to cryptoCurrency.network.backendId,
|
||||
|
|
@ -520,7 +500,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
)
|
||||
|
||||
Timber.e(
|
||||
/* t = */ it,
|
||||
/* t = */ throwable,
|
||||
/* message = */ "Unable to get wallet manager for user wallet %s and network %s",
|
||||
/* ...args = */ userWalletId,
|
||||
cryptoCurrency.network,
|
||||
|
|
@ -674,8 +654,8 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
userWalletId,
|
||||
cryptoCurrency.network,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
Timber.e(it.cause?.localizedMessage.orEmpty())
|
||||
ifLeft = { throwable ->
|
||||
Timber.e(throwable.cause?.localizedMessage.orEmpty())
|
||||
""
|
||||
},
|
||||
ifRight = { it },
|
||||
|
|
@ -976,9 +956,9 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
message?.let {
|
||||
internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(it))
|
||||
Timber.e(it)
|
||||
if (message != null) {
|
||||
internalUiState.value = stateFactory.getStateWithErrorDialog(stringReference(message))
|
||||
Timber.e(message)
|
||||
}
|
||||
},
|
||||
ifRight = {
|
||||
|
|
@ -1019,7 +999,10 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
is SendTransactionError.UnknownError -> error.ex?.localizedMessage
|
||||
}?.let { stringReference(it) }
|
||||
}
|
||||
message?.let { internalUiState.value = stateFactory.getStateWithErrorDialog(message) }
|
||||
|
||||
if (message != null) {
|
||||
internalUiState.value = stateFactory.getStateWithErrorDialog(message)
|
||||
}
|
||||
},
|
||||
ifRight = { internalUiState.value = stateFactory.getStateWithRemovedRequiredTrustlineNotification() },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues/>
|
||||
<CurrentIssues/>
|
||||
</SmellBaseline>
|
||||
|
|
@ -22,7 +22,6 @@ import com.tangem.core.ui.message.DialogMessage
|
|||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.core.ui.utils.requestPermission
|
||||
import com.tangem.datasource.local.accounts.AccountTokenMigrationStore
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.feature.walletsettings.component.NetworksAvailableForNotificationsComponent
|
||||
import com.tangem.feature.walletsettings.component.RenameWalletComponent
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
|
|
@ -49,7 +48,6 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
|
|||
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
|
||||
private val networksAvailableForNotificationsComponent: NetworksAvailableForNotificationsComponent.Factory,
|
||||
private val accountTokenMigrationStore: AccountTokenMigrationStore,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) : WalletSettingsComponent, AppComponentContext by context {
|
||||
|
||||
private val model: WalletSettingsModel = getOrCreateModel(params)
|
||||
|
|
@ -74,11 +72,7 @@ internal class DefaultWalletSettingsComponent @AssistedInject constructor(
|
|||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
showMigrationAlertIfNeeded()
|
||||
}
|
||||
},
|
||||
onResume = { showMigrationAlertIfNeeded() },
|
||||
onPause = { accountMigrationJobHolder.cancel() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.models.AccountList
|
||||
import com.tangem.domain.account.models.AccountStatusList
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
|
|
@ -46,13 +45,12 @@ internal class AccountItemsDelegate @Inject constructor(
|
|||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val accountListSortingSaver: AccountListSortingSaver,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
) {
|
||||
|
||||
private val userWalletId = paramsContainer.require<WalletSettingsComponent.Params>().userWalletId
|
||||
|
||||
fun isAccountsSupported(wallet: UserWallet) = accountsFeatureToggles.isFeatureEnabled && wallet.isAccountsSupported
|
||||
fun isAccountsSupported(wallet: UserWallet) = wallet.isAccountsSupported
|
||||
|
||||
fun loadAccount(wallet: UserWallet): Flow<List<WalletSettingsAccountsUM>> {
|
||||
if (!isAccountsSupported(wallet)) return flowOf(emptyList())
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import dagger.assisted.AssistedInject
|
|||
internal class MultiWalletContentLoader @AssistedInject constructor(
|
||||
@Assisted private val userWallet: UserWallet,
|
||||
private val accountListSubscriberFactory: AccountListSubscriber.Factory,
|
||||
private val walletNFTListSubscriberFactory: WalletNFTListSubscriberV2.Factory,
|
||||
private val walletNFTListSubscriberFactory: WalletNFTListSubscriber.Factory,
|
||||
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
|
||||
private val multiWalletWarningsSubscriberFactory: MultiWalletWarningsSubscriber.Factory,
|
||||
private val walletNotificationsSubscriberFactory: WalletNotificationsSubscriber.Factory,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
internal class WalletNFTListSubscriberV2 @AssistedInject constructor(
|
||||
internal class WalletNFTListSubscriber @AssistedInject constructor(
|
||||
@Assisted override val userWallet: UserWallet,
|
||||
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
|
|
@ -35,7 +35,7 @@ internal class WalletNFTListSubscriberV2 @AssistedInject constructor(
|
|||
// if NFT is enabled for this wallet and there are currencies,
|
||||
// then start observing changes from store and apply transformer if need
|
||||
if (nftEnabled && currencies.isNotEmpty()) {
|
||||
getNFTCollectionsUseCase.invokeForAccounts(userWallet.walletId)
|
||||
getNFTCollectionsUseCase(userWallet.walletId)
|
||||
.shareIn(
|
||||
scope = coroutineScope,
|
||||
started = SharingStarted.WhileSubscribed(),
|
||||
|
|
@ -61,6 +61,6 @@ internal class WalletNFTListSubscriberV2 @AssistedInject constructor(
|
|||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(userWallet: UserWallet): WalletNFTListSubscriberV2
|
||||
fun create(userWallet: UserWallet): WalletNFTListSubscriber
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue