diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index 77bd0c1526..335ce4196c 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -168,15 +168,6 @@ internal object OnrampDomainModule { ) } - @Provides - @Singleton - fun provideGetOnrampSelectedPaymentMethodUseCase( - onrampRepository: OnrampRepository, - onrampErrorResolver: OnrampErrorResolver, - ): GetOnrampSelectedPaymentMethodUseCase { - return GetOnrampSelectedPaymentMethodUseCase(onrampRepository, onrampErrorResolver) - } - @Provides @Singleton fun provideGetOnrampProviderWithQuoteUseCase( @@ -186,15 +177,6 @@ internal object OnrampDomainModule { return GetOnrampProviderWithQuoteUseCase(onrampRepository, onrampErrorResolver) } - @Provides - @Singleton - fun provideOnrampSaveSelectedPaymentMethod( - onrampRepository: OnrampRepository, - onrampErrorResolver: OnrampErrorResolver, - ): OnrampSaveSelectedPaymentMethod { - return OnrampSaveSelectedPaymentMethod(onrampRepository, onrampErrorResolver) - } - @Provides @Singleton fun provideOnrampFetchPairsUseCase( diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 7643323dcb..8b279d736b 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -409,17 +409,6 @@ internal class DefaultOnrampRepository( return quotes.mapTo(hashSetOf(), OnrampQuote::paymentMethod) } - override suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod) { - val dto = paymentMethodsConverter.convertBack(paymentMethod) - paymentMethodsStore.store(SELECTED_PAYMENT_METHOD_KEY, listOf(dto)) - } - - override fun getSelectedPaymentMethod(): Flow { - return paymentMethodsStore - .get(SELECTED_PAYMENT_METHOD_KEY) - .mapNotNull { paymentMethods -> paymentMethods.firstOrNull()?.let(paymentMethodsConverter::convert) } - } - override suspend fun clearCache() = withContext(NonCancellable) { paymentMethodsStore.clear() pairsStore.clear() diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampSelectedPaymentMethodUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampSelectedPaymentMethodUseCase.kt deleted file mode 100644 index 0548228e5e..0000000000 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampSelectedPaymentMethodUseCase.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.tangem.domain.onramp - -import arrow.core.Either -import arrow.core.left -import arrow.core.right -import com.tangem.domain.core.utils.EitherFlow -import com.tangem.domain.onramp.model.OnrampPaymentMethod -import com.tangem.domain.onramp.model.error.OnrampError -import com.tangem.domain.onramp.repositories.OnrampErrorResolver -import com.tangem.domain.onramp.repositories.OnrampRepository -import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.map - -class GetOnrampSelectedPaymentMethodUseCase( - private val repository: OnrampRepository, - private val errorResolver: OnrampErrorResolver, -) { - - operator fun invoke(): EitherFlow { - return repository.getSelectedPaymentMethod() - .map> { it.right() } - .catch { - emit(errorResolver.resolve(it).left()) - } - } -} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveSelectedPaymentMethod.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveSelectedPaymentMethod.kt deleted file mode 100644 index d7200a0b7d..0000000000 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveSelectedPaymentMethod.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.domain.onramp - -import arrow.core.Either -import com.tangem.domain.onramp.model.OnrampPaymentMethod -import com.tangem.domain.onramp.model.error.OnrampError -import com.tangem.domain.onramp.repositories.OnrampErrorResolver -import com.tangem.domain.onramp.repositories.OnrampRepository - -class OnrampSaveSelectedPaymentMethod( - private val repository: OnrampRepository, - private val errorResolver: OnrampErrorResolver, -) { - - suspend operator fun invoke(paymentMethod: OnrampPaymentMethod): Either { - return Either.catch { repository.saveSelectedPaymentMethod(paymentMethod) } - .mapLeft(errorResolver::resolve) - } -} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index fb2ef76f92..4d1cb26f88 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -41,8 +41,6 @@ interface OnrampRepository { suspend fun getDefaultCountrySync(): OnrampCountry? fun getDefaultCountry(): Flow suspend fun getAvailablePaymentMethods(): Set - suspend fun saveSelectedPaymentMethod(paymentMethod: OnrampPaymentMethod) - fun getSelectedPaymentMethod(): Flow fun getQuotes(): Flow> suspend fun getQuotesSync(): List? suspend fun clearCache() diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt index 7a71823750..6b3f2f5ab8 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/GetCardImageUseCase.kt @@ -36,7 +36,7 @@ class GetCardImageUseCase( cardPublicKey: ByteArray, manufacturerName: String, firmwareVersion: FirmwareVersion, - size: ArtworkSize, + size: ArtworkSize = ArtworkSize.SMALL, ): ArtworkModel { return if (cardSdkFeatureToggles.isNewArtworkLoadingEnabled) { val result = cardArtworksProvider.getArtwork( diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 6609be68ac..b614e87a77 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -81,4 +81,7 @@ dependencies { /* Libs */ implementation(projects.libs.crypto) implementation(projects.libs.blockchainSdk) + + /** Tangem libraries */ + implementation(tangemDeps.card.core) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index acd79ae798..7fa44c4df2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -7,6 +7,8 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.components.rows.model.BlockchainRowUM import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.ArtworkModel +import com.tangem.domain.tokens.model.TotalFiatBalance import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.isMultiCurrency @@ -45,19 +47,25 @@ internal class AddToPortfolioBSContentUMFactory( * @param selectedWallet selected wallet * @param alreadyAddedNetworks already added networks */ + @Suppress("LongParameterList") fun create( currentState: TangemBottomSheetConfig?, portfolioData: PortfolioData, portfolioUIData: PortfolioUIData, selectedWallet: UserWallet?, alreadyAddedNetworks: Set?, + artworks: HashMap, ): TangemBottomSheetConfig { return (currentState ?: TangemBottomSheetConfig.Empty).copy( isShown = portfolioUIData.portfolioBSVisibilityModel.addToPortfolioBSVisibility, onDismissRequest = { onAddToPortfolioVisibilityChange(false) }, content = if (selectedWallet != null && alreadyAddedNetworks != null) { AddToPortfolioBSContentUM( - selectedWallet = selectedWallet.toSelectedUserWalletItemUM(), + selectedWallet = selectedWallet.toSelectedUserWalletItemUM( + portfolioData = portfolioData, + balance = portfolioData.walletsWithBalance[selectedWallet.walletId]?.getOrNull(), + artwork = artworks[selectedWallet.walletId], + ), selectNetworkUM = SelectNetworkUMConverter( networksWithToggle = portfolioUIData.addToPortfolioData.associateWithToggle( userWalletId = selectedWallet.walletId, @@ -87,6 +95,7 @@ internal class AddToPortfolioBSContentUMFactory( isShow = portfolioUIData.portfolioBSVisibilityModel.walletSelectorBSVisibility, portfolioData = portfolioData, selectedWalletId = selectedWallet.walletId, + artworks = artworks, ), isWalletBlockVisible = portfolioData.walletsWithCurrencies .filterKeys(UserWallet::isMultiCurrency).size > 1, @@ -97,10 +106,18 @@ internal class AddToPortfolioBSContentUMFactory( ) } - private fun UserWallet.toSelectedUserWalletItemUM(): UserWalletItemUM { + private fun UserWallet.toSelectedUserWalletItemUM( + artwork: ArtworkModel? = null, + portfolioData: PortfolioData, + balance: TotalFiatBalance?, + ): UserWalletItemUM { return UserWalletItemUMConverter( onClick = { onWalletSelectorVisibilityChange(true) }, endIcon = UserWalletItemUM.EndIcon.Arrow, + balance = balance, + artwork = artwork, + appCurrency = portfolioData.appCurrency, + isBalanceHidden = portfolioData.isBalanceHidden, ).convert(value = this) } @@ -108,6 +125,7 @@ internal class AddToPortfolioBSContentUMFactory( isShow: Boolean, portfolioData: PortfolioData, selectedWalletId: UserWalletId, + artworks: HashMap, ): TangemBottomSheetConfig { return TangemBottomSheetConfig( isShown = isShow, @@ -134,6 +152,7 @@ internal class AddToPortfolioBSContentUMFactory( } else { UserWalletItemUM.EndIcon.None }, + artwork = artworks[userWallet.walletId], ).convert(userWallet) } .toImmutableList(), diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 13034c0d21..080cfda2f7 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -20,12 +20,18 @@ import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase import com.tangem.domain.managetokens.model.CurrencyUnsupportedState import com.tangem.domain.markets.SaveMarketTokensUseCase import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.models.ArtworkModel +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.isMultiCurrency +import com.tangem.domain.wallets.models.requireColdWallet +import com.tangem.domain.wallets.usecase.GetCardImageUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.features.markets.impl.R import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent import com.tangem.features.markets.portfolio.impl.analytics.PortfolioAnalyticsEvent +import com.tangem.features.markets.portfolio.impl.loader.PortfolioData import com.tangem.features.markets.portfolio.impl.loader.PortfolioDataLoader import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM import com.tangem.lib.crypto.BlockchainUtils @@ -33,6 +39,8 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import javax.inject.Inject @@ -50,6 +58,7 @@ internal class MarketsPortfolioModel @Inject constructor( private val portfolioDataLoader: PortfolioDataLoader, private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase, private val saveMarketTokensUseCase: SaveMarketTokensUseCase, + private val getCardImageUseCase: GetCardImageUseCase, private val addToPortfolioManager: AddToPortfolioManager, private val analyticsEventHandler: AnalyticsEventHandler, ) : Model() { @@ -57,6 +66,10 @@ internal class MarketsPortfolioModel @Inject constructor( val state: StateFlow get() = _state private val _state: MutableStateFlow = MutableStateFlow(value = MyPortfolioUM.Loading) + private val loadedArtworks: HashMap = hashMapOf() + private val artworksState: MutableStateFlow> = MutableStateFlow(hashMapOf()) + private val loadArtworksMutex = Mutex() + private val params = paramsContainer.require() private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder( token = params.token, @@ -162,14 +175,44 @@ internal class MarketsPortfolioModel @Inject constructor( private fun subscribeOnStateUpdates() { combine( - flow = portfolioDataLoader.load(params.token.id, addToPortfolioManager.availableNetworks), + flow = loadPortfolioData(params.token.id, addToPortfolioManager.availableNetworks), flow2 = getPortfolioUIDataFlow(), + flow3 = artworksState, transform = factory::create, ) .onEach { _state.value = it } .launchIn(modelScope) } + private fun loadPortfolioData( + currencyRawId: CryptoCurrency.RawID, + availableNetworksFlow: Flow?>, + ): Flow { + portfolioDataLoader.load(currencyRawId, availableNetworksFlow).onEach { + loadArtworks(it.walletsWithCurrencies.keys.toList()) + }.also { return it } + } + + private fun loadArtworks(wallets: List) { + modelScope.launch { + loadArtworksMutex.withLock { + wallets.forEach { wallet -> + wallet.requireColdWallet() // TODO [REDACTED_TASK_KEY] + if (!loadedArtworks.containsKey(wallet.walletId)) { + val artwork = getCardImageUseCase( + cardId = wallet.cardId, + manufacturerName = wallet.scanResponse.card.manufacturer.name, + firmwareVersion = wallet.scanResponse.card.firmwareVersion.toSdkFirmwareVersion(), + cardPublicKey = wallet.scanResponse.card.cardPublicKey, + ) + loadedArtworks[wallet.walletId] = artwork + artworksState.emit(loadedArtworks) + } + } + } + } + } + private fun getPortfolioUIDataFlow(): Flow { return combine( flow = portfolioBSVisibilityModelFlow, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt index 51e961071a..914e300b7a 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt @@ -2,8 +2,10 @@ package com.tangem.features.markets.portfolio.impl.model import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.models.ArtworkModel import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.isMultiCurrency import com.tangem.features.markets.portfolio.impl.loader.PortfolioData import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM @@ -31,7 +33,11 @@ internal class MyPortfolioUMFactory( private val updateTokens: ((ImmutableList) -> ImmutableList) -> Unit, ) { - fun create(portfolioData: PortfolioData, portfolioUIData: PortfolioUIData): MyPortfolioUM { + fun create( + portfolioData: PortfolioData, + portfolioUIData: PortfolioUIData, + artworks: HashMap, + ): MyPortfolioUM { val addToPortfolioData = portfolioUIData.addToPortfolioData val isOnlyUnavailableNetworks = addToPortfolioData.availableNetworks?.isEmpty() == true @@ -52,6 +58,7 @@ internal class MyPortfolioUMFactory( addToPortfolioBSConfig = createAddToPortfolioBSConfig( portfolioData = portfolioData, portfolioUIData = portfolioUIData, + artworks = artworks, ), onAddClick = onAddClick, ) @@ -66,7 +73,11 @@ internal class MyPortfolioUMFactory( addButtonState = walletsWithCurrencies.getAddButtonState( availableNetworks = addToPortfolioData.availableNetworks, ), - bsConfig = createAddToPortfolioBSConfig(portfolioData = portfolioData, portfolioUIData = portfolioUIData), + bsConfig = createAddToPortfolioBSConfig( + portfolioData = portfolioData, + portfolioUIData = portfolioUIData, + artworks = artworks, + ), onAddClick = onAddClick, quickActionsIntents = tokenActionsHandler, currentState = currentState, @@ -78,6 +89,7 @@ internal class MyPortfolioUMFactory( private fun createAddToPortfolioBSConfig( portfolioData: PortfolioData, portfolioUIData: PortfolioUIData, + artworks: HashMap, ): TangemBottomSheetConfig { val selectedWallet = portfolioData.walletsWithCurrencies.keys .firstOrNull { it.walletId == portfolioUIData.selectedWalletId } @@ -96,6 +108,7 @@ internal class MyPortfolioUMFactory( portfolioUIData = portfolioUIData, selectedWallet = selectedWallet, alreadyAddedNetworks = alreadyAddedNetworks, + artworks = artworks, ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt index 0cc48e4657..126eedcb3a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/providers/model/SelectProviderModel.kt @@ -15,8 +15,6 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.domain.onramp.GetOnrampPaymentMethodsUseCase import com.tangem.domain.onramp.GetOnrampProviderWithQuoteUseCase -import com.tangem.domain.onramp.GetOnrampSelectedPaymentMethodUseCase -import com.tangem.domain.onramp.OnrampSaveSelectedPaymentMethod import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.model.OnrampPaymentMethod import com.tangem.domain.onramp.model.OnrampProviderWithQuote @@ -36,7 +34,10 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber import java.math.BigDecimal @@ -49,9 +50,7 @@ internal class SelectProviderModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, private val getOnrampPaymentMethodsUseCase: GetOnrampPaymentMethodsUseCase, - private val getOnrampSelectedPaymentMethodUseCase: GetOnrampSelectedPaymentMethodUseCase, private val getOnrampProviderWithQuoteUseCase: GetOnrampProviderWithQuoteUseCase, - private val saveSelectedPaymentMethod: OnrampSaveSelectedPaymentMethod, getUserCountryUseCase: GetUserCountryUseCase, paramsContainer: ParamsContainer, ) : Model() { @@ -70,7 +69,6 @@ internal class SelectProviderModel @Inject constructor( analyticsEventHandler.send(OnrampAnalyticsEvent.ProvidersScreenOpened) getPaymentMethods() getProviders(params.selectedPaymentMethod) - subscribeToPaymentMethodUpdates() } private fun getPaymentMethods() { @@ -104,17 +102,6 @@ internal class SelectProviderModel @Inject constructor( } } - private fun subscribeToPaymentMethodUpdates() { - getOnrampSelectedPaymentMethodUseCase.invoke() - .onEach { maybePaymentMethod -> - maybePaymentMethod.fold( - ifLeft = ::sendOnrampErrorEvent, - ifRight = ::getProviders, - ) - } - .launchIn(modelScope) - } - private fun getProviders(paymentMethod: OnrampPaymentMethod) { modelScope.launch { getOnrampProviderWithQuoteUseCase.invoke(paymentMethod) @@ -176,7 +163,6 @@ internal class SelectProviderModel @Inject constructor( val firstProvider = methodContainer.providers.firstOrNull() analyticsEventHandler.send(OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = paymentMethod.name)) modelScope.launch { - saveSelectedPaymentMethod.invoke(paymentMethod) _state.update { state -> state.copy( selectedPaymentMethod = state.selectedPaymentMethod.copy( @@ -184,6 +170,7 @@ internal class SelectProviderModel @Inject constructor( ), ) } + getProviders(paymentMethod) if (firstProvider is ProviderListItemUM.Available) { onProviderSelected(firstProvider.providerResult, firstProvider.isBestRate) }