diff --git a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt index 9c295b058d..1a93dc67e5 100644 --- a/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt +++ b/domain/account/src/main/java/com/tangem/domain/account/usecase/IsAccountsModeEnabledUseCase.kt @@ -34,6 +34,7 @@ class IsAccountsModeEnabledUseCase( combine(flows = totalAccountsCountList) { it.toList().isModeEnabled() } } .onEmpty { emit(false) } + .distinctUntilChanged() } suspend fun invokeSync(): Boolean { diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts index 7bcf648384..ac6adcc8fb 100644 --- a/features/markets/impl/build.gradle.kts +++ b/features/markets/impl/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { /* Domain */ implementation(projects.domain.account) + implementation(projects.domain.account.status) implementation(projects.domain.appCurrency) implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt index 7f9e509ab3..400f8b25c2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/api/AvailableToAddData.kt @@ -4,40 +4,51 @@ import com.tangem.domain.markets.TokenMarketInfo import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountStatus import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId -data class AvailableToAddData( +internal data class AvailableToAddData( val availableToAddWallets: Map, ) { + val availableToAdd: Boolean + get() = availableToAddWallets.isNotEmpty() val isSinglePortfolio: Boolean get() = availableToAddWallets.size == 1 && availableToAddWallets.values.first().accounts.size == 1 } -data class AvailableToAddWallet( +internal data class AvailableToAddWallet( val userWallet: UserWallet, val accounts: Set, val availableNetworks: Set, val availableToAddAccounts: Map, -) { - val isSingleAvailableNetworks: Boolean get() = availableNetworks.size == 1 - val singleAvailableNetworks: TokenMarketInfo.Network get() = availableNetworks.first() -} - -data class AvailableToAddAccount( - val account: AccountStatus, - val availableNetworks: Set, - val availableToAddNetworks: Set, ) -data class SelectedPortfolio( - val userWallet: UserWallet, +internal data class AvailableToAddAccount( val account: AccountStatus, + val availableNetworks: Set, + val addedNetworks: Set, +) { + val isSingleNetwork: Boolean + get() = availableNetworks.size == 1 + + val availableToAddNetworks: Set = availableNetworks + .filter { available -> addedNetworks.none { added -> added.backendId == available.networkId } } + .toSet() + + val addedMarketNetworks: Set = availableNetworks + .filter { available -> addedNetworks.any { added -> added.backendId == available.networkId } } + .toSet() +} + +internal data class SelectedPortfolio( + val userWallet: UserWallet, + val account: AvailableToAddAccount, val isAccountMode: Boolean, val availableMorePortfolio: Boolean, ) -data class SelectedNetwork( +internal data class SelectedNetwork( val selectedNetwork: TokenMarketInfo.Network, val cryptoCurrency: CryptoCurrency, val availableMoreNetwork: Boolean, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt index f4d441890e..0b1272cba8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/AddTokenComponent.kt @@ -1,6 +1,7 @@ package com.tangem.features.markets.portfolio.add.impl import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext @@ -8,6 +9,7 @@ import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.markets.TokenMarketParams +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.features.markets.portfolio.add.api.SelectedNetwork import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio import com.tangem.features.markets.portfolio.add.impl.model.AddTokenModel @@ -46,7 +48,7 @@ internal class AddTokenComponent @AssistedInject constructor( interface Callbacks { fun onChangeNetworkClick() fun onChangePortfolioClick() - fun onTokenAdded() + fun onTokenAdded(status: CryptoCurrencyStatus) } @AssistedFactory diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt index 7167f6bc11..132a4b8ba2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/converter/AvailableToAddDataConverter.kt @@ -1,9 +1,13 @@ package com.tangem.features.markets.portfolio.add.impl.converter +import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase import com.tangem.domain.markets.FilterAvailableNetworksForWalletUseCase +import com.tangem.domain.markets.GetTokenMarketCryptoCurrency import com.tangem.domain.markets.TokenMarketInfo +import com.tangem.domain.markets.TokenMarketParams import com.tangem.domain.models.account.AccountId import com.tangem.domain.models.account.AccountStatus +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.features.account.PortfolioFetcher @@ -12,33 +16,42 @@ import com.tangem.features.markets.portfolio.add.api.AvailableToAddData import com.tangem.features.markets.portfolio.add.api.AvailableToAddWallet import javax.inject.Inject -class AvailableToAddDataConverter @Inject constructor( +internal class AvailableToAddDataConverter @Inject constructor( private val filterAvailableNetworksForWalletUseCase: FilterAvailableNetworksForWalletUseCase, + private val getTokenMarketCryptoCurrency: GetTokenMarketCryptoCurrency, + private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, ) { suspend fun convert( balances: Map, availableNetworks: Set, + marketParams: TokenMarketParams, ): AvailableToAddData { - fun AccountStatus.getAvailableToAddAccount(): AvailableToAddAccount { - val availableToAddNetworks = availableNetworks - .filterTo(mutableSetOf()) { network -> !this.isAlreadyAddedNetworks(network) } + suspend fun AccountStatus.getAvailableToAddAccount(wallet: UserWallet): AvailableToAddAccount { + val addedNetworks = availableNetworks + .mapNotNull { createCryptoCurrency(wallet, it, marketParams) } + .mapNotNull { getAccountCurrencyStatusUseCase.invokeSync(wallet.walletId, it) } + .mapNotNull { it.getOrNull()?.status?.currency?.network } + .toSet() return AvailableToAddAccount( account = this, availableNetworks = availableNetworks, - availableToAddNetworks = availableToAddNetworks, + addedNetworks = addedNetworks, ) } - fun UserWallet.getAvailableToAddWallet(balance: PortfolioFetcher.PortfolioBalance): AvailableToAddWallet { - val filteredNetworks = this.filteredAvailableNetworks(availableNetworks) + suspend fun getAvailableToAddWallet( + entry: Map.Entry, + ): AvailableToAddWallet { + val (wallet, balance) = entry + val filteredNetworks = wallet.filteredAvailableNetworks(availableNetworks) val accounts = balance.accountsBalance.accountStatuses val availableToAddAccounts: Map = accounts - .map { it.account.accountId to it.getAvailableToAddAccount() } + .map { it.account.accountId to it.getAvailableToAddAccount(wallet) } .filter { (_, account) -> account.availableToAddNetworks.isNotEmpty() } .toMap() return AvailableToAddWallet( - userWallet = this, + userWallet = wallet, accounts = accounts, availableNetworks = filteredNetworks, availableToAddAccounts = availableToAddAccounts, @@ -46,8 +59,9 @@ class AvailableToAddDataConverter @Inject constructor( } val availableToAddWallets: Map = balances - .map { (wallet, balance) -> - val availableToAddWallet = wallet.getAvailableToAddWallet(balance) + .map { + val (wallet, balance) = it + val availableToAddWallet = getAvailableToAddWallet(it) wallet.walletId to availableToAddWallet } .filter { (_, wallet) -> wallet.availableToAddAccounts.isNotEmpty() } @@ -64,8 +78,13 @@ class AvailableToAddDataConverter @Inject constructor( networks = networks, ) - private fun AccountStatus.isAlreadyAddedNetworks(network: TokenMarketInfo.Network): Boolean = when (this) { - is AccountStatus.CryptoPortfolio -> this.tokenList.flattenCurrencies() - .any { status -> network.networkId == status.currency.network.backendId } - } + private suspend fun createCryptoCurrency( + userWallet: UserWallet, + network: TokenMarketInfo.Network, + marketParams: TokenMarketParams, + ): CryptoCurrency? = getTokenMarketCryptoCurrency( + userWalletId = userWallet.walletId, + tokenMarketParams = marketParams, + network = network, + ) } \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt index b74ecede69..8e227d1ec8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenModel.kt @@ -4,7 +4,8 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.domain.markets.SaveMarketTokensUseCase +import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase +import com.tangem.domain.account.status.usecase.SaveCryptoCurrenciesUseCase import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase import com.tangem.features.markets.portfolio.add.api.SelectedNetwork import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio @@ -20,13 +21,15 @@ import kotlinx.coroutines.launch import javax.inject.Inject @ModelScoped +@Suppress("LongParameterList") internal class AddTokenModel @Inject constructor( paramsContainer: ParamsContainer, - private val saveMarketTokensUseCase: SaveMarketTokensUseCase, private val uiBuilder: AddTokenUiBuilder, private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase, override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, + private val saveCryptoCurrenciesUseCase: SaveCryptoCurrenciesUseCase, + private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase, ) : Model() { private val params = paramsContainer.require() @@ -37,13 +40,10 @@ internal class AddTokenModel @Inject constructor( field = MutableStateFlow(value = null) init { - channelFlow { - val selectedNetwork = params.selectedNetwork.stateIn(this) - val selectedPortfolio = params.selectedPortfolio.stateIn(this) - combine( - flow = selectedNetwork, - flow2 = selectedPortfolio, - ) { selectedNetwork, selectedPortfolio -> + combine( + flow = params.selectedNetwork.distinctUntilChanged(), + flow2 = params.selectedPortfolio.distinctUntilChanged(), + transform = { selectedNetwork, selectedPortfolio -> addTokenJob.cancel() val isTangemIconVisible = needColdWalletInteraction(selectedNetwork, selectedPortfolio) uiBuilder.updateContent( @@ -52,29 +52,35 @@ internal class AddTokenModel @Inject constructor( isTangemIconVisible = isTangemIconVisible, onConfirmClick = { onAddClick(selectedNetwork, selectedPortfolio).saveIn(addTokenJob) }, ) - }.collect { newUI -> send(newUI) } - } - .flowOn(dispatchers.default) + }, + ) .onEach { newUI -> uiState.value = newUI } + .flowOn(dispatchers.default) .launchIn(modelScope) } - private fun onAddClick(selectedNetwork: SelectedNetwork, selectedPortfolio: SelectedPortfolio) = modelScope.launch { - val um = uiState.value ?: return@launch - uiState.value = um.toggleProgress(true) - val blockchainNames = listOf(selectedNetwork.selectedNetwork) - .mapNotNull { BlockchainUtils.getNetworkInfo(it.networkId)?.name } - analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioContinue(blockchainNames)) - // todo account - saveMarketTokensUseCase( - userWalletId = selectedPortfolio.userWallet.walletId, - tokenMarketParams = params.marketParams, - addedNetworks = setOf(selectedNetwork.selectedNetwork), - removedNetworks = setOf(), - ) - params.callbacks.onTokenAdded() - uiState.value = um.toggleProgress(false) - } + private fun onAddClick(selectedNetwork: SelectedNetwork, selectedPortfolio: SelectedPortfolio) = + modelScope.launch(dispatchers.default) { + val um = uiState.value ?: return@launch + uiState.value = um.toggleProgress(true) + val blockchainNames = listOf(selectedNetwork.selectedNetwork) + .mapNotNull { BlockchainUtils.getNetworkInfo(it.networkId)?.name } + analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioContinue(blockchainNames)) + val cryptoCurrency = selectedNetwork.cryptoCurrency + val accountId = selectedPortfolio.account.account.account.accountId + saveCryptoCurrenciesUseCase( + accountId = accountId, + add = listOf(cryptoCurrency), + remove = listOf(), + ) + val status = getAccountCurrencyStatusUseCase.invokeSync( + userWalletId = accountId.userWalletId, + currencyId = cryptoCurrency.id, + network = cryptoCurrency.network, + ).getOrNull() ?: return@launch + params.callbacks.onTokenAdded(status.status) + uiState.value = um.toggleProgress(false) + } private suspend fun needColdWalletInteraction( selectedNetwork: SelectedNetwork, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt index 4d4dd3366a..d0823406f3 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/add/impl/model/AddTokenUiBuilder.kt @@ -16,9 +16,10 @@ import com.tangem.features.markets.portfolio.add.api.SelectedNetwork import com.tangem.features.markets.portfolio.add.api.SelectedPortfolio import com.tangem.features.markets.portfolio.add.impl.AddTokenComponent import com.tangem.features.markets.portfolio.add.impl.ui.state.AddTokenUM +import javax.inject.Inject @ModelScoped -internal class AddTokenUiBuilder( +internal class AddTokenUiBuilder @Inject constructor( paramsContainer: ParamsContainer, ) { private val params = paramsContainer.require() @@ -41,9 +42,10 @@ internal class AddTokenUiBuilder( portfolioName = stringReference(selectedPortfolio.userWallet.name) } true -> { - portfolioName = selectedPortfolio.account.account.accountName.toUM().value - accountIcon = when (selectedPortfolio.account) { - is AccountStatus.CryptoPortfolio -> selectedPortfolio.account.account.icon.toUM() + val accountStatus = selectedPortfolio.account.account + portfolioName = accountStatus.account.accountName.toUM().value + accountIcon = when (accountStatus) { + is AccountStatus.CryptoPortfolio -> accountStatus.account.icon.toUM() } } } @@ -61,8 +63,11 @@ internal class AddTokenUiBuilder( isTangemIconVisible: Boolean, onConfirmClick: () -> Unit, ): AddTokenUM { + // its may happens when change portfolio after selected both params in line navigation + val isAvailableNetwork = selectedPortfolio.account.availableToAddNetworks + .any { selectedNetwork.selectedNetwork.networkId == it.networkId } val button = AddTokenUM.Button( - isEnabled = true, + isEnabled = isAvailableNetwork, showProgress = false, isTangemIconVisible = isTangemIconVisible, text = resourceReference(R.string.common_add),