diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index aa3171803c..26be12c002 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -6,6 +6,7 @@ import com.squareup.moshi.JsonDataException import com.squareup.moshi.Types import com.tangem.datasource.local.preferences.AppPreferencesStore import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map @@ -20,7 +21,7 @@ inline fun AppPreferencesStore.getObject(key: Preferences.Key AppPreferencesStore.getObject(key: Preferences.Key AppPreferencesStore.storeObjectList(key: Preferen /** Get flow of list of data [T] by string [key]. If data is not found, it returns `null` */ inline fun AppPreferencesStore.getObjectList(key: Preferences.Key): Flow?> { val adapter = moshi.adapter>(Types.newParameterizedType(List::class.java, T::class.java)) - return data.map { it[key]?.let(adapter::fromJson) } + return data.map { it[key]?.let(adapter::fromJson) }.distinctUntilChanged() } /** Get list of data [T] by string [key], or empty if data is not found */ diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7170767402..8c540a7cbd 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -19,10 +19,6 @@ "name": "WC_SOLANA_TX_SIGN_ENABLED", "version": "undefined" }, - { - "name": "TOKEN_LIST_LCE_ENABLED", - "version": "5.12.0" - }, { "name": "CARDANO_TOKENS_SUPPORT_ENABLED", "version": "5.12.0" diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt index 24f64babdc..49752f2700 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/block/BlockCard.kt @@ -32,6 +32,6 @@ val TangemBlockCardColors: CardColors get() = CardColors( containerColor = TangemTheme.colors.background.primary, contentColor = TangemTheme.colors.text.primary1, - disabledContainerColor = TangemTheme.colors.button.disabled, - disabledContentColor = TangemTheme.colors.text.disabled, + disabledContainerColor = TangemTheme.colors.background.primary, + disabledContentColor = TangemTheme.colors.text.primary1, ) \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 7951e83c17..dcfd66e7a7 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -261,7 +261,7 @@ internal class DefaultCurrenciesRepository( launch(dispatchers.io) { combine( - getMultiCurrencyWalletCurrencies(userWallet).distinctUntilChanged(), + getMultiCurrencyWalletCurrencies(userWallet), isMultiCurrencyWalletCurrenciesFetching.map { it.getOrElse(userWallet.walletId) { false } }, ) { currencies, isFetching -> send(currencies, isStillLoading = isFetching) @@ -442,21 +442,23 @@ internal class DefaultCurrenciesRepository( } private suspend fun fetchTokensIfCacheExpired(userWallet: UserWallet, refresh: Boolean) { - try { - isMultiCurrencyWalletCurrenciesFetching.update { - it + (userWallet.walletId to true) - } + cacheRegistry.invokeOnExpire( + key = getTokensCacheKey(userWallet.walletId), + skipCache = refresh, + block = { + isMultiCurrencyWalletCurrenciesFetching.update { + it + (userWallet.walletId to true) + } - cacheRegistry.invokeOnExpire( - key = getTokensCacheKey(userWallet.walletId), - skipCache = refresh, - block = { fetchTokens(userWallet) }, - ) - } finally { - isMultiCurrencyWalletCurrenciesFetching.update { - it - userWallet.walletId - } - } + try { + fetchTokens(userWallet) + } finally { + isMultiCurrencyWalletCurrenciesFetching.update { + it - userWallet.walletId + } + } + }, + ) } private suspend fun fetchTokens(userWallet: UserWallet) { diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index ce8d705b9f..bcc65da983 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -183,24 +183,22 @@ internal class DefaultNetworksRepository( networks: Set, refresh: Boolean, ) { - try { - isNetworkStatusesFetching.update { - it + (userWalletId to true) - } + val currencies = getCurrencies(userWalletId, networks) + val networksDeferred = networks.mapNotNull { network -> + fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) + } - val currencies = getCurrencies(userWalletId, networks) - coroutineScope { - networks - .map { network -> - async { - fetchNetworkStatusIfCacheExpired(userWalletId, network, currencies, refresh) - } - } - .awaitAll() - } - } finally { - isNetworkStatusesFetching.update { - it - userWalletId + if (networksDeferred.isNotEmpty()) { + try { + isNetworkStatusesFetching.update { + it + (userWalletId to true) + } + + networksDeferred.awaitAll() + } finally { + isNetworkStatusesFetching.update { + it - userWalletId + } } } } @@ -226,12 +224,19 @@ internal class DefaultNetworksRepository( network: Network, currencies: Sequence, refresh: Boolean, - ) { - cacheRegistry.invokeOnExpire( - key = getNetworksStatusesCacheKey(userWalletId, network), - skipCache = refresh, - block = { fetchNetworkStatus(userWalletId, network, currencies) }, - ) + ): Deferred? = coroutineScope { + val key = getNetworksStatusesCacheKey(userWalletId, network) + if (refresh || cacheRegistry.isExpired(key)) { + async { + cacheRegistry.invokeOnExpire( + key = key, + skipCache = refresh, + block = { fetchNetworkStatus(userWalletId, network, currencies) }, + ) + } + } else { + null + } } private suspend fun fetchNetworkStatus( diff --git a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/error/HideBalancesError.kt b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/error/HideBalancesError.kt index 7438c20918..fb0e72e862 100644 --- a/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/error/HideBalancesError.kt +++ b/domain/balance-hiding/src/main/kotlin/com/tangem/domain/balancehiding/error/HideBalancesError.kt @@ -2,7 +2,7 @@ package com.tangem.domain.balancehiding.error sealed class HideBalancesError { - object HidingDisabled : HideBalancesError() + data object HidingDisabled : HideBalancesError() data class DataError(val cause: Throwable) : HideBalancesError() } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt index 0812a3d523..2d6902a58a 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/Lce.kt @@ -107,4 +107,43 @@ sealed class Lce { ifContent = { null }, ifError = ::identity, ) + + /** + * Returns `true` if this [Lce] is a [Lce.Loading] state and the given predicate is `true`. + * + * @param predicate The predicate to apply to the partial content. + * By default, the predicate is `true` for any partial content. + * @return `true` if this [Lce] is a [Lce.Loading] state and the given predicate is `true`, `false` otherwise. + */ + fun isLoading(predicate: (maybeContent: C?) -> Boolean = { true }): Boolean = fold( + ifLoading = { predicate(it) }, + ifContent = { false }, + ifError = { false }, + ) + + /** + * Returns `true` if this [Lce] is a [Lce.Error] state and the given predicate is `true`. + * + * @param predicate The predicate to apply to the error. + * By default, the predicate is `true` for any error. + * @return `true` if this [Lce] is a [Lce.Error] state and the given predicate is `true`, `false` otherwise. + */ + fun isError(predicate: (error: E) -> Boolean = { true }): Boolean = fold( + ifLoading = { false }, + ifContent = { false }, + ifError = { predicate(it) }, + ) + + /** + * Returns `true` if this [Lce] is a [Lce.Content] state and the given predicate is `true`. + * + * @param predicate The predicate to apply to the content. + * By default, the predicate is `true` for any content. + * @return `true` if this [Lce] is a [Lce.Content] state and the given predicate is `true`, `false` otherwise. + */ + fun isContent(predicate: (content: C) -> Boolean = { true }): Boolean = fold( + ifLoading = { false }, + ifContent = { predicate(it) }, + ifError = { false }, + ) } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt index 2040b0774a..1484205426 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceRaise.kt @@ -46,7 +46,7 @@ class LceRaise @PublishedApi internal constructor( * */ @RaiseDSL @OptIn(ExperimentalTypeInference::class) - inline fun withError( + inline fun withError( transform: (OtherError) -> E, @BuilderInference block: LceRaise.() -> C, ): C = recover( diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt index ef0b96e10b..9cd14a845c 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetTokenListUseCase.kt @@ -1,8 +1,6 @@ package com.tangem.domain.tokens -import arrow.core.left import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.utils.EitherFlow import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import com.tangem.domain.core.utils.toLce @@ -12,7 +10,6 @@ import com.tangem.domain.tokens.error.mapper.mapToTokenListError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.operations.CurrenciesStatusesLceOperations -import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations import com.tangem.domain.tokens.operations.TokenListOperations import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository @@ -31,29 +28,7 @@ class GetTokenListUseCase( ) { @OptIn(ExperimentalCoroutinesApi::class) - fun launch(userWalletId: UserWalletId): EitherFlow { - val operations = CurrenciesStatusesOperations( - userWalletId = userWalletId, - currenciesRepository = currenciesRepository, - quotesRepository = quotesRepository, - networksRepository = networksRepository, - stakingRepository = stakingRepository, - ) - - return operations.getCurrenciesStatusesFlow().transformLatest { maybeTokens -> - maybeTokens.fold( - ifLeft = { error -> - emit(error.mapToTokenListError().left()) - }, - ifRight = { tokens -> - emitAll(createTokenList(userWalletId, tokens)) - }, - ) - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - fun launchLce(userWalletId: UserWalletId): LceFlow { + fun launch(userWalletId: UserWalletId): LceFlow { val operations = CurrenciesStatusesLceOperations( currenciesRepository = currenciesRepository, quotesRepository = quotesRepository, @@ -78,21 +53,6 @@ class GetTokenListUseCase( } } - private fun createTokenList( - userWalletId: UserWalletId, - tokens: List, - ): EitherFlow { - val operations = TokenListOperations( - userWalletId = userWalletId, - tokens = tokens, - currenciesRepository = currenciesRepository, - ) - - return operations.getTokenListFlow().map { maybeTokenList -> - maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError) - } - } - private fun createTokenListLce( userWalletId: UserWalletId, currencies: List, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt index ce783509f9..0f4d3477da 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetWalletTotalBalanceUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import arrow.atomic.update import arrow.core.raise.ensureNotNull import arrow.core.toNonEmptyListOrNull import com.tangem.domain.core.lce.Lce @@ -16,9 +17,10 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.tokens.repository.NetworksRepository import com.tangem.domain.tokens.repository.QuotesRepository import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.transformLatest class GetWalletTotalBalanceUseCase( private val currenciesRepository: CurrenciesRepository, @@ -28,9 +30,9 @@ class GetWalletTotalBalanceUseCase( ) { suspend operator fun invoke( - userWallestIds: Collection, + userTallestIds: Collection, ): LceFlow> { - val flows = userWallestIds.distinct() + val flows = userTallestIds.distinct() .map { userWalletId -> invoke(userWalletId).map { maybeBalance -> userWalletId to maybeBalance @@ -39,17 +41,23 @@ class GetWalletTotalBalanceUseCase( return combine(flows) { balances -> lce { - balances.associate { (userWalletId, maybeBalance) -> - userWalletId to maybeBalance.bind() + balances.fold(mutableMapOf()) { acc, (userWalletId, maybeBalance) -> + val balance = maybeBalance.bindOrNull() ?: TotalFiatBalance.Loading + + isLoading.update { it || balance is TotalFiatBalance.Loading } + + acc[userWalletId] = balance + acc } } } } + @OptIn(ExperimentalCoroutinesApi::class) suspend operator fun invoke(userWalletId: UserWalletId): LceFlow { val currenciesStatuses = getStatuses(userWalletId) - return currenciesStatuses.transform { maybeStatuses -> + return currenciesStatuses.transformLatest { maybeStatuses -> val balance = createBalance(maybeStatuses) emit(balance) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt index a4265dd621..0a542d0874 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesLceOperations.kt @@ -33,7 +33,7 @@ internal class CurrenciesStatusesLceOperations( return transformToCurrenciesStatuses( userWalletId = userWalletId, flow = if (isSingleCurrencyWalletsAllowed) { - getWalletCurrenies(userWalletId) + getWalletCurrencies(userWalletId) } else { getMultiCurrencyWalletCurrencies(userWalletId) }, @@ -105,7 +105,7 @@ internal class CurrenciesStatusesLceOperations( return statuses } - private fun getWalletCurrenies(userWalletId: UserWalletId): LceFlow> { + private fun getWalletCurrencies(userWalletId: UserWalletId): LceFlow> { return currenciesRepository.getWalletCurrenciesUpdates(userWalletId) .map { maybeCurrencies -> maybeCurrencies.mapError { TokenListError.DataError(it) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt index 24bb880d94..a1c6c428a3 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt @@ -24,52 +24,6 @@ internal class CurrenciesStatusesOperations( private val userWalletId: UserWalletId, ) { - @OptIn(ExperimentalCoroutinesApi::class) - fun getCurrenciesStatusesFlow(): EitherFlow> { - return getMultiCurrencyWalletCurrencies().transformLatest { maybeCurrencies -> - val nonEmptyCurrencies = maybeCurrencies.fold( - ifLeft = { error -> - emit(error.left()) - return@transformLatest - }, - ifRight = List::toNonEmptyListOrNull, - ) - - if (nonEmptyCurrencies == null) { - val emptyCurrenciesStatuses = emptyList() - - emit(emptyCurrenciesStatuses.right()) - return@transformLatest - } - - val maybeLoadingCurrenciesStatuses = createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeNetworkStatuses = null, - maybeQuotes = null, - maybeYieldBalances = null, - ) - - emit(maybeLoadingCurrenciesStatuses) - - val (networks, currenciesIds) = getIds(nonEmptyCurrencies) - - val currenciesFlow = combine( - getQuotes(currenciesIds), - getNetworksStatuses(networks), - getYieldBalances(), - ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances -> - createCurrenciesStatuses( - currencies = nonEmptyCurrencies, - maybeQuotes = maybeQuotes, - maybeNetworkStatuses = maybeNetworksStatuses, - maybeYieldBalances = maybeYieldBalances, - ) - } - - emitAll(currenciesFlow) - } - } - suspend fun getCurrenciesStatusesSync(): Either> { return either { catch( @@ -360,13 +314,6 @@ internal class CurrenciesStatusesOperations( return currencyStatusOperations.createTokenStatus() } - private fun getMultiCurrencyWalletCurrencies(): Flow>> { - return currenciesRepository.getMultiCurrencyWalletCurrenciesUpdates(userWalletId) - .map, Either>> { it.right() } - .catch { emit(Error.DataError(it).left()) } - .onEmpty { emit(Error.EmptyCurrencies.left()) } - } - private suspend fun Raise.getMultiCurrencyWalletCurrency(currencyId: CryptoCurrency.ID): CryptoCurrency { return Either.catch { currenciesRepository.getMultiCurrencyWalletCurrency( diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt deleted file mode 100644 index b5cd747c50..0000000000 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/GetTokenListUseCaseTest.kt +++ /dev/null @@ -1,322 +0,0 @@ -package com.tangem.domain.tokens - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.domain.core.error.DataError -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.tokens.mock.MockNetworks -import com.tangem.domain.tokens.mock.MockQuotes -import com.tangem.domain.tokens.mock.MockTokenLists -import com.tangem.domain.tokens.mock.MockTokens -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.NetworkStatus -import com.tangem.domain.tokens.model.Quote -import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.tokens.repository.MockCurrenciesRepository -import com.tangem.domain.tokens.repository.MockNetworksRepository -import com.tangem.domain.tokens.repository.MockQuotesRepository -import com.tangem.domain.tokens.repository.MockStakingRepository -import com.tangem.domain.wallets.models.UserWalletId -import junit.framework.TestCase.assertEquals -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.test.runTest -import org.junit.Ignore -import org.junit.Test - -internal class GetTokenListUseCaseTest { - - private val userWalletId = UserWalletId(value = null) - - @Ignore - @Test - fun `when list ungrouped and unsorted then correct token list should be returned`() = runTest { - // Given - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.right(), - MockTokenLists.failedUngroupedTokenList.right(), - ) - - val useCase = getUseCase( - isGrouped = flowOf(false.right()), - isSortedByBalance = flowOf(false.right()), - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when tokens getting failed then error should be received`() = runTest { - // Given - val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(tokens = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes getting failed then token list without quotes should be received`() = runTest { - // Given - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.right(), - MockTokenLists.noQuotesUngroupedTokenList.right(), - ) - - val useCase = getUseCase( - quotes = flowOf(DataError.NetworkError.NoInternetConnection.left()), - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when grouping type getting failed then error should be received`() = runTest { - // Given - val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(isGrouped = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when sorting type getting failed then error should be received`() = runTest { - // Given - val expectedResult = TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left() - - val useCase = getUseCase(isSortedByBalance = flowOf(DataError.NetworkError.NoInternetConnection.left())) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Ignore - @Test - fun `when tokens getting failed on second emit then error should be received`() = runTest { - // Given - val error = DataError.NetworkError.NoInternetConnection.left() - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.right(), - MockTokenLists.failedUngroupedTokenList.right(), - TokenListError.DataError(DataError.NetworkError.NoInternetConnection).left(), - ) - - val useCase = getUseCase( - tokens = flowOf( - MockTokens.tokens.right(), - error, - ).map { delay(timeMillis = 1_000); it }, - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 3) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Ignore - @Test - fun `when list grouped then correct token list should be received`() = runTest { - val expectedResult = listOf( - MockTokenLists.loadingGroupedTokenList.right(), - MockTokenLists.failedGroupedTokenList.right(), - ) - - val useCase = getUseCase(isGrouped = flowOf(true.right())) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when list is sorted and ungrouped then correct token list should be received`() = runTest { - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(), - MockTokenLists.sortedUngroupedTokenList.right(), - ) - - val useCase = getUseCase( - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - isGrouped = flowOf(false.right()), - isSortedByBalance = flowOf(true.right()), - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when list is sorted and grouped then correct token list should be received`() = runTest { - val expectedResult = listOf( - MockTokenLists.loadingGroupedTokenList.copy(sortedBy = TokenList.SortType.BALANCE).right(), - MockTokenLists.sortedGroupedTokenList.right(), - ) - - val useCase = getUseCase( - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - isGrouped = flowOf(true.right()), - isSortedByBalance = flowOf(true.right()), - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when tokens is empty then not initialized token list should be received`() = runTest { - val expectedResult = MockTokenLists.emptyTokenList.right() - - val useCase = getUseCase(tokens = flowOf(emptyList().right())) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when tokens flow is empty then error should be received`() = runTest { - val expectedResult = TokenListError.EmptyTokens.left() - - val useCase = getUseCase(tokens = flowOf()) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when networks statuses flow is empty then error should be received`() = runTest { - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.right(), - TokenListError.EmptyTokens.left(), - ) - - val useCase = getUseCase(statuses = flowOf()) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when networks statuses is empty then loading token list should be received`() = runTest { - val expectedResult = MockTokenLists.loadingUngroupedTokenList.right() - - val useCase = getUseCase(statuses = flowOf(emptySet().right())) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes flow is empty then list without quotes should be received`() = runTest { - val expectedResult = listOf( - MockTokenLists.loadingUngroupedTokenList.right(), - MockTokenLists.noQuotesUngroupedTokenList.right(), - ) - - val useCase = getUseCase( - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - quotes = flowOf(emptySet().right()), - ) - - // When - val result = useCase.launch(userWalletId) - .take(count = 2) - .toList() - - // Then - assertEquals(expectedResult, result) - } - - @Test - fun `when quotes is empty and statuses verified then loading token list should be received`() = runTest { - val expectedResult = MockTokenLists.loadingUngroupedTokenList.right() - - val useCase = getUseCase( - statuses = flowOf(MockNetworks.verifiedNetworksStatuses.right()), - quotes = flowOf(emptySet().right()), - ) - - // When - val result = useCase.launch(userWalletId).first() - - // Then - assertEquals(expectedResult, result) - } - - private fun getUseCase( - tokens: Flow>> = flowOf(MockTokens.tokens.right()), - quotes: Flow>> = flowOf(MockQuotes.quotes.right()), - statuses: Flow>> = flowOf(MockNetworks.errorNetworksStatuses.right()), - isGrouped: Flow> = flowOf(MockTokenLists.isGrouped.right()), - isSortedByBalance: Flow> = flowOf(MockTokenLists.isSortedByBalance.right()), - ) = GetTokenListUseCase( - currenciesRepository = MockCurrenciesRepository( - sortTokensResult = Unit.right(), - removeCurrencyResult = Unit.right(), - token = MockTokens.token1.right(), - tokens = tokens, - isGrouped = isGrouped, - isSortedByBalance = isSortedByBalance, - ), - quotesRepository = MockQuotesRepository(quotes), - networksRepository = MockNetworksRepository(statuses), - stakingRepository = MockStakingRepository(), - ) -} \ No newline at end of file diff --git a/features/details/impl/build.gradle.kts b/features/details/impl/build.gradle.kts index c1f2899eff..c6694323ba 100644 --- a/features/details/impl/build.gradle.kts +++ b/features/details/impl/build.gradle.kts @@ -37,11 +37,15 @@ dependencies { implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) implementation(projects.domain.walletConnect) + implementation(projects.domain.balanceHiding) + implementation(projects.domain.balanceHiding.models) implementation(projects.domain.legacy) /* SDK */ // TODO: For TangemError model, should be removed after card domain scanning refactoring implementation(deps.tangem.card.core) + // For image resolving + implementation(deps.tangem.blockchain) /* AndroidX */ implementation(deps.androidx.fragment.ktx) @@ -55,6 +59,7 @@ dependencies { implementation(deps.compose.foundation) implementation(deps.compose.material3) implementation(deps.compose.shimmer) + implementation(deps.compose.coil) /* DI */ implementation(deps.hilt.android) diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt index a8906b5061..92b6f46de3 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/preview/PreviewUserWalletListComponent.kt @@ -19,23 +19,26 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent { userWallets = persistentListOf( UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_1".encodeToByteArray()), - name = "My Wallet", + name = stringReference("My Wallet"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_wallet_2_211_343, + imageUrl = "", + isEnabled = true, onClick = {}, ), UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_2".encodeToByteArray()), - name = "Old wallet", + name = stringReference("Old wallet"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_note_eth_211_343, + imageUrl = "", + isEnabled = true, onClick = {}, ), UserWalletListUM.UserWalletUM( id = UserWalletId("user_wallet_3".encodeToByteArray()), - name = "Multi Card", + name = stringReference("Multi Card"), information = getInformation(3, "4 496,75 $"), - imageResId = R.drawable.ill_card_note_bnb_211_343, + imageUrl = "", + isEnabled = false, onClick = {}, ), ), diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt index d6f947b063..1569f48efb 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/entity/UserWalletListUM.kt @@ -1,10 +1,11 @@ package com.tangem.features.details.entity -import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.wallets.models.UserWalletId import kotlinx.collections.immutable.ImmutableList +@Immutable internal data class UserWalletListUM( val userWallets: ImmutableList, val isWalletSavingInProgress: Boolean, @@ -12,12 +13,13 @@ internal data class UserWalletListUM( val onAddNewWalletClick: () -> Unit, ) { + @Immutable data class UserWalletUM( val id: UserWalletId, - val name: String, + val name: TextReference, val information: TextReference, - @DrawableRes - val imageResId: Int, + val imageUrl: String, + val isEnabled: Boolean, val onClick: () -> Unit, ) } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt index cb235d20fa..794ef632c3 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/UserWalletListBlock.kt @@ -10,15 +10,21 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.details.entity.UserWalletListUM import com.tangem.features.details.impl.R +import com.tangem.features.details.ui.coil.RotationTransformation @Composable internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) { @@ -46,6 +52,7 @@ private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modif BlockCard( modifier = modifier, onClick = model.onClick, + enabled = model.isEnabled, ) { Row( modifier = Modifier @@ -55,39 +62,81 @@ private fun UserWalletItem(model: UserWalletListUM.UserWalletUM, modifier: Modif verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { - Image( - modifier = Modifier - .width(TangemTheme.dimens.size24) - .height(TangemTheme.dimens.size36), - painter = painterResource(id = model.imageResId), - contentScale = ContentScale.FillBounds, - contentDescription = null, + Image(imageUrl = model.imageUrl) + NameAndInfo( + name = model.name, + information = model.information, ) - - Column( - modifier = Modifier.heightIn(min = TangemTheme.dimens.size40), - horizontalAlignment = Alignment.Start, - verticalArrangement = Arrangement.SpaceEvenly, - ) { - Text( - text = model.name, - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = model.information.resolveReference(), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } } } } +@Composable +private fun NameAndInfo(name: TextReference, information: TextReference, modifier: Modifier = Modifier) { + Column( + modifier = modifier.heightIn(min = TangemTheme.dimens.size40), + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.SpaceEvenly, + ) { + Text( + text = name.resolveReference(), + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + AnimatedContent( + targetState = information.resolveReference(), + label = "User wallet information", + ) { information -> + Text( + text = information, + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } +} + +@Composable +private fun Image(imageUrl: String, modifier: Modifier = Modifier) { + val imageModifier = modifier + .width(TangemTheme.dimens.size24) + .height(TangemTheme.dimens.size36) + .clip(TangemTheme.shapes.roundedCornersSmall) + + SubcomposeAsyncImage( + modifier = imageModifier, + model = ImageRequest.Builder(LocalContext.current) + .transformations(RotationTransformation(angle = 90f)) + .size( + width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }, + height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() }, + ) + .data(imageUrl) + .crossfade(enable = true) + .allowHardware(enable = false) + .build(), + loading = { + RectangleShimmer( + modifier = imageModifier, + radius = TangemTheme.dimens.size2, + ) + }, + error = { + Image( + modifier = imageModifier, + painter = painterResource(id = R.drawable.img_card_wallet_2_gray_22_36), + contentDescription = null, + ) + }, + contentDescription = null, + ) +} + @Composable private fun AddWalletButton( text: TextReference, @@ -110,6 +159,7 @@ private fun AddWalletButton( AnimatedContent( modifier = Modifier.size(TangemTheme.dimens.size24), targetState = isInProgress, + label = "Add wallet progress", ) { isInProgress -> if (isInProgress) { CircularProgressIndicator( diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt new file mode 100644 index 0000000000..ca7f0929d0 --- /dev/null +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/ui/coil/RotationTransformation.kt @@ -0,0 +1,22 @@ +package com.tangem.features.details.ui.coil + +import android.graphics.Bitmap +import android.graphics.Matrix +import coil.size.Size +import coil.transform.Transformation + +internal class RotationTransformation(private val angle: Float) : Transformation { + + override val cacheKey: String = "rotate:$angle" + + override suspend fun transform(input: Bitmap, size: Size): Bitmap { + val matrix = Matrix().apply { + val centerX = input.width / 2f + val centerY = input.height / 2f + + postRotate(angle, centerX, centerY) + } + + return Bitmap.createBitmap(input, 0, 0, input.width, input.height, matrix, true) + } +} \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt index 992fee20eb..94fc6e00b1 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletMappers.kt @@ -1,9 +1,6 @@ package com.tangem.features.details.utils -import androidx.annotation.DrawableRes -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.scan.CardDTO @@ -12,6 +9,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM import com.tangem.features.details.impl.R +import com.tangem.utils.Strings.STARS import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -19,11 +17,15 @@ internal fun List.toUiModels( onClick: (UserWalletId) -> Unit, appCurrency: AppCurrency? = null, balances: Map = emptyMap(), + isLoading: Boolean = true, + isBalancesHidden: Boolean = false, ): ImmutableList = this.map { model -> val balance = balances[model.walletId] model.mapToUiModel( balance = balance, appCurrency = appCurrency, + isLoading = isLoading, + isBalanceHidden = isBalancesHidden, onClick = { onClick(model.walletId) }, ) }.toImmutableList() @@ -31,22 +33,52 @@ internal fun List.toUiModels( private fun UserWallet.mapToUiModel( balance: TotalFiatBalance?, appCurrency: AppCurrency?, + isLoading: Boolean, + isBalanceHidden: Boolean, onClick: () -> Unit, ): UserWalletUM = UserWalletUM( id = walletId, - name = name, - information = getInfo(appCurrency, balance), - imageResId = resolveImage(), + name = stringReference(name), + information = getInfo( + appCurrency = appCurrency, + balance = balance, + isBalanceHidden = isBalanceHidden, + isLoading = isLoading, + ), + imageUrl = artworkUrl, + isEnabled = !isLocked, onClick = onClick, ) -private fun UserWallet.getInfo(appCurrency: AppCurrency?, balance: TotalFiatBalance?): TextReference { +private fun UserWallet.getInfo( + appCurrency: AppCurrency?, + balance: TotalFiatBalance?, + isBalanceHidden: Boolean, + isLoading: Boolean, +): TextReference { + val dividerRef = stringReference(value = " • ") + val cardCount = getCardCount() val cardCountRef = TextReference.PluralRes( id = R.plurals.card_label_card_count, count = cardCount, formatArgs = wrappedList(cardCount), ) + + return when { + isLocked -> combinedReference(cardCountRef, dividerRef, resourceReference(R.string.common_locked)) + isLoading -> cardCountRef + isBalanceHidden -> combinedReference(cardCountRef, dividerRef, stringReference(STARS)) + else -> getBalanceInfo(balance, appCurrency, cardCountRef, dividerRef) + } +} + +private fun getBalanceInfo( + balance: TotalFiatBalance?, + appCurrency: AppCurrency?, + cardCountRef: TextReference, + dividerRef: TextReference, +): TextReference { val amount = when (balance) { is TotalFiatBalance.Loaded -> balance.amount.takeIf { balance.isAllAmountsSummarized } is TotalFiatBalance.Failed, @@ -56,16 +88,15 @@ private fun UserWallet.getInfo(appCurrency: AppCurrency?, balance: TotalFiatBala } return if (amount != null && appCurrency != null) { - val divider = stringReference(value = " • ") val formattedAmount = BigDecimalFormatter.formatFiatAmount( fiatAmount = amount, fiatCurrencyCode = appCurrency.code, fiatCurrencySymbol = appCurrency.symbol, ) val amountRef = stringReference(formattedAmount) - TextReference.Combined(wrappedList(cardCountRef, divider, amountRef)) + combinedReference(cardCountRef, dividerRef, amountRef) } else { - cardCountRef + combinedReference(cardCountRef, dividerRef, stringReference(BigDecimalFormatter.EMPTY_BALANCE_SIGN)) } } @@ -75,10 +106,4 @@ private fun UserWallet.getCardCount() = when (val status = scanResponse.card.bac is CardDTO.BackupStatus.NoBackup, null, -> 1 -} - -@DrawableRes -private fun UserWallet.resolveImage(): Int { - // TODO: Implement image resolving [REDACTED_JIRA] - return R.drawable.ill_card_wallet_2_211_343 } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt index 5377be02ea..e141c4034e 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/utils/UserWalletsFetcher.kt @@ -10,6 +10,8 @@ import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.error.SelectedAppCurrencyError import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.balancehiding.BalanceHidingSettings +import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.lce import com.tangem.domain.core.utils.getOrElse @@ -24,10 +26,7 @@ import com.tangem.features.details.entity.UserWalletListUM.UserWalletUM import com.tangem.features.details.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.flow.* import javax.inject.Inject @ComponentScoped @@ -35,6 +34,7 @@ internal class UserWalletsFetcher @Inject constructor( getWalletsUseCase: GetWalletsUseCase, private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val router: Router, private val messageSender: UiMessageSender, ) { @@ -44,10 +44,16 @@ internal class UserWalletsFetcher @Inject constructor( emit(wallets.toUiModels(onClick = ::navigateToWalletSettings)) combine( - getSelectedAppCurrencyUseCase(), - getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)), - ) { maybeAppCurrency, maybeBalances -> - val models = createUiModels(wallets, maybeAppCurrency, maybeBalances).getOrElse( + getSelectedAppCurrencyUseCase().distinctUntilChanged(), + getBalanceHidingSettingsUseCase().distinctUntilChanged(), + getWalletTotalBalanceUseCase(wallets.map(UserWallet::walletId)).distinctUntilChanged(), + ) { maybeAppCurrency, balanceHidingSettings, maybeBalances -> + val models = createUiModels( + wallets = wallets, + maybeAppCurrency = maybeAppCurrency, + maybeBalances = maybeBalances, + balanceHidingSettings = balanceHidingSettings, + ).getOrElse( ifLoading = { return@combine }, ifError = { val message = resourceReference(R.string.common_unknown_error) @@ -65,10 +71,11 @@ internal class UserWalletsFetcher @Inject constructor( wallets: List, maybeAppCurrency: Either, maybeBalances: Lce>, + balanceHidingSettings: BalanceHidingSettings, ): Lce> = lce { val balances = withError( transform = { Error.UnableToGetBalances }, - block = { maybeBalances.bind() }, + block = { maybeBalances.bindOrNull().orEmpty() }, ) val appCurrency = withError( transform = { Error.UnableToGetAppCurrency }, @@ -79,6 +86,8 @@ internal class UserWalletsFetcher @Inject constructor( appCurrency = appCurrency, balances = balances, onClick = ::navigateToWalletSettings, + isBalancesHidden = balanceHidingSettings.isBalanceHidden, + isLoading = maybeBalances.isLoading(), ) } diff --git a/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png deleted file mode 100644 index 033efd2699..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_note_bnb_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png deleted file mode 100644 index b338ee1eb5..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_note_eth_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png b/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png deleted file mode 100644 index 9625b941a1..0000000000 Binary files a/features/details/impl/src/main/res/drawable/ill_card_wallet_2_211_343.png and /dev/null differ diff --git a/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml b/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml new file mode 100644 index 0000000000..977d693e60 --- /dev/null +++ b/features/details/impl/src/main/res/drawable/img_card_wallet_2_gray_22_36.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt deleted file mode 100644 index a358685961..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/di/FeatureTogglesModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.feature.wallet.di - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager -import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object FeatureTogglesModule { - - @Provides - @Singleton - fun provideWalletFeatureToggles(featureTogglesManager: FeatureTogglesManager): WalletFeatureToggles { - return WalletFeatureToggles(featureTogglesManager) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt deleted file mode 100644 index e0881eead6..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/featuretoggle/WalletFeatureToggles.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.feature.wallet.featuretoggle - -import com.tangem.core.featuretoggle.manager.FeatureTogglesManager - -internal class WalletFeatureToggles( - private val featureTogglesManager: FeatureTogglesManager, -) { - - val isTokenListLceFlowEnabled: Boolean - get() = featureTogglesManager.isFeatureEnabled("TOKEN_LIST_LCE_ENABLED") -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt index 2540a508f8..93c0aab777 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/organizetokens/OrganizeTokensViewModel.kt @@ -13,9 +13,7 @@ import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase import com.tangem.domain.tokens.ToggleTokenListSortingUseCase import com.tangem.domain.tokens.model.TokenList -import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState @@ -42,7 +40,6 @@ internal class OrganizeTokensViewModel @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val analyticsEventsHandler: AnalyticsEventHandler, - private val walletFeatureToggles: WalletFeatureToggles, private val dispatchers: CoroutineDispatcherProvider, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, OrganizeTokensIntents { @@ -173,34 +170,21 @@ internal class OrganizeTokensViewModel @Inject constructor( } private suspend fun getTokenList(): TokenList? { - return if (walletFeatureToggles.isTokenListLceFlowEnabled) { - val tokenList = getTokenListUseCase.launchLce(userWalletId) - .transform { maybeTokenList -> - val tokenList = maybeTokenList.getOrElse( - ifLoading = { return@transform }, - ifError = { error -> - stateHolder.updateStateWithError(error) + val tokenList = getTokenListUseCase.launch(userWalletId) + .transform { maybeTokenList -> + val tokenList = maybeTokenList.getOrElse( + ifLoading = { return@transform }, + ifError = { error -> + stateHolder.updateStateWithError(error) - return@transform - }, - ) + return@transform + }, + ) - emit(tokenList) - } - - tokenList.firstOrNull() - } else { - val maybeTokenList = getTokenListUseCase.launch(userWalletId) - .first { maybeTokenList -> - maybeTokenList.getOrNull()?.totalFiatBalance !is TotalFiatBalance.Loading - } - - maybeTokenList.getOrElse { error -> - stateHolder.updateStateWithError(error) - - null + emit(tokenList) } - } + + return tokenList.firstOrNull() } private fun bootstrapDragAndDropUpdates() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index b0513c7656..e3bef9a24d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -1,8 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.domain -import arrow.core.Either import com.tangem.domain.common.CardTypesResolver import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.core.lce.Lce import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.promo.PromoBanner import com.tangem.domain.settings.IsReadyToShowRateAppUseCase @@ -23,7 +23,6 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flow import javax.inject.Inject import kotlin.collections.count @@ -47,11 +46,11 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( val promoFlow = flow { emit(promoRepository.getOkxPromoBanner()) } return combine( - flow = getTokenListUseCase.launch(userWallet.walletId).conflate(), - flow2 = isReadyToShowRateAppUseCase().conflate(), - flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), - flow4 = shouldShowSwapPromoWalletUseCase().conflate(), - flow5 = promoFlow.conflate(), + flow = getTokenListUseCase.launch(userWallet.walletId), + flow2 = isReadyToShowRateAppUseCase(), + flow3 = isNeedToBackupUseCase(userWallet.walletId), + flow4 = shouldShowSwapPromoWalletUseCase(), + flow5 = promoFlow, ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo, promoBanner -> readyForRateAppNotification = true @@ -113,7 +112,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private fun MutableList.addInformationalNotifications( cardTypesResolver: CardTypesResolver, - maybeTokenList: Either, + maybeTokenList: Lce, clickIntents: WalletClickIntents, ) { addIf( @@ -125,7 +124,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( } private fun MutableList.addMissingAddressesNotification( - maybeTokenList: Either, + maybeTokenList: Lce, clickIntents: WalletClickIntents, ) { val currencies = maybeTokenList.getMissingAddressCurrencies() @@ -141,26 +140,23 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) } - private fun Either.getMissingAddressCurrencies(): List { - return fold( - ifLeft = { emptyList() }, - ifRight = { tokenList -> - val currencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } + private fun Lce.getMissingAddressCurrencies(): List { + val tokenList = getOrNull(isPartialContentAccepted = false) ?: return emptyList() - currencies - .filter { it.value is CryptoCurrencyStatus.MissedDerivation } - .map(CryptoCurrencyStatus::currency) - }, - ) + val currencies = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) + is TokenList.Ungrouped -> tokenList.currencies + is TokenList.Empty -> emptyList() + } + + return currencies + .filter { it.value is CryptoCurrencyStatus.MissedDerivation } + .map(CryptoCurrencyStatus::currency) } private fun MutableList.addWarningNotifications( cardTypesResolver: CardTypesResolver, - tokenList: Either, + tokenList: Lce, isNeedToBackup: Boolean, clickIntents: WalletClickIntents, ) { @@ -182,19 +178,16 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( ) } - private fun Either.hasUnreachableNetworks(): Boolean { - return fold( - ifLeft = { false }, - ifRight = { tokenList -> - val currencies = when (tokenList) { - is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) - is TokenList.Ungrouped -> tokenList.currencies - is TokenList.Empty -> emptyList() - } + private fun Lce.hasUnreachableNetworks(): Boolean { + val tokenList = getOrNull(isPartialContentAccepted = false) ?: return false - currencies.any { it.value is CryptoCurrencyStatus.Unreachable } - }, - ) + val currencies = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) + is TokenList.Ungrouped -> tokenList.currencies + is TokenList.Empty -> emptyList() + } + + return currencies.any { it.value is CryptoCurrencyStatus.Unreachable } } private fun MutableList.addRateTheAppNotification( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 0a3310ce92..7dc59658c4 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -5,7 +5,6 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory @@ -28,7 +27,6 @@ internal class MultiWalletContentLoader( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, - private val walletFeatureToggles: WalletFeatureToggles, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { @@ -42,7 +40,6 @@ internal class MultiWalletContentLoader( walletWithFundsChecker = walletWithFundsChecker, getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, - walletFeatureToggles = walletFeatureToggles, applyTokenListSortingUseCase = applyTokenListSortingUseCase, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 13dbadebb3..1b405c8af6 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -5,7 +5,6 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory @@ -26,7 +25,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, - private val walletFeatureToggles: WalletFeatureToggles, private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { @@ -42,7 +40,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( getMultiWalletWarningsFactory = getMultiWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, - walletFeatureToggles = walletFeatureToggles, runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 6b19dfd81d..12d5115bcf 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -3,7 +3,6 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow -import com.tangem.domain.core.utils.toLce import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase @@ -12,19 +11,16 @@ import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet -import com.tangem.feature.wallet.featuretoggle.WalletFeatureToggles import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents -import kotlinx.coroutines.flow.map @Suppress("LongParameterList") internal class MultiWalletTokenListSubscriber( private val userWallet: UserWallet, private val getTokenListUseCase: GetTokenListUseCase, private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, - private val walletFeatureToggles: WalletFeatureToggles, stateHolder: WalletStateController, clickIntents: WalletClickIntents, tokenListAnalyticsSender: TokenListAnalyticsSender, @@ -42,11 +38,7 @@ internal class MultiWalletTokenListSubscriber( ) { override fun tokenListFlow(): LceFlow { - return if (walletFeatureToggles.isTokenListLceFlowEnabled) { - getTokenListUseCase.launchLce(userWallet.walletId) - } else { - getTokenListUseCase.launch(userWallet.walletId).map { it.toLce() } - } + return getTokenListUseCase.launch(userWallet.walletId) } override suspend fun onTokenListReceived(maybeTokenList: Lce) {