diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 29b1eb08c8..404a978379 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -31,6 +31,7 @@ import com.tangem.domain.onboarding.repository.OnboardingRepository import com.tangem.domain.onramp.FetchHotCryptoUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.promo.models.StoryContentIds +import com.tangem.domain.quotes.multi.MultiQuoteUpdater import com.tangem.domain.settings.DeleteDeprecatedLogsUseCase import com.tangem.domain.settings.IncrementAppLaunchCounterUseCase import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase @@ -86,6 +87,7 @@ internal class MainViewModel @Inject constructor( private val updateRemoteWalletsInfoUseCase: UpdateRemoteWalletsInfoUseCase, private val sendPushTokenUseCase: SendPushTokenUseCase, private val apiConfigsManager: ApiConfigsManager, + private val multiQuoteUpdater: MultiQuoteUpdater, routingFeatureToggle: RoutingFeatureToggle, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, ) : ViewModel() { @@ -116,6 +118,8 @@ internal class MainViewModel @Inject constructor( viewModelScope.launch { incrementAppLaunchCounterUseCase() } + multiQuoteUpdater.subscribe() + observeFlips() displayBalancesHidingStatusToast() displayHiddenBalancesModalNotification() @@ -131,6 +135,12 @@ internal class MainViewModel @Inject constructor( } } + override fun onCleared() { + super.onCleared() + + multiQuoteUpdater.unsubscribe() + } + fun checkForUnfinishedBackup() { viewModelScope.launch(dispatchers.main) { val onboardingScanResponse = onboardingRepository.getUnfinishedFinalizeOnboarding() ?: return@launch diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 32256ffa87..ce11ee65f0 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -18,6 +18,7 @@ import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason import com.tangem.domain.tokens.repository.CurrenciesRepository +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.utils.Provider @@ -55,7 +56,7 @@ internal class DefaultRampManager( } override suspend fun availableForSell( - userWalletId: UserWalletId, + userWallet: UserWallet, status: CryptoCurrencyStatus, ): Either { return either { @@ -68,7 +69,7 @@ internal class DefaultRampManager( catch = { raise(ScenarioUnavailabilityReason.NotSupportedBySellService(status.currency.name)) }, ) - val reason = getSendUnavailabilityReason(userWalletId, status) + val reason = getSendUnavailabilityReason(userWallet = userWallet, cryptoCurrencyStatus = status) ensure(condition = reason is ScenarioUnavailabilityReason.None) { when (reason) { @@ -205,14 +206,13 @@ internal class DefaultRampManager( } private suspend fun getSendUnavailabilityReason( - userWalletId: UserWalletId, + userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus, ): ScenarioUnavailabilityReason { val coinStatus = getNetworkCoinStatusUseCase.invokeSync( - userWalletId = userWalletId, + userWallet = userWallet, networkId = cryptoCurrencyStatus.currency.network.id, derivationPath = cryptoCurrencyStatus.currency.network.derivationPath, - isSingleWalletWithTokens = false, ).getOrNull() return when { diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt index 698ec1ccf5..9ad35360a5 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/UserWalletItem.kt @@ -12,7 +12,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.vectorResource @@ -178,32 +180,42 @@ fun CardImage(imageState: UserWalletItemUM.ImageState, modifier: Modifier = Modi ) } is UserWalletItemUM.ImageState.Image -> { - SubcomposeAsyncImage( - modifier = imageModifier, - model = ImageRequest.Builder(LocalContext.current) - .size( - width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }, - height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() }, - ) - .data(imageState.artwork.verifiedArtwork?.toByteArray() ?: imageState.artwork.defaultUrl) - .crossfade(enable = true) - .allowHardware(enable = false) - .build(), - loading = { - RectangleShimmer( - modifier = imageModifier, - radius = TangemTheme.dimens.size2, - ) - }, - error = { - Image( - modifier = imageModifier.then(Modifier.rotate(90F)), - imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36), - contentDescription = null, - ) - }, - contentDescription = null, - ) + val verifiedArtwork = imageState.artwork.verifiedArtwork + if (verifiedArtwork != null) { + Image( + modifier = imageModifier, + painter = BitmapPainter(verifiedArtwork), + contentScale = ContentScale.Fit, + contentDescription = null, + ) + } else { + SubcomposeAsyncImage( + modifier = imageModifier, + model = ImageRequest.Builder(LocalContext.current) + .size( + width = with(LocalDensity.current) { TangemTheme.dimens.size36.roundToPx() }, + height = with(LocalDensity.current) { TangemTheme.dimens.size24.roundToPx() }, + ) + .data(imageState.artwork.defaultUrl) + .crossfade(enable = true) + .allowHardware(enable = false) + .build(), + loading = { + RectangleShimmer( + modifier = imageModifier, + radius = TangemTheme.dimens.size2, + ) + }, + error = { + Image( + modifier = imageModifier.then(Modifier.rotate(90F)), + imageVector = ImageVector.vectorResource(R.drawable.img_card_wallet_2_gray_22_36), + contentDescription = null, + ) + }, + contentDescription = null, + ) + } } } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt new file mode 100644 index 0000000000..3278017126 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/ArtworkUMConverter.kt @@ -0,0 +1,23 @@ +package com.tangem.common.ui.userwallet.converter + +import android.graphics.BitmapFactory +import androidx.compose.ui.graphics.asImageBitmap +import com.tangem.core.ui.components.artwork.ArtworkUM +import com.tangem.domain.models.ArtworkModel +import com.tangem.utils.converter.Converter +import javax.inject.Inject + +class ArtworkUMConverter @Inject constructor() : Converter { + + override fun convert(value: ArtworkModel): ArtworkUM { + val bimap = try { + value.verifiedArtwork?.let { BitmapFactory.decodeByteArray(it, 0, it.size) } + } catch (ignore: Exception) { + null + } + return ArtworkUM( + verifiedArtwork = bimap?.asImageBitmap(), + defaultUrl = value.defaultUrl, + ) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt index 875f1044f8..534a8de836 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/userwallet/converter/UserWalletItemUMConverter.kt @@ -2,7 +2,6 @@ package com.tangem.common.ui.userwallet.converter import com.tangem.common.ui.R import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList @@ -38,6 +37,8 @@ class UserWalletItemUMConverter( private val artwork: ArtworkModel? = null, ) : Converter { + private val artworkUMConverter = ArtworkUMConverter() + override fun convert(value: UserWallet): UserWalletItemUM { return with(value) { UserWalletItemUM( @@ -49,7 +50,7 @@ class UserWalletItemUMConverter( endIcon = endIcon, onClick = { onClick(value.walletId) }, imageState = artwork?.let { - UserWalletItemUM.ImageState.Image(ArtworkUM(it.verifiedArtwork, it.defaultUrl)) + UserWalletItemUM.ImageState.Image(artworkUMConverter.convert(it)) } ?: UserWalletItemUM.ImageState.Loading, ) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index be0ea9267f..1dd4445077 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -220,5 +220,7 @@ sealed class AnalyticsParam { const val COLLECTIONS = "Collections" const val NFT = "Nft" const val NONCE = "Nonce" + const val STANDARD = "Standard" + const val NO_COLLECTION = "No collection" } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt index bd93af8f19..b06cb3be83 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt @@ -16,7 +16,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter NFTAsset.Identifier.Solana( tokenAddress = value.tokenAddress, - cnft = value.cnft, + tokenStandard = value.tokenStandard, ) is SdkNFTAsset.Identifier.Unknown -> NFTAsset.Identifier.Unknown } @@ -32,7 +32,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter SdkNFTAsset.Identifier.Solana( tokenAddress = value.tokenAddress, - cnft = value.cnft, + tokenStandard = value.tokenStandard, ) is NFTAsset.Identifier.Unknown -> SdkNFTAsset.Identifier.Unknown } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt index 5c1f568b02..a0e9d2e0f7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/artwork/ArtworkUM.kt @@ -1,17 +1,10 @@ package com.tangem.core.ui.components.artwork import androidx.compose.runtime.Immutable -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList +import androidx.compose.ui.graphics.ImageBitmap @Immutable data class ArtworkUM( - val verifiedArtwork: ImmutableList? = null, + val verifiedArtwork: ImageBitmap? = null, val defaultUrl: String, -) { - - constructor(bytes: ByteArray?, defaultUrl: String) : this( - verifiedArtwork = bytes?.toList()?.toImmutableList(), - defaultUrl = defaultUrl, - ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt b/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt index deb556fc61..59ee027076 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/fetcher/CommonNetworkStatusFetcher.kt @@ -14,6 +14,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton /** * Common implementation of network status fetcher @@ -24,6 +25,7 @@ import javax.inject.Inject * [REDACTED_AUTHOR] */ +@Singleton internal class CommonNetworkStatusFetcher @Inject constructor( private val walletManagersFacade: WalletManagersFacade, private val networksStatusesStore: NetworksStatusesStore, diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt index 8019b9fcc8..8c21c90072 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/DefaultMultiYieldBalanceFetcher.kt @@ -165,7 +165,7 @@ internal class DefaultMultiYieldBalanceFetcher @Inject constructor( val yieldBalances = coroutineScope { requests // TODO: in the future, consider optimizing this part - .chunked(size = 16) // StakeKitApi limitation: no more than 16 requests at the same time + .chunked(size = 15) // StakeKitApi limitation: no more than 15 requests at the same time .map { async(dispatchers.io) { stakeKitApi.getMultipleYieldBalances(it).bind() } } diff --git a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt index 0f10ddbc84..ece1e97c5b 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/exchange/RampStateManager.kt @@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow @@ -23,11 +24,11 @@ interface RampStateManager { /** * Check if [CryptoCurrency] is available for sell * - * @param userWalletId id of multi-currency wallet - * @param status crypto currency status + * @param userWallet user wallet + * @param status crypto currency status */ suspend fun availableForSell( - userWalletId: UserWalletId, + userWallet: UserWallet, status: CryptoCurrencyStatus, ): Either diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt index f30b865df7..f83aec887a 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt @@ -68,7 +68,7 @@ data class NFTAsset( @Serializable data class Solana( val tokenAddress: String, - val cnft: Boolean, + val tokenStandard: Int?, ) : Identifier() { override val stringValue: String = tokenAddress } diff --git a/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt b/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt index 34bb1ab6f7..9ee3357e17 100644 --- a/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt +++ b/domain/nft/src/main/kotlin/com/tangem/domain/nft/analytics/NFTAnalyticsEvent.kt @@ -2,9 +2,12 @@ package com.tangem.domain.nft.analytics import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN +import com.tangem.core.analytics.models.AnalyticsParam.Key.STANDARD import com.tangem.core.analytics.models.AnalyticsParam.Key.COLLECTIONS import com.tangem.core.analytics.models.AnalyticsParam.Key.NFT +import com.tangem.core.analytics.models.AnalyticsParam.Key.NO_COLLECTION import com.tangem.core.analytics.models.AnalyticsParam.Key.STATE +import kotlin.collections.buildMap sealed class NFTAnalyticsEvent( event: String, @@ -17,19 +20,21 @@ sealed class NFTAnalyticsEvent( data class NFTListScreenOpened( val state: State, + val collectionsCount: Int, + val allAssetsCount: Int, + val noCollectionAssetsCount: Int, ) : NFTAnalyticsEvent( event = "NFT List Screen Opened", params = buildMap { put(STATE, state.value) - if (state is State.Full) { - put(COLLECTIONS, state.collectionsCount.toString()) - put(NFT, state.assetsCount.toString()) - } + put(COLLECTIONS, collectionsCount.toString()) + put(NFT, allAssetsCount.toString()) + put(NO_COLLECTION, noCollectionAssetsCount.toString()) }, ) { - sealed class State(val value: String) { - data object Empty : State("Empty") - data class Full(val assetsCount: Int, val collectionsCount: Int) : State("Full") + enum class State(val value: String) { + Empty("Empty"), + Full("Full"), } } @@ -52,7 +57,16 @@ sealed class NFTAnalyticsEvent( object Details { data class ScreenOpened( private val blockchain: String, - ) : NFTAnalyticsEvent(event = "NFT Details Screen Opened", params = mapOf(BLOCKCHAIN to blockchain)) + private val standard: String?, + ) : NFTAnalyticsEvent( + event = "NFT Details Screen Opened", + params = buildMap { + put(BLOCKCHAIN, blockchain) + standard?.let { + put(STANDARD, it) + } + }, + ) data object ButtonReadMore : NFTAnalyticsEvent(event = "Button - Read More") data object ButtonSeeAll : NFTAnalyticsEvent(event = "Button - See All") diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt index d890e9269e..c4d88e77f7 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/FetchCurrencyStatusUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either import arrow.core.raise.Raise import arrow.core.raise.catch import arrow.core.raise.either +import arrow.core.right import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.networks.single.SingleNetworkStatusFetcher @@ -51,17 +52,18 @@ class FetchCurrencyStatusUseCase( return either { val currency = getCurrency(userWalletId, id) - coroutineScope { + return@either coroutineScope { val fetchStatus = async { - fetchNetworkStatus(userWalletId, currency.network) - } - val fetchQuote = async { - fetchQuote(currency.id) + fetchNetworkStatus(userWalletId = userWalletId, network = currency.network) } + + val fetchQuote = async { fetchQuote(currencyId = currency.id) } + val fetchStakingBalance = async { - fetchStakingBalance(userWalletId, currency, refresh) + fetchStakingBalance(userWalletId = userWalletId, cryptoCurrency = currency, refresh = refresh) } - awaitAll(fetchStatus, fetchQuote, fetchStakingBalance) + + awaitAll(fetchStatus, fetchQuote, fetchStakingBalance).summarizeResult() } } } @@ -80,14 +82,14 @@ class FetchCurrencyStatusUseCase( return either { val currency = getPrimaryCurrency(userWalletId, refresh) - coroutineScope { + return@either coroutineScope { val fetchStatus = async { - fetchNetworkStatus(userWalletId, currency.network) + fetchNetworkStatus(userWalletId = userWalletId, network = currency.network) } - val fetchQuote = async { - fetchQuote(currency.id) - } - awaitAll(fetchStatus, fetchQuote) + + val fetchQuote = async { fetchQuote(currencyId = currency.id) } + + awaitAll(fetchStatus, fetchQuote).summarizeResult() } } } @@ -97,9 +99,7 @@ class FetchCurrencyStatusUseCase( id: CryptoCurrency.ID, ): CryptoCurrency { return catch( - block = { - currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id) - }, + block = { currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId = userWalletId, id = id) }, ) { raise(CurrencyStatusError.DataError(it)) } @@ -114,31 +114,27 @@ class FetchCurrencyStatusUseCase( } } - private suspend fun Raise.fetchNetworkStatus(userWalletId: UserWalletId, network: Network) { - singleNetworkStatusFetcher( + private suspend fun fetchNetworkStatus(userWalletId: UserWalletId, network: Network): Either { + return singleNetworkStatusFetcher( params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = network), ) - .mapLeft { CurrencyStatusError.DataError(it) } - .bind() } - private suspend fun Raise.fetchQuote(currencyId: CryptoCurrency.ID) { - multiQuoteStatusFetcher( + private suspend fun fetchQuote(currencyId: CryptoCurrency.ID): Either { + return multiQuoteStatusFetcher( params = MultiQuoteStatusFetcher.Params( currenciesIds = setOfNotNull(currencyId.rawCurrencyId), appCurrencyId = null, ), ) - .mapLeft { CurrencyStatusError.DataError(it) } - .bind() } - private suspend fun Raise.fetchStakingBalance( + private suspend fun fetchStakingBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, refresh: Boolean, - ) { - if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) { + ): Either { + return if (tokensFeatureToggles.isStakingLoadingRefactoringEnabled) { singleYieldBalanceFetcher( params = SingleYieldBalanceFetcher.Params( userWalletId = userWalletId, @@ -147,11 +143,11 @@ class FetchCurrencyStatusUseCase( ), ) } else { - catch( - block = { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) }, - ) { - raise(CurrencyStatusError.DataError(it)) - } + Either.catch { stakingRepository.fetchSingleYieldBalance(userWalletId, cryptoCurrency, refresh) } } } + + private fun List>.summarizeResult(): Either { + return firstOrNull { it.isLeft() } ?: Unit.right() + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt index 1e1a09d0d2..1226448965 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyActionsUseCase.kt @@ -194,10 +194,7 @@ class GetCryptoCurrencyActionsUseCase( } // region sell - rampManager.availableForSell( - userWalletId = userWallet.walletId, - status = cryptoCurrencyStatus, - ) + rampManager.availableForSell(userWallet = userWallet, status = cryptoCurrencyStatus) .onRight { activeList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.None)) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt index 6a1fab3d6b..4ecbcaebcb 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetNetworkCoinStatusUseCase.kt @@ -1,13 +1,16 @@ package com.tangem.domain.tokens import arrow.core.Either +import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.models.network.Network import com.tangem.domain.tokens.error.CurrencyStatusError import com.tangem.domain.tokens.error.mapper.mapToCurrencyError import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* @@ -36,16 +39,21 @@ class GetNetworkCoinStatusUseCase( } suspend fun invokeSync( - userWalletId: UserWalletId, + userWallet: UserWallet, networkId: Network.ID, derivationPath: Network.DerivationPath, - isSingleWalletWithTokens: Boolean, ): Either { - val maybeCurrency = if (isSingleWalletWithTokens) { + val userWalletId = userWallet.walletId + val cardTypesResolver = userWallet.requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY] + + val maybeCurrency = if (userWallet.isMultiCurrency) { + currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath) + } else if (cardTypesResolver.isSingleWalletWithToken()) { currencyStatusOperations.getNetworkCoinForSingleWalletWithTokenSync(userWalletId, networkId) } else { - currencyStatusOperations.getNetworkCoinSync(userWalletId, networkId, derivationPath) + currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId) } + return maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError) } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt index 09a6e9bbe8..9e8914bfdf 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/BaseCurrencyStatusOperations.kt @@ -54,11 +54,6 @@ abstract class BaseCurrencyStatusOperations( protected abstract fun getQuotes(id: CryptoCurrency.RawID): Flow>> - protected abstract fun getNetworksStatuses( - userWalletId: UserWalletId, - network: Network, - ): EitherFlow> - protected abstract suspend fun fetchComponents( userWalletId: UserWalletId, networks: Set, @@ -106,13 +101,7 @@ abstract class BaseCurrencyStatusOperations( flow { emit(Error.EmptyQuotes.left()) } } - val statusFlow = getNetworksStatuses(userWalletId = userWalletId, network = currency.network) - .map { maybeStatuses -> - maybeStatuses.flatMap { statuses -> - statuses.singleOrNull { it.network == currency.network }?.right() - ?: Error.EmptyNetworksStatuses.left() - } - } + val statusFlow = getNetworkStatus(userWalletId = userWalletId, network = currency.network) val yieldBalanceFlow = getYieldBalance(userWalletId = userWalletId, cryptoCurrency = currency) @@ -349,6 +338,15 @@ abstract class BaseCurrencyStatusOperations( .onEmpty { emit(Error.EmptyYieldBalances.left()) } } + private fun getNetworkStatus(userWalletId: UserWalletId, network: Network): EitherFlow { + return singleNetworkStatusSupplier( + params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), + ) + .map>(NetworkStatus::right) + .distinctUntilChanged() + .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } + } + private suspend fun Raise.getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt index fc57cf580f..b6886643dc 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CachedCurrenciesStatusesOperations.kt @@ -303,18 +303,6 @@ class CachedCurrenciesStatusesOperations( .distinctUntilChanged() } - override fun getNetworksStatuses( - userWalletId: UserWalletId, - network: Network, - ): EitherFlow> { - return singleNetworkStatusSupplier( - params = SingleNetworkStatusProducer.Params(userWalletId = userWalletId, network = network), - ) - .map>> { setOf(it).right() } - .distinctUntilChanged() - .onEmpty { emit(Error.EmptyNetworksStatuses.left()) } - } - private fun getYieldBalances( userWalletId: UserWalletId, cryptoCurrencies: List, diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt index 4b2b63d11a..cedf40aae1 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/NFTAssetUM.kt @@ -62,6 +62,7 @@ data class NFTAssetUM( val value: String, val valueTextEllipsis: TextEllipsis = TextEllipsis.End, val showInfoButton: Boolean, - val onClick: () -> Unit = { }, + val onBlockClick: (() -> Unit)? = null, + val onValueClick: (() -> Unit)? = null, ) } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt index 66f7a6cae1..ae5bfb547a 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt @@ -27,7 +27,8 @@ internal class NFTDetailsUMFactory( private val onExploreClick: () -> Unit, private val onSendClick: () -> Unit, private val onRefresh: () -> Unit, - private val onInfoBlockClick: (title: TextReference, text: TextReference) -> Unit, + private val onRegularInfoBlockClick: (title: TextReference, text: TextReference) -> Unit, + private val onTokenAddressBlockClick: () -> Unit, ) { fun getInitialState(nftAsset: NFTAsset, nftCollection: NFTCollection): NFTDetailsUM = NFTDetailsUM( @@ -70,13 +71,13 @@ internal class NFTDetailsUMFactory( label = rarity.label, showDivider = hasSalePrice || hasDescription, onLabelClick = { - onInfoBlockClick( + onRegularInfoBlockClick( resourceReference(R.string.nft_details_rarity_label), resourceReference(R.string.nft_details_info_rarity_label), ) }, onRankClick = { - onInfoBlockClick( + onRegularInfoBlockClick( resourceReference(R.string.nft_details_rarity_rank), resourceReference(R.string.nft_details_info_rarity_rank), ) @@ -135,8 +136,8 @@ internal class NFTDetailsUMFactory( title = resourceReference(R.string.nft_details_token_standard), value = contractType, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_standard), resourceReference(R.string.nft_details_info_token_standard), ) @@ -147,20 +148,23 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_contract_address), resourceReference(R.string.nft_details_info_contract_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_token_id), value = id.tokenId.toString(), valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_id), resourceReference(R.string.nft_details_info_token_id), ) @@ -170,8 +174,8 @@ internal class NFTDetailsUMFactory( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) @@ -184,19 +188,22 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_address), resourceReference(R.string.nft_details_info_token_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) @@ -209,19 +216,22 @@ internal class NFTDetailsUMFactory( value = id.tokenAddress, valueTextEllipsis = TextEllipsis.Middle, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_token_address), resourceReference(R.string.nft_details_info_token_address), ) }, + onValueClick = { + onTokenAddressBlockClick() + }, ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_chain), value = network.name, showInfoButton = true, - onClick = { - onInfoBlockClick( + onBlockClick = { + onRegularInfoBlockClick( resourceReference(R.string.nft_details_chain), resourceReference(R.string.nft_details_info_chain), ) diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt index 852e5d2e40..f20a708479 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/model/NFTDetailsModel.kt @@ -10,6 +10,7 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.clipboard.ClipboardManager import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -20,6 +21,7 @@ import com.tangem.domain.nft.FetchNFTPriceUseCase import com.tangem.domain.nft.GetNFTExploreUrlUseCase import com.tangem.domain.nft.GetNFTPriceUseCase import com.tangem.domain.nft.analytics.NFTAnalyticsEvent +import com.tangem.domain.nft.models.NFTAsset import com.tangem.features.nft.details.NFTDetailsComponent import com.tangem.features.nft.details.entity.NFTAssetUM import com.tangem.features.nft.details.entity.NFTDetailsBottomSheetConfig @@ -49,6 +51,7 @@ internal class NFTDetailsModel @Inject constructor( private val getNFTPriceUseCase: GetNFTPriceUseCase, private val fetchNFTCollectionAssetsUseCase: FetchNFTCollectionAssetsUseCase, private val fetchNFTPriceUseCase: FetchNFTPriceUseCase, + private val clipboardManager: ClipboardManager, paramsContainer: ParamsContainer, ) : Model() { @@ -68,8 +71,9 @@ internal class NFTDetailsModel @Inject constructor( }, onExploreClick = ::onExploreClick, onSendClick = ::onSendClick, - onInfoBlockClick = ::onInfoBlockClick, + onRegularInfoBlockClick = ::onInfoBlockClick, onRefresh = ::onRefresh, + onTokenAddressBlockClick = ::onTokenAddressBlockClick, ) private val _state by lazy { @@ -81,7 +85,17 @@ internal class NFTDetailsModel @Inject constructor( val bottomSheetNavigation: SlotNavigation = SlotNavigation() init { - analyticsEventHandler.send(NFTAnalyticsEvent.Details.ScreenOpened(params.nftAsset.network.name)) + analyticsEventHandler.send( + NFTAnalyticsEvent.Details.ScreenOpened( + blockchain = params.nftAsset.network.name, + standard = when (val id = params.nftAsset.id) { + is NFTAsset.Identifier.EVM -> id.contractType.name + is NFTAsset.Identifier.Solana -> id.tokenStandard?.toString() + is NFTAsset.Identifier.TON -> null + is NFTAsset.Identifier.Unknown -> null + }, + ), + ) initAppCurrency() subscribeToPriceChanges() } @@ -191,4 +205,15 @@ internal class NFTDetailsModel @Inject constructor( ), ) } + + private fun onTokenAddressBlockClick() { + val asset = params.nftAsset + val addressToCopy = when (val id = asset.id) { + is NFTAsset.Identifier.EVM -> id.tokenAddress + is NFTAsset.Identifier.Solana -> id.tokenAddress + is NFTAsset.Identifier.TON -> id.tokenAddress + is NFTAsset.Identifier.Unknown -> return + } + clipboardManager.setText(text = addressToCopy, isSensitive = false) + } } \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt index cc7726dfd1..df471fec36 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/ui/NFTDetailsBlocksGroup.kt @@ -69,17 +69,14 @@ internal fun NFTDetailsBlocksGroup( NFTDetailsGroupBlock( modifier = Modifier .weight(1f) - .padding(paddingValues) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = item1.onClick, - ), + .padding(paddingValues), title = item1.title, value = stringReference(item1.value), titleEllipsis = item1.titleTextEllipsis, valueEllipsis = item1.valueTextEllipsis, showInfoButton = item1.showInfoButton, + onBlockClick = item1.onBlockClick, + onValueClick = item1.onValueClick, ) if (item2 == null) { Box( @@ -91,17 +88,14 @@ internal fun NFTDetailsBlocksGroup( NFTDetailsGroupBlock( modifier = Modifier .weight(1f) - .padding(paddingValues) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onClick = item2.onClick, - ), + .padding(paddingValues), title = item2.title, value = stringReference(item2.value), titleEllipsis = item2.titleTextEllipsis, valueEllipsis = item2.valueTextEllipsis, showInfoButton = item2.showInfoButton, + onBlockClick = item2.onBlockClick, + onValueClick = item2.onValueClick, ) } } @@ -128,6 +122,8 @@ internal fun NFTDetailsGroupBlock( value: TextReference, showInfoButton: Boolean, modifier: Modifier = Modifier, + onBlockClick: (() -> Unit)? = null, + onValueClick: (() -> Unit)? = null, titleEllipsis: TextEllipsis = TextEllipsis.End, valueEllipsis: TextEllipsis = TextEllipsis.End, ) { @@ -136,7 +132,14 @@ internal fun NFTDetailsGroupBlock( ) { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .clickable( + enabled = onBlockClick != null, + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + onBlockClick?.invoke() + }, ) { EllipsisText( modifier = Modifier @@ -159,6 +162,18 @@ internal fun NFTDetailsGroupBlock( } EllipsisText( modifier = Modifier + .fillMaxWidth() + .clickable( + enabled = onValueClick != null || onBlockClick != null, + interactionSource = remember { MutableInteractionSource() }, + indication = null, + ) { + if (onValueClick != null) { + onValueClick.invoke() + } else { + onBlockClick?.invoke() + } + } .padding(top = TangemTheme.dimens.spacing4), text = value.resolveReference(), style = TangemTheme.typography.body1, @@ -255,27 +270,27 @@ private class NFTAssetBlocksProvider : CollectionPreviewParameterProvider() private val _uiState = MutableStateFlow(OnboardingMultiWalletUM()) @@ -90,7 +91,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( firmwareVersion = backup.card2.firmwareVersion, ) _uiState.update { - it.copy(artwork2 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork2 = artworkUMConverter.convert(artwork)) } } _uiState.value.artwork3 == null && backup.card3 != null -> { @@ -103,7 +104,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( firmwareVersion = backup.card3.firmwareVersion, ) _uiState.update { - it.copy(artwork3 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork3 = artworkUMConverter.convert(artwork)) } } } @@ -179,7 +180,7 @@ internal class OnboardingMultiWalletModel @Inject constructor( ) _uiState.update { - it.copy(artwork1 = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(artwork1 = artworkUMConverter.convert(artwork)) } } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt index 9830905a93..672d5e838b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/child/topup/ui/OnboardingNoteTopUpHeader.kt @@ -22,7 +22,6 @@ import com.tangem.features.onboarding.v2.common.ui.RefreshButton import com.tangem.features.onboarding.v2.common.ui.WalletCard import com.tangem.features.onboarding.v2.impl.R import com.valentinilk.shimmer.shimmer -import kotlinx.collections.immutable.ImmutableList @Composable fun OnboardingNoteTopUpHeader( @@ -95,7 +94,7 @@ private fun OnboardinNoteTopUpHeaderPreview() { TangemThemePreview { OnboardingNoteTopUpHeader( balance = "0.00000001 BTC", - cardArtwork = ArtworkUM(null as ImmutableList?, ""), + cardArtwork = ArtworkUM(null, ""), onRefreshBalanceClick = {}, isRefreshing = false, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt index 43d12d119e..27e57382fd 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/note/impl/model/OnboardingNoteModel.kt @@ -1,12 +1,12 @@ package com.tangem.features.onboarding.v2.note.impl.model import com.arkivanov.decompose.router.stack.StackNavigation +import com.tangem.common.ui.userwallet.converter.ArtworkUMConverter import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender -import com.tangem.core.ui.components.artwork.ArtworkUM import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.features.onboarding.v2.common.ui.exitOnboardingDialog @@ -29,6 +29,7 @@ internal class OnboardingNoteModel @Inject constructor( private val router: Router, private val messageSender: UiMessageSender, private val getCardImageUseCase: GetCardImageUseCase, + private val artworkUMConverter: ArtworkUMConverter, ) : Model() { @Suppress("UnusedPrivateMember") @@ -98,7 +99,7 @@ internal class OnboardingNoteModel @Inject constructor( firmwareVersion = cardInfo.firmwareVersion.toSdkFirmwareVersion(), ) _uiState.update { - it.copy(cardArtwork = ArtworkUM(artwork.verifiedArtwork, artwork.defaultUrl)) + it.copy(cardArtwork = artworkUMConverter.convert(artwork)) } } } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index 7086e3f10c..2fb53e1132 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -59,9 +59,9 @@ internal class OnrampTokenListModel @Inject constructor( val state: StateFlow = tokenListUMController.state private val params: OnrampTokenListComponent.Params = paramsContainer.require() - private val scanResponse by lazy { + private val userWallet by lazy { getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } - .requireColdWallet().scanResponse // TODO [REDACTED_TASK_KEY] + .requireColdWallet() // TODO [REDACTED_TASK_KEY] } init { @@ -229,16 +229,13 @@ internal class OnrampTokenListModel @Inject constructor( return when (params.filterOperation) { OnrampOperation.BUY -> { rampStateManager.availableForBuy( - scanResponse = scanResponse, + scanResponse = userWallet.scanResponse, userWalletId = params.userWalletId, cryptoCurrency = status.currency, ).isAvailable() } OnrampOperation.SELL -> { - rampStateManager.availableForSell( - userWalletId = params.userWalletId, - status = status, - ).isRight() + rampStateManager.availableForSell(userWallet = userWallet, status = status).isRight() } OnrampOperation.SWAP -> { val isAvailable = rampStateManager.availableForSwap( diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt index 798a2abda2..a61ec8f1e6 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/model/SendModel.kt @@ -277,14 +277,18 @@ internal class SendModel @Inject constructor( isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean, ): Flow> { - return if (isMultiCurrency) { - getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + return when { + isSingleWalletWithToken -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( userWalletId = userWalletId, currencyId = cryptoCurrency.id, - isSingleWalletWithTokens = isSingleWalletWithToken, + isSingleWalletWithTokens = true, ) - } else { - getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) + isMultiCurrency -> getSingleCryptoCurrencyStatusUseCase.invokeMultiWallet( + userWalletId = userWalletId, + currencyId = cryptoCurrency.id, + isSingleWalletWithTokens = false, + ) + else -> getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWalletId = userWalletId) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt index 82db22b95a..2454768e9c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/model/StakingModel.kt @@ -165,6 +165,7 @@ internal class StakingModel @Inject constructor( } private var isInitialInfoAnalyticSent: Boolean = false + private var isBalanceUpdatedAfterStart: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { stakingBalanceUpdater.create( @@ -421,7 +422,7 @@ internal class StakingModel @Inject constructor( override fun onRefreshSwipe(isRefreshing: Boolean) { stateController.update(SetInitialLoadingStateTransformer(isRefreshing)) coroutineScope.launch { - balanceUpdater.updatePullToRefresh() + balanceUpdater.partialUpdate() }.invokeOnCompletion { stateController.update(SetInitialLoadingStateTransformer(false)) } @@ -1011,7 +1012,11 @@ internal class StakingModel @Inject constructor( when { isInitState() -> { updateInitialData(status) - balanceUpdater.updateAfterNavigationToInitial() + + if (!isBalanceUpdatedAfterStart) { + isBalanceUpdatedAfterStart = true + balanceUpdater.partialUpdate() + } } isAssentState() -> { getFee() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 22e12b38e9..e6c32aa82c 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -1,6 +1,7 @@ package com.tangem.features.staking.impl.presentation.state.helpers import com.tangem.domain.staking.FetchActionsUseCase +import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchCurrencyStatusUseCase @@ -22,6 +23,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( private val fetchActionsUseCase: FetchActionsUseCase, private val txHistoryContentUpdateEmitter: TxHistoryContentUpdateEmitter, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, + private val fetchStakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase, @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @@ -37,6 +39,13 @@ internal class StakingBalanceUpdater @AssistedInject constructor( network = cryptoCurrencyStatus.currency.network, ) }, + async { + fetchStakingYieldBalanceUseCase( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + isRefactoringEnabled = true, + ) + }, // we should update tx history and network for new balances async { fetchCurrencyStatus(delayMillis = BALANCE_UPDATE_DELAY) @@ -51,7 +60,7 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } - suspend fun updatePullToRefresh() { + suspend fun partialUpdate() { coroutineScope { listOf( async { @@ -64,12 +73,6 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } - suspend fun updateAfterNavigationToInitial() { - coroutineScope { - async { updateStakingActions() }.await() - } - } - private suspend fun fetchCurrencyStatus(delayMillis: Long = 0L) { delay(delayMillis) fetchCurrencyStatusUseCase( diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt new file mode 100644 index 0000000000..6c2fbd0ee0 --- /dev/null +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/WalletSettingsAnalyticEvents.kt @@ -0,0 +1,17 @@ +package com.tangem.feature.walletsettings.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS + +internal sealed class WalletSettingsAnalyticEvents( + category: String = "Settings / Wallet", + event: String, + params: Map = mapOf(), +) : AnalyticsEvent(category, event, params) { + + data class NftToggleSwitch(val enabled: AnalyticsParam.OnOffState) : WalletSettingsAnalyticEvents( + event = "NFT toggle switch", + params = mapOf(STATUS to enabled.value), + ) +} \ No newline at end of file diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index ebe302c2e7..b85bb4de06 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -6,6 +6,8 @@ import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.tangem.common.routing.AppRoute import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.Off +import com.tangem.core.analytics.models.AnalyticsParam.OnOffState.On import com.tangem.core.analytics.utils.AnalyticsContextProxy import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model @@ -29,6 +31,7 @@ import com.tangem.domain.wallets.models.isMultiCurrency import com.tangem.domain.wallets.models.requireColdWallet import com.tangem.domain.wallets.usecase.* import com.tangem.feature.walletsettings.analytics.Settings +import com.tangem.feature.walletsettings.analytics.WalletSettingsAnalyticEvents import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.entity.DialogConfig import com.tangem.feature.walletsettings.entity.NetworksAvailableForNotificationBSConfig @@ -214,6 +217,11 @@ internal class WalletSettingsModel @Inject constructor( } else { disableWalletNFTUseCase.invoke(params.userWalletId) } + analyticsEventHandler.send( + WalletSettingsAnalyticEvents.NftToggleSwitch( + enabled = if (isChecked) On else Off, + ), + ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index dacede164b..f125cd626d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -238,19 +238,31 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( override fun onNFTClick(userWallet: UserWallet) { val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return - val analyticsState = when (val nftState = selectedWallet.nftState) { - is WalletNFTItemUM.Content -> NFTAnalyticsEvent.NFTListScreenOpened.State.Full( - assetsCount = nftState.assetsCount, - collectionsCount = nftState.collectionsCount, - ) - is WalletNFTItemUM.Empty -> NFTAnalyticsEvent.NFTListScreenOpened.State.Empty + when (val state = selectedWallet.nftState) { + is WalletNFTItemUM.Content -> { + analyticsEventHandler.send( + NFTAnalyticsEvent.NFTListScreenOpened( + state = NFTAnalyticsEvent.NFTListScreenOpened.State.Full, + allAssetsCount = state.allAssetsCount, + collectionsCount = state.collectionsCount, + noCollectionAssetsCount = state.noCollectionAssetsCount, + ), + ) + } + is WalletNFTItemUM.Empty -> { + analyticsEventHandler.send( + NFTAnalyticsEvent.NFTListScreenOpened( + state = NFTAnalyticsEvent.NFTListScreenOpened.State.Empty, + allAssetsCount = 0, + collectionsCount = 0, + noCollectionAssetsCount = 0, + ), + ) + } is WalletNFTItemUM.Failed, is WalletNFTItemUM.Hidden, is WalletNFTItemUM.Loading, - -> null - } - analyticsState?.let { - analyticsEventHandler.send(NFTAnalyticsEvent.NFTListScreenOpened(analyticsState)) + -> Unit } router.openNFT(userWallet) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt index 08937391da..9a3d8d6a28 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/preview/WalletScreenPreviewData.kt @@ -123,7 +123,8 @@ internal object WalletScreenPreviewData { nftState = WalletNFTItemUM.Content( previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")), collectionsCount = 1, - assetsCount = 3, + allAssetsCount = 3, + noCollectionAssetsCount = 0, isFlickering = false, onItemClick = { }, ), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt index f4406d5dc5..d6fb28e300 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNFTItemUM.kt @@ -19,7 +19,8 @@ sealed class WalletNFTItemUM { data class Content( val previews: ImmutableList, val collectionsCount: Int, - val assetsCount: Int, + val allAssetsCount: Int, + val noCollectionAssetsCount: Int, val isFlickering: Boolean, val onItemClick: () -> Unit, ) : WalletNFTItemUM() { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt index b749ffccf3..7f47fb0a15 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetNFTCollectionsTransformer.kt @@ -52,8 +52,24 @@ internal class SetNFTCollectionsTransformer( .toPersistentList() }, collectionsCount = collections.size, - assetsCount = collections - .sumOf { it.count }, + allAssetsCount = collections.sumOf { it.count }, + noCollectionAssetsCount = collections.sumOf { collection -> + when (val collectionId = collection.id) { + is NFTCollection.Identifier.Solana -> + collection + .count + .takeIf { collectionId.collectionAddress == null } + ?: 0 + is NFTCollection.Identifier.TON -> + collection + .count + .takeIf { collectionId.contractAddress == null } + ?: 0 + is NFTCollection.Identifier.EVM, + is NFTCollection.Identifier.Unknown, + -> 0 + } + }, isFlickering = false, onItemClick = onItemClick, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt index 079f2876e1..b79c1832b5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletNFTItem.kt @@ -108,7 +108,7 @@ private fun WalletNFTItemContent(state: WalletNFTItemUM.Content, onClick: () -> Text( text = stringResourceSafe( id = R.string.nft_wallet_count, - state.assetsCount, + state.allAssetsCount, state.collectionsCount, ), style = TangemTheme.typography.caption2.applyBladeBrush( @@ -411,8 +411,9 @@ private class WalletNFTItemProvider : CollectionPreviewParameterProvider { return getNetworkCoinUseCase.invokeSync( - userWalletId = userWallet.walletId, + userWallet = userWallet, networkId = network.id, derivationPath = network.derivationPath, - isSingleWalletWithTokens = userWallet is UserWallet.Cold && - userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), ) } diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index d33eab3225..e18a2ec5cf 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1093" +tangemBlockchainSdk = "develop-1097" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-484" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ diff --git a/tangem-android-tools b/tangem-android-tools index 034ad04fdf..bc4cd43085 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit 034ad04fdf0810e338cb8898f50554874cec96d7 +Subproject commit bc4cd430853ca794614b8d5163c9b28b9ca26112