From 435e579a6122fd47b133700e984db25023465383 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 25 Feb 2026 19:06:24 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../impl/DefaultMarketsPortfolioComponent.kt | 3 +- .../impl/loader/PortfolioDataLoader.kt | 135 ------ .../model/AddToPortfolioBSContentUMFactory.kt | 161 ------- .../impl/model/AddToPortfolioManager.kt | 193 -------- ...elegate.kt => MarketsPortfolioDelegate.kt} | 10 +- .../impl/model/MarketsPortfolioModel.kt | 415 +++--------------- .../impl/model/MyPortfolioUMFactory.kt | 153 ------- .../impl/model/PortfolioBSVisibilityModel.kt | 14 - .../portfolio/impl/model/PortfolioUIData.kt | 20 - .../impl/model/SelectNetworkUMConverter.kt | 37 -- .../impl/model/TokensPortfolioUMConverter.kt | 112 ----- .../impl/ui/AddToPortfolioBottomSheet.kt | 383 ---------------- .../details/portfolio/impl/ui/MyPortfolio.kt | 5 - .../impl/ui/TokenActionsBottomSheet.kt | 86 ---- .../impl/ui/WalletSelectorBottomSheet.kt | 145 ------ .../PreviewAddToPortfolioBSContentProvider.kt | 86 ---- .../preview/PreviewMyPortfolioUMProvider.kt | 9 +- .../ui/state/AddToPortfolioBSContentUM.kt | 15 - .../portfolio/impl/ui/state/MyPortfolioUM.kt | 22 +- .../impl/ui/state/SelectNetworkUM.kt | 13 - .../ui/state/WalletSelectorBSContentUM.kt | 10 - 21 files changed, 78 insertions(+), 1949 deletions(-) delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/loader/PortfolioDataLoader.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioManager.kt rename features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/{NewMarketsPortfolioDelegate.kt => MarketsPortfolioDelegate.kt} (97%) delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MyPortfolioUMFactory.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioBSVisibilityModel.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioUIData.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/SelectNetworkUMConverter.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/AddToPortfolioBottomSheet.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/TokenActionsBottomSheet.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/WalletSelectorBottomSheet.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/AddToPortfolioBSContentUM.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/SelectNetworkUM.kt delete mode 100644 features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/DefaultMarketsPortfolioComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/DefaultMarketsPortfolioComponent.kt index 6df78dda91..a1cf255e9b 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/DefaultMarketsPortfolioComponent.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/DefaultMarketsPortfolioComponent.kt @@ -58,7 +58,6 @@ internal class DefaultMarketsPortfolioComponent @AssistedInject constructor( bottomSheet.child?.instance?.BottomSheet() } - @Suppress("UnsafeCallOnNullableType") private fun bottomSheetChild( config: MarketsPortfolioRoute, componentContext: ComponentContext, @@ -66,7 +65,7 @@ internal class DefaultMarketsPortfolioComponent @AssistedInject constructor( MarketsPortfolioRoute.AddToPortfolio -> addToPortfolioComponentFactory.create( context = childByContext(componentContext), params = AddToPortfolioComponent.Params( - addToPortfolioManager = model.newAddToPortfolioManager!!, + addToPortfolioManager = model.addToPortfolioManager, callback = model.addToPortfolioCallback, ), ) diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/loader/PortfolioDataLoader.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/loader/PortfolioDataLoader.kt deleted file mode 100644 index c22e143ea1..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/loader/PortfolioDataLoader.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.loader - -import arrow.core.getOrElse -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase -import com.tangem.domain.core.lce.Lce -import com.tangem.domain.models.TotalFiatBalance -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.tokens.GetAllWalletsCryptoCurrencyStatusesUseCase -import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase -import com.tangem.domain.tokens.GetWalletTotalBalanceUseCase -import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.yield.supply.models.YieldSupplyAvailability -import com.tangem.domain.yield.supply.usecase.YieldSupplyGetAvailabilityUseCase -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.* -import javax.inject.Inject - -/** - * Loader of portfolio data - * - * @property getAllWalletsCryptoCurrencyStatusesUseCase use case for getting all wallets crypto currency statuses - * @property getSelectedAppCurrencyUseCase use case for getting selected app currency - * @property getBalanceHidingSettingsUseCase use case for getting balance hiding settings - * @property getWalletTotalBalanceUseCase use case for getting wallet total balance - * -[REDACTED_AUTHOR] - */ -internal class PortfolioDataLoader @Inject constructor( - private val getAllWalletsCryptoCurrencyStatusesUseCase: GetAllWalletsCryptoCurrencyStatusesUseCase, - private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, - private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase, - private val yieldSupplyGetAvailabilityUseCase: YieldSupplyGetAvailabilityUseCase, - private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, -) { - - /** Load data by [currencyRawId] */ - @OptIn(ExperimentalCoroutinesApi::class) - fun load(currencyRawId: CryptoCurrency.RawID): Flow { - return combine( - flow = getAllWalletsCryptoCurrenciesData(currencyRawId = currencyRawId), - flow2 = getSelectedAppCurrencyUseCase.invokeOrDefault(), - flow3 = getBalanceHidingSettingsUseCase.isBalanceHidden(), - ) { walletsWithCurrencies, appCurrency, isBalanceHidden -> - PortfolioData( - walletsWithCurrencies = walletsWithCurrencies, - appCurrency = appCurrency, - isBalanceHidden = isBalanceHidden, - walletsWithBalance = emptyMap(), - ) - } - // setup balances for wallets from walletsWithCurrencyStatuses - .flatMapLatest { portfolioData -> - getWalletsWithTotalBalanceFlow( - ids = portfolioData.walletsWithCurrencies.keys.map(UserWallet::walletId), - ) - .map { portfolioData.copy(walletsWithBalance = it) } - .onEmpty { emit(portfolioData) } - } - } - - @OptIn(ExperimentalCoroutinesApi::class) - private fun getAllWalletsCryptoCurrenciesData( - currencyRawId: CryptoCurrency.RawID, - ): Flow>> { - return getAllWalletsCryptoCurrencyStatusesUseCase(currencyRawId) - .distinctUntilChanged() - .map { walletsWithMaybeStatuses -> - walletsWithMaybeStatuses.mapValues { entry -> - entry.value.mapNotNull { it.getOrNull() } - } - } - .flatMapLatest { walletsWithStatuses -> - val actionsFlows = walletsWithStatuses.flatMap { (wallet, statuses) -> - statuses.map { status -> - val yieldSupplyAvailability = yieldSupplyGetAvailabilityUseCase(status.currency).getOrElse { - YieldSupplyAvailability.Unavailable - } - getCryptoCurrencyActionsUseCase(wallet, status, yieldSupplyAvailability) - .map { tokenActionsState -> - PortfolioData.CryptoCurrencyData( - userWallet = wallet, - status = status, - actions = tokenActionsState.states, - ) - } - } - } - - combine(actionsFlows) { actions -> - walletsWithStatuses.mapValues { entry -> - entry.value.mapNotNull { status -> - actions.firstOrNull { - it.userWallet == entry.key && it.status == status - } - } - } - }.onEmpty { - emit( - walletsWithStatuses.mapValues { (wallet, statuses) -> - statuses.map { status -> - PortfolioData.CryptoCurrencyData( - userWallet = wallet, - status = status, - actions = emptyList(), - ) - } - }, - ) - } - }.onEmpty { - emit(emptyMap()) - } - .distinctUntilChanged() - } - - private fun getWalletsWithTotalBalanceFlow( - ids: List, - ): Flow>> { - return combine( - flows = ids - .map { userWalletId -> - getWalletTotalBalanceUseCase(userWalletId) - .map { userWalletId to it } - .distinctUntilChanged() - }, - transform = { it.toMap() }, - ) - .distinctUntilChanged() - .onEmpty { ids.associateWith { Lce.Loading(partialContent = null) } } - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt deleted file mode 100644 index e139659522..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ /dev/null @@ -1,161 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter -import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -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.TotalFiatBalance -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.AddToPortfolioBSContentUM -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.WalletSelectorBSContentUM -import kotlinx.collections.immutable.toImmutableList - -/** - * Factory to create AddToPortfolio bottom sheet content [TangemBottomSheetConfig] - * - * @property token token params - * @property onAddToPortfolioVisibilityChange callback is invoked when add to portfolio visibility is changed - * @property onWalletSelectorVisibilityChange callback is invoked when wallet selector visibility is changed - * @property onNetworkSwitchClick callback is invoked when network switch is clicked - * @property onAnotherWalletSelect callback is invoked when wallet is selected - * @property onContinueClick callback is invoked when continue button is clicked - * -[REDACTED_AUTHOR] - */ -@Suppress("LongParameterList") -internal class AddToPortfolioBSContentUMFactory( - private val addToPortfolioManager: AddToPortfolioManager, - private val token: TokenMarketParams, - private val onAddToPortfolioVisibilityChange: (Boolean) -> Unit, - private val onWalletSelectorVisibilityChange: (Boolean) -> Unit, - private val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit, - private val onAnotherWalletSelect: (UserWalletId) -> Unit, - private val onContinueClick: (selectedWalletId: UserWalletId, addedNetworks: Set) -> Unit, -) { - - /** - * Create [TangemBottomSheetConfig] - * - - * @param portfolioData portfolio data - * @param portfolioUIData portfolio bottom sheet visibility model - * @param selectedWallet selected wallet - * @param alreadyAddedNetworks already added networks - */ - @Suppress("LongParameterList") - fun create( - currentState: TangemBottomSheetConfig?, - portfolioData: PortfolioData, - portfolioUIData: PortfolioUIData, - selectedWallet: UserWallet?, - alreadyAddedNetworks: Set?, - artworks: Map, - ): TangemBottomSheetConfig { - return (currentState ?: TangemBottomSheetConfig.Empty).copy( - isShown = portfolioUIData.portfolioBSVisibilityModel.isAddToPortfolioBSVisible, - onDismissRequest = { onAddToPortfolioVisibilityChange(false) }, - content = if (selectedWallet != null && alreadyAddedNetworks != null) { - AddToPortfolioBSContentUM( - selectedWallet = selectedWallet.toSelectedUserWalletItemUM( - portfolioData = portfolioData, - balance = portfolioData.walletsWithBalance[selectedWallet.walletId]?.getOrNull(), - artwork = artworks[selectedWallet.walletId], - ), - selectNetworkUM = SelectNetworkUMConverter( - networksWithToggle = addToPortfolioManager.associateWithToggle( - userWalletId = selectedWallet.walletId, - alreadyAddedNetworkIds = alreadyAddedNetworks, - addToPortfolioData = portfolioUIData.addToPortfolioData, - ), - alreadyAddedNetworks = alreadyAddedNetworks, - onNetworkSwitchClick = onNetworkSwitchClick, - ).convert(value = token), - isScanCardNotificationVisible = portfolioUIData.isNeededColdWalletInteraction, - isContinueButtonEnabled = portfolioUIData.addToPortfolioData.isUserAddedNetworks( - userWalletId = selectedWallet.walletId, - ), - onContinueButtonClick = { - onContinueClick( - selectedWallet.walletId, - portfolioUIData.addToPortfolioData.getAddedNetworks( - userWalletId = selectedWallet.walletId, - alreadyAddedNetworkIds = alreadyAddedNetworks, - ), - ) - }, - walletSelectorConfig = createWalletSelectorBSConfig( - isShow = portfolioUIData.portfolioBSVisibilityModel.isWalletSelectorBSVisible, - portfolioData = portfolioData, - selectedWalletId = selectedWallet.walletId, - artworks = artworks, - ), - isWalletBlockVisible = portfolioData.walletsWithCurrencies - .filterKeys(UserWallet::isMultiCurrency).size > 1, - ) - } else { - TangemBottomSheetConfigContent.Empty - }, - ) - } - - private fun UserWallet.toSelectedUserWalletItemUM( - artwork: UserWalletItemUM.ImageState? = 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) - } - - private fun createWalletSelectorBSConfig( - isShow: Boolean, - portfolioData: PortfolioData, - selectedWalletId: UserWalletId, - artworks: Map, - ): TangemBottomSheetConfig { - return TangemBottomSheetConfig( - isShown = isShow, - onDismissRequest = { onWalletSelectorVisibilityChange(false) }, - content = WalletSelectorBSContentUM( - userWallets = portfolioData.walletsWithCurrencies - .filterKeys(UserWallet::isMultiCurrency) - .map { it.key } - .map { userWallet -> - val balance = portfolioData.walletsWithBalance[userWallet.walletId] - - UserWalletItemUMConverter( - onClick = { id -> - if (id != selectedWalletId) { - onAnotherWalletSelect(id) - onWalletSelectorVisibilityChange(false) - } - }, - appCurrency = portfolioData.appCurrency, - balance = balance?.getOrNull(), - isBalanceHidden = portfolioData.isBalanceHidden, - endIcon = if (userWallet.walletId == selectedWalletId) { - UserWalletItemUM.EndIcon.Checkmark - } else { - UserWalletItemUM.EndIcon.None - }, - artwork = artworks[userWallet.walletId], - ).convert(userWallet) - } - .toImmutableList(), - onBack = { onWalletSelectorVisibilityChange(false) }, - ), - ) - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioManager.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioManager.kt deleted file mode 100644 index d176536657..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/AddToPortfolioManager.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.domain.markets.FilterAvailableNetworksForWalletUseCase -import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.models.wallet.UserWalletId -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.update -import timber.log.Timber -import javax.inject.Inject -import kotlin.collections.firstOrNull -import kotlin.collections.orEmpty -import kotlin.collections.set - -internal typealias WalletsWithNetworks = Map> - -/** - * Manager for tracking changing networks in AddToPortfolio - * -[REDACTED_AUTHOR] - */ -internal class AddToPortfolioManager @Inject constructor( - private val filterAvailableNetworksForWalletUseCase: FilterAvailableNetworksForWalletUseCase, -) { - - val availableNetworks = MutableStateFlow?>(value = null) - private val addedNetworks = MutableStateFlow(value = emptyMap()) - private val removedNetworks = MutableStateFlow(value = emptyMap()) - - /** Get [AddToPortfolioData] as flow */ - fun getAddToPortfolioData(): Flow { - return combine( - flow = availableNetworks, - flow2 = addedNetworks, - flow3 = removedNetworks, - transform = ::AddToPortfolioData, - ) - } - - /** Set available networks [networks] */ - fun setAvailableNetworks(networks: List) { - availableNetworks.value = networks.toSet() - } - - /** Add network [networkId] to [userWalletId] */ - fun addNetwork(userWalletId: UserWalletId, networkId: String) { - addedNetworks.add(userWalletId, networkId) - - removedNetworks.cancelPrevChangeIfExist(userWalletId = userWalletId, networkId = networkId) - } - - /** Remove network [networkId] from [userWalletId] */ - fun removeNetwork(userWalletId: UserWalletId, networkId: String) { - removedNetworks.add(userWalletId, networkId) - - addedNetworks.cancelPrevChangeIfExist( - userWalletId = userWalletId, - networkId = networkId, - ) - } - - /** Remove all networks by [userWalletId] */ - fun removeAllChanges(userWalletId: UserWalletId) { - addedNetworks.update { - it.toMutableMap().apply { remove(userWalletId) } - } - - removedNetworks.update { - it.toMutableMap().apply { remove(userWalletId) } - } - } - - fun associateWithToggle( - userWalletId: UserWalletId, - alreadyAddedNetworkIds: Set, - addToPortfolioData: AddToPortfolioData, - ): Map { - val filteredNetworks = filterAvailableNetworksForWalletUseCase( - userWalletId = userWalletId, - networks = addToPortfolioData.availableNetworks.orEmpty(), - ) - // Use user choice or check already added networks - return filteredNetworks.associateWith { availableNetwork -> - val isAddedByUser = addToPortfolioData.addedNetworks[userWalletId]?.contains(availableNetwork) - - if (isAddedByUser == true) return@associateWith true - - val isRemovedByUser = addToPortfolioData.removedNetworks[userWalletId]?.contains(availableNetwork) - - if (isRemovedByUser == true) return@associateWith false - - val isAddedBefore = alreadyAddedNetworkIds.any { it == availableNetwork.networkId } - - isAddedBefore - } - } - - private fun MutableStateFlow.cancelPrevChangeIfExist( - userWalletId: UserWalletId, - networkId: String, - ) { - if (value[userWalletId].orEmpty().any { it.networkId == networkId }) remove(userWalletId, networkId) - } - - private fun MutableStateFlow.add(userWalletId: UserWalletId, networkId: String) { - change(userWalletId = userWalletId, networkId = networkId, isAddAction = true) - } - - private fun MutableStateFlow.remove(userWalletId: UserWalletId, networkId: String) { - change(userWalletId = userWalletId, networkId = networkId, isAddAction = false) - } - - private fun MutableStateFlow.change( - userWalletId: UserWalletId, - networkId: String, - isAddAction: Boolean, - ) { - val network = availableNetworks.value.orEmpty().firstOrNull { it.networkId == networkId } - - if (network == null) { - Timber.d( - "Network [$networkId] doesn't contain in available networks [%s]", - availableNetworks.value?.joinToString { it.networkId }, - ) - - return - } - - update { walletsWithNetworks -> - walletsWithNetworks.toMutableMap().apply { - this[userWalletId] = if (isAddAction) { - this[userWalletId].orEmpty() + network - } else { - this[userWalletId].orEmpty() - network - } - } - } - } - - /** - * Add to portfolio data - * - * @property availableNetworks available networks that user can add to portfolio - * @property addedNetworks networks that user toggled on, but it might have already been added to the wallet - * @property removedNetworks networks that user toggled off, but it might haven't been added to the wallet - * - * Example for [addedNetworks] and [removedNetworks]. This lists will include new networks when user just - * toggle it. But when we will save user changes, we will check what tokens have already been added or - * haven't been added to the wallet. See [getAddedNetworks] and [getRemovedNetworks] - */ - data class AddToPortfolioData( - val availableNetworks: Set?, - val addedNetworks: WalletsWithNetworks, - val removedNetworks: WalletsWithNetworks, - ) { - - fun isUserAddedNetworks(userWalletId: UserWalletId): Boolean { - return addedNetworks[userWalletId].orEmpty().isNotEmpty() - } - - fun isUserChangedNetworks(userWalletId: UserWalletId): Boolean { - return addedNetworks[userWalletId].orEmpty().isNotEmpty() || - removedNetworks[userWalletId].orEmpty().isNotEmpty() - } - - /** Get new networks that user [userWalletId] added using [alreadyAddedNetworkIds] */ - fun getAddedNetworks( - userWalletId: UserWalletId, - alreadyAddedNetworkIds: Set, - ): Set { - val addedNetworksByUser = addedNetworks[userWalletId].orEmpty() - - return addedNetworksByUser.map { it.networkId } - .minus(alreadyAddedNetworkIds) - .mapNotNull { networkId -> addedNetworksByUser.firstOrNull { it.networkId == networkId } } - .toSet() - } - - /** Get networks that user [userWalletId] removed using [alreadyAddedNetworkIds] */ - fun getRemovedNetworks( - userWalletId: UserWalletId, - alreadyAddedNetworkIds: Set, - ): Set { - val removedNetworksByUser = removedNetworks[userWalletId].orEmpty() - - return alreadyAddedNetworkIds - .minus(removedNetworksByUser.map { it.networkId }.toSet()) - .mapNotNull { networkId -> removedNetworksByUser.firstOrNull { it.networkId == networkId } } - .toSet() - } - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt similarity index 97% rename from features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt rename to features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt index 2df91aab47..50cf8f9881 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/NewMarketsPortfolioDelegate.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioDelegate.kt @@ -5,7 +5,6 @@ import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network import com.tangem.common.ui.account.AccountTitleUM import com.tangem.common.ui.account.CryptoPortfolioIconConverter import com.tangem.common.ui.account.toUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.account.models.AccountStatusList @@ -45,7 +44,7 @@ import kotlinx.coroutines.flow.* @OptIn(ExperimentalCoroutinesApi::class) @Suppress("LongParameterList") -internal class NewMarketsPortfolioDelegate @AssistedInject constructor( +internal class MarketsPortfolioDelegate @AssistedInject constructor( getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val allAccountSupplier: MultiAccountStatusListSupplier, @@ -106,10 +105,7 @@ internal class NewMarketsPortfolioDelegate @AssistedInject constructor( private fun addFirstTokenFlow(): Flow = buttonState.map { state -> when (state) { MyPortfolioUM.Tokens.AddButtonState.Loading -> MyPortfolioUM.Loading - MyPortfolioUM.Tokens.AddButtonState.Available -> MyPortfolioUM.AddFirstToken( - onAddClick = onAddClick, - addToPortfolioBSConfig = TangemBottomSheetConfig.Empty, - ) + MyPortfolioUM.Tokens.AddButtonState.Available -> MyPortfolioUM.AddFirstToken(onAddClick = onAddClick) MyPortfolioUM.Tokens.AddButtonState.Unavailable -> MyPortfolioUM.Unavailable } } @@ -314,7 +310,7 @@ internal class NewMarketsPortfolioDelegate @AssistedInject constructor( tokenActionsHandler: TokenActionsHandler, buttonState: Flow, onAddClick: () -> Unit, - ): NewMarketsPortfolioDelegate + ): MarketsPortfolioDelegate } } diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt index 71d48270b1..7c7818ad79 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MarketsPortfolioModel.kt @@ -5,91 +5,48 @@ import arrow.core.getOrElse import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss -import com.tangem.common.ui.userwallet.state.UserWalletItemUM 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.core.decompose.ui.UiMessageSender -import com.tangem.core.ui.components.rows.model.BlockchainRowUM -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.core.ui.extensions.wrappedList -import com.tangem.core.ui.message.DialogMessage -import com.tangem.core.ui.message.SnackbarMessage -import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency -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.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.transaction.usecase.ReceiveAddressesFactory -import com.tangem.domain.wallets.usecase.ColdWalletAndHasMissedDerivationsUseCase -import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioComponent +import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent import com.tangem.features.feed.components.market.details.portfolio.impl.analytics.PortfolioAnalyticsEvent -import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData -import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioDataLoader import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM -import com.tangem.features.feed.impl.R -import com.tangem.features.wallet.utils.UserWalletImageFetcher -import com.tangem.lib.crypto.BlockchainUtils -import com.tangem.operations.attestation.ArtworkSize import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import timber.log.Timber import javax.inject.Inject -import com.tangem.features.feed.components.market.details.portfolio.add.AddToPortfolioManager as NewAddToPortfolioManager -@Suppress("LongParameterList", "LargeClass") +@Suppress("LongParameterList") @Stable @ModelScoped internal class MarketsPortfolioModel @Inject constructor( paramsContainer: ParamsContainer, - getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, - tokenActionsIntentsFactory: TokenActionsHandler.Factory, - override val dispatchers: CoroutineDispatcherProvider, - private val messageSender: UiMessageSender, - private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase, - private val getSelectedWalletUseCase: GetSelectedWalletUseCase, - private val portfolioDataLoader: PortfolioDataLoader, - private val coldWalletAndHasMissedDerivationsUseCase: ColdWalletAndHasMissedDerivationsUseCase, - private val saveMarketTokensUseCase: SaveMarketTokensUseCase, - private val addToPortfolioManager: AddToPortfolioManager, - private val analyticsEventHandler: AnalyticsEventHandler, - private val userWalletImageFetcher: UserWalletImageFetcher, + private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory, + private val marketsPortfolioDelegateFactory: MarketsPortfolioDelegate.Factory, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val tokenActionsHandlerFactory: TokenActionsHandler.Factory, private val receiveAddressesFactory: ReceiveAddressesFactory, - accountsFeatureToggles: AccountsFeatureToggles, - newAddToPortfolioManagerFactory: NewAddToPortfolioManager.Factory, - newMarketsPortfolioDelegateFactory: NewMarketsPortfolioDelegate.Factory, + private val analyticsEventHandler: AnalyticsEventHandler, + override val dispatchers: CoroutineDispatcherProvider, ) : Model() { - private val _state: MutableStateFlow = MutableStateFlow(value = MyPortfolioUM.Loading) - val state: StateFlow get() = _state + val state: StateFlow + field = MutableStateFlow(value = MyPortfolioUM.Loading) private val params = paramsContainer.require() - private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder( - tokenSymbol = params.token.symbol, - source = params.analyticsParams?.source, - ) - val newAddToPortfolioManager: NewAddToPortfolioManager? - val newMarketsPortfolioDelegate: NewMarketsPortfolioDelegate? - - /** Multi-wallet [UserWalletId] that user uses to add new tokens in AddToPortfolio bottom sheet */ - private val selectedMultiWalletIdFlow = MutableStateFlow(value = null) - - private val portfolioBSVisibilityModelFlow = MutableStateFlow(value = PortfolioBSVisibilityModel()) + val addToPortfolioManager: AddToPortfolioManager = createAddToPortfolioManager() + private val marketsPortfolioDelegate: MarketsPortfolioDelegate = createMarketsPortfolioDelegate() val bottomSheetNavigation: SlotNavigation = SlotNavigation() val addToPortfolioCallback = object : AddToPortfolioComponent.Callback { @@ -97,321 +54,87 @@ internal class MarketsPortfolioModel @Inject constructor( override fun onSuccess(addedToken: CryptoCurrency) = bottomSheetNavigation.dismiss() } - private val currentAppCurrency = getSelectedAppCurrencyUseCase() - .map { maybeAppCurrency -> - maybeAppCurrency.getOrElse { AppCurrency.Default } - } - .stateIn( - scope = modelScope, - started = SharingStarted.Eagerly, - initialValue = AppCurrency.Default, - ) - - private val tokenActionsHandler = tokenActionsIntentsFactory.create( - currentAppCurrency = Provider { currentAppCurrency.value }, - onHandleQuickAction = { handledAction -> - analyticsEventHandler.send( - analyticsEventBuilder.quickActionClick( - actionUM = handledAction.action, - blockchainName = handledAction - .cryptoCurrencyData - .status - .currency - .network - .name, - ), - ) - configureReceiveAddresses(handledAction) - }, + private val analyticsEventBuilder = PortfolioAnalyticsEvent.EventBuilder( + tokenSymbol = params.token.symbol, + source = params.analyticsParams?.source, ) - private val factory = MyPortfolioUMFactory( - onAddClick = { - onAddToPortfolioBSVisibilityChange(isShow = true) - // === Analytics === - analyticsEventHandler.send( - analyticsEventBuilder.addToPortfolioClicked(), - ) - }, - addToPortfolioBSContentUMFactory = AddToPortfolioBSContentUMFactory( - addToPortfolioManager = addToPortfolioManager, - token = params.token, - onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange, - onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange, - onNetworkSwitchClick = ::onNetworkSwitchClick, - onAnotherWalletSelect = { walletId -> - onWalletSelect(walletId) - // === Analytics === - analyticsEventHandler.send( - analyticsEventBuilder.addToPortfolioWalletChanged(), - ) - }, - onContinueClick = { selectedWalletId, addedNetworks -> - onContinueClick(selectedWalletId, addedNetworks) + private val currentAppCurrency = createAppCurrencyFlow() - // === Analytics === - analyticsEventHandler.send( - analyticsEventBuilder.addToPortfolioContinue( - blockchainNames = addedNetworks.mapNotNull { - BlockchainUtils.getNetworkInfo(it.networkId)?.name - }, - ), - ) - }, - ), - currentState = Provider { _state.value }, - tokenActionsHandler = tokenActionsHandler, - updateTokens = { updateBlock -> - updateTokensState { state -> - state.copy(tokens = updateBlock(state.tokens)) - } - }, - ) + private val tokenActionsHandler = createTokenActionsHandler() init { - if (accountsFeatureToggles.isFeatureEnabled) { - newAddToPortfolioManager = newAddToPortfolioManagerFactory - .create( - modelScope, - params.token, - params.analyticsParams?.source?.let { NewAddToPortfolioManager.AnalyticsParams(it) }, - ) - newMarketsPortfolioDelegate = newMarketsPortfolioDelegateFactory.create( - scope = modelScope, - token = params.token, - tokenActionsHandler = tokenActionsHandler, - buttonState = newAddToPortfolioManager.state.map { state -> - when (state) { - is NewAddToPortfolioManager.State.AvailableToAdd -> { - MyPortfolioUM.Tokens.AddButtonState.Available - } - NewAddToPortfolioManager.State.Init -> MyPortfolioUM.Tokens.AddButtonState.Loading - NewAddToPortfolioManager.State.NothingToAdd -> MyPortfolioUM.Tokens.AddButtonState.Unavailable - } - }, - onAddClick = { - analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioClicked()) - bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio) - }, - ) - newMarketsPortfolioDelegate.combineData() - .onEach { _state.value = it } - .flowOn(dispatchers.default) - .launchIn(modelScope) - } else { - newAddToPortfolioManager = null - newMarketsPortfolioDelegate = null - // Subscribe on selected wallet flow to support actual selected wallet - subscribeOnSelectedMultiWalletUpdates() - - subscribeOnStateUpdates() - } + marketsPortfolioDelegate.combineData() + .onEach { state.value = it } + .flowOn(dispatchers.default) + .launchIn(modelScope) } fun setTokenNetworks(networks: List) { - addToPortfolioManager.setAvailableNetworks(networks) - newAddToPortfolioManager?.setTokenNetworks(networks) - newMarketsPortfolioDelegate?.setTokenNetworks(networks) + addToPortfolioManager.setTokenNetworks(networks) + marketsPortfolioDelegate.setTokenNetworks(networks) } fun setNoNetworksAvailable() { - addToPortfolioManager.setAvailableNetworks(emptyList()) - newAddToPortfolioManager?.setTokenNetworks(emptyList()) - newMarketsPortfolioDelegate?.setTokenNetworks(emptyList()) + addToPortfolioManager.setTokenNetworks(emptyList()) + marketsPortfolioDelegate.setTokenNetworks(emptyList()) } - private fun subscribeOnSelectedMultiWalletUpdates() { - getSelectedWalletUseCase() - .getOrElse { e -> - Timber.e("Failed to load selected wallet: $e") - error("Failed to load selected wallet") - } - .onEach { userWallet -> - selectedMultiWalletIdFlow.value = userWallet.takeIf { it.isMultiCurrency }?.walletId - } - .launchIn(modelScope) - } - - private fun subscribeOnStateUpdates() { - combine( - flow = loadPortfolioDataWithArtworks(params.token.id), - flow2 = getPortfolioUIDataFlow(), - transform = { pair, portfolioUIData -> - val (portfolioData, artworks) = pair - factory.create(portfolioData, portfolioUIData, artworks) - }, - ) - .onEach { _state.value = it } - .launchIn(modelScope) - } - - private fun loadPortfolioDataWithArtworks( - currencyRawId: CryptoCurrency.RawID, - ): Flow>> { - val wallets = Channel>() - val portfolioFlow = portfolioDataLoader - .load(currencyRawId) - .onEach { wallets.trySend(it.walletsWithCurrencies.keys) } - - val artworksFlow = wallets.receiveAsFlow() - .distinctUntilChanged() - .flatMapLatest { userWalletImageFetcher.walletsImage(wallets = it, size = ArtworkSize.SMALL) } - - return combine( - flow = portfolioFlow, - flow2 = artworksFlow, - ) { portfolioData, artworks -> portfolioData to artworks } - } - - private fun getPortfolioUIDataFlow(): Flow { - return combine( - flow = portfolioBSVisibilityModelFlow, - flow2 = selectedMultiWalletIdFlow, - flow3 = addToPortfolioManager.getAddToPortfolioData(), - transform = { portfolioBSVisibilityModel, selectedWalletId, addToPortfolioData -> - PortfolioUIData( - portfolioBSVisibilityModel = portfolioBSVisibilityModel, - selectedWalletId = selectedWalletId, - addToPortfolioData = addToPortfolioData, - isNeededColdWalletInteraction = isNeededColdWalletInteraction(selectedWalletId, addToPortfolioData), - ) - }, + private fun createAddToPortfolioManager(): AddToPortfolioManager { + return addToPortfolioManagerFactory.create( + scope = modelScope, + token = params.token, + analyticsParams = params.analyticsParams?.source?.let { AddToPortfolioManager.AnalyticsParams(it) }, ) } - private suspend fun isNeededColdWalletInteraction( - selectedWalletId: UserWalletId?, - addToPortfolioData: AddToPortfolioManager.AddToPortfolioData, - ): Boolean { - return if (selectedWalletId != null) { - coldWalletAndHasMissedDerivationsUseCase.invoke( - userWalletId = selectedWalletId, - networksWithDerivationPath = addToPortfolioData.addedNetworks[selectedWalletId].orEmpty() - .associate { it.networkId to null }, - ) - } else { - false - } - } - - private fun onNetworkSwitchClick(blockchainRowUM: BlockchainRowUM, isChecked: Boolean) { - val selectedWalletId = selectedMultiWalletIdFlow.value - - if (selectedWalletId == null) { - Timber.e("Impossible to switch network when selected wallet is null") - return - } - - if (isChecked) { - modelScope.launch { - val unsupportedState = checkCurrencyUnsupportedState( - userWalletId = selectedWalletId, - rawNetworkId = blockchainRowUM.id, - isMainNetwork = blockchainRowUM.isMainNetwork, - ) - if (unsupportedState != null) { - showUnsupportedWarning(unsupportedState) - } else { - addToPortfolioManager.addNetwork(userWalletId = selectedWalletId, networkId = blockchainRowUM.id) + private fun createMarketsPortfolioDelegate(): MarketsPortfolioDelegate { + return marketsPortfolioDelegateFactory.create( + scope = modelScope, + token = params.token, + tokenActionsHandler = tokenActionsHandler, + buttonState = addToPortfolioManager.state.map { state -> + when (state) { + is AddToPortfolioManager.State.AvailableToAdd -> { + MyPortfolioUM.Tokens.AddButtonState.Available + } + AddToPortfolioManager.State.Init -> MyPortfolioUM.Tokens.AddButtonState.Loading + AddToPortfolioManager.State.NothingToAdd -> MyPortfolioUM.Tokens.AddButtonState.Unavailable } - } - } else { - addToPortfolioManager.removeNetwork(userWalletId = selectedWalletId, networkId = blockchainRowUM.id) - } - } - - private suspend fun checkCurrencyUnsupportedState( - userWalletId: UserWalletId, - rawNetworkId: String, - isMainNetwork: Boolean, - ): CurrencyUnsupportedState? { - return checkCurrencyUnsupportedUseCase( - userWalletId = userWalletId, - networkId = rawNetworkId, - isMainNetwork = isMainNetwork, - ).getOrElse { throwable -> - Timber.e( - throwable, - """ - Failed to check currency unsupported state - |- User wallet ID: $userWalletId - |- Network ID: $rawNetworkId - |- Is main network: $isMainNetwork - """.trimIndent(), - ) - - val message = SnackbarMessage( - message = throwable.localizedMessage - ?.let(::stringReference) - ?: resourceReference(R.string.common_error), - ) - messageSender.send(message) - - null - } - } - - private fun showUnsupportedWarning(unsupportedState: CurrencyUnsupportedState) { - val message = DialogMessage( - message = when (unsupportedState) { - is CurrencyUnsupportedState.Token.NetworkTokensUnsupported -> resourceReference( - id = R.string.alert_manage_tokens_unsupported_message, - formatArgs = wrappedList(unsupportedState.networkName), - ) - is CurrencyUnsupportedState.Token.UnsupportedCurve -> resourceReference( - id = R.string.alert_manage_tokens_unsupported_curve_message, - formatArgs = wrappedList(unsupportedState.networkName), - ) - is CurrencyUnsupportedState.UnsupportedNetwork -> resourceReference( - id = R.string.alert_manage_tokens_unsupported_curve_message, - formatArgs = wrappedList(unsupportedState.networkName), - ) + }, + onAddClick = { + analyticsEventHandler.send(analyticsEventBuilder.addToPortfolioClicked()) + bottomSheetNavigation.activate(MarketsPortfolioRoute.AddToPortfolio) }, ) - - messageSender.send(message) } - private fun onWalletSelect(userWalletId: UserWalletId) { - selectedMultiWalletIdFlow.update { prevUserWalletId -> - prevUserWalletId?.let(addToPortfolioManager::removeAllChanges) - - userWalletId - } - } - - private fun onContinueClick(userWalletId: UserWalletId, addedNetworks: Set) { - modelScope.launch { - saveMarketTokensUseCase( - userWalletId = userWalletId, - tokenMarketParams = params.token, - addedNetworks = addedNetworks, - removedNetworks = emptySet(), + private fun createAppCurrencyFlow(): StateFlow { + return getSelectedAppCurrencyUseCase() + .map { maybeAppCurrency -> + maybeAppCurrency.getOrElse { AppCurrency.Default } + } + .stateIn( + scope = modelScope, + started = SharingStarted.Eagerly, + initialValue = AppCurrency.Default, ) - - onAddToPortfolioBSVisibilityChange(isShow = false) - - addToPortfolioManager.removeAllChanges(userWalletId) - } } - private fun onAddToPortfolioBSVisibilityChange(isShow: Boolean) { - portfolioBSVisibilityModelFlow.update { - it.copy(isAddToPortfolioBSVisible = isShow, isWalletSelectorBSVisible = false) - } - } - - private fun onWalletSelectorVisibilityChange(isShow: Boolean) { - portfolioBSVisibilityModelFlow.update { - it.copy(isAddToPortfolioBSVisible = true, isWalletSelectorBSVisible = isShow) - } - } - - private fun updateTokensState(block: (MyPortfolioUM.Tokens) -> MyPortfolioUM) { - _state.update { stateToUpdate -> - val tokensState = stateToUpdate as? MyPortfolioUM.Tokens ?: return@update stateToUpdate - block(tokensState) - } + private fun createTokenActionsHandler(): TokenActionsHandler { + return tokenActionsHandlerFactory.create( + currentAppCurrency = Provider { currentAppCurrency.value }, + onHandleQuickAction = { handledAction -> + val currency = handledAction.cryptoCurrencyData.status.currency + analyticsEventHandler.send( + analyticsEventBuilder.quickActionClick( + actionUM = handledAction.action, + blockchainName = currency.network.name, + ), + ) + configureReceiveAddresses(handledAction) + }, + ) } private fun configureReceiveAddresses(quickAction: TokenActionsHandler.HandledQuickAction) { diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MyPortfolioUMFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MyPortfolioUMFactory.kt deleted file mode 100644 index 72ff24db98..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/MyPortfolioUMFactory.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.models.wallet.isMultiCurrency -import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM -import com.tangem.utils.Provider -import kotlinx.collections.immutable.ImmutableList - -/** - * Factory for creating [MyPortfolioUM] - * - * @property onAddClick callback when user wants to add new token - * @property addToPortfolioBSContentUMFactory factory for creating add to portfolio bottom sheet content - * @property tokenActionsHandler token actions handler - * @property currentState current state provider - * @property updateTokens callback for updating tokens - * -[REDACTED_AUTHOR] - */ -internal class MyPortfolioUMFactory( - private val onAddClick: () -> Unit, - private val addToPortfolioBSContentUMFactory: AddToPortfolioBSContentUMFactory, - private val tokenActionsHandler: TokenActionsHandler, - private val currentState: Provider, - private val updateTokens: ((ImmutableList) -> ImmutableList) -> Unit, -) { - - fun create( - portfolioData: PortfolioData, - portfolioUIData: PortfolioUIData, - artworks: Map, - ): MyPortfolioUM { - val addToPortfolioData = portfolioUIData.addToPortfolioData - - val isOnlyUnavailableNetworks = addToPortfolioData.availableNetworks?.isEmpty() == true - if (isOnlyUnavailableNetworks) return MyPortfolioUM.Unavailable - - val walletsWithCurrencies = if (addToPortfolioData.availableNetworks == null) { - portfolioData.walletsWithCurrencies - } else { - portfolioData.walletsWithCurrencies.filterAvailableNetworks(networks = addToPortfolioData.availableNetworks) - } - - val isPortfolioEmpty = walletsWithCurrencies.flatMap { it.value }.isEmpty() - if (isPortfolioEmpty) { - val hasMultiWallets = walletsWithCurrencies.filterKeys(UserWallet::isMultiCurrency).isNotEmpty() - - return if (hasMultiWallets) { - MyPortfolioUM.AddFirstToken( - addToPortfolioBSConfig = createAddToPortfolioBSConfig( - portfolioData = portfolioData, - portfolioUIData = portfolioUIData, - artworks = artworks, - ), - onAddClick = onAddClick, - ) - } else { - MyPortfolioUM.UnavailableForWallet - } - } - - return TokensPortfolioUMConverter( - appCurrency = portfolioData.appCurrency, - isBalanceHidden = portfolioData.isBalanceHidden, - addButtonState = walletsWithCurrencies.getAddButtonState( - availableNetworks = addToPortfolioData.availableNetworks, - ), - bsConfig = createAddToPortfolioBSConfig( - portfolioData = portfolioData, - portfolioUIData = portfolioUIData, - artworks = artworks, - ), - onAddClick = onAddClick, - quickActionsIntents = tokenActionsHandler, - currentState = currentState, - updateTokens = updateTokens, - ) - .convert(walletsWithCurrencies) - } - - private fun createAddToPortfolioBSConfig( - portfolioData: PortfolioData, - portfolioUIData: PortfolioUIData, - artworks: Map, - ): TangemBottomSheetConfig { - val selectedWallet = portfolioData.walletsWithCurrencies.keys - .firstOrNull { it.walletId == portfolioUIData.selectedWalletId } - ?: portfolioData.walletsWithCurrencies.keys.firstOrNull { it.isMultiCurrency } - - val availableNetworks = portfolioUIData.addToPortfolioData.availableNetworks.orEmpty() - - val alreadyAddedNetworks = portfolioData.walletsWithCurrencies - .filterAvailableNetworks(availableNetworks)[selectedWallet] - ?.filter { !it.status.currency.isCustom } - ?.map { it.status.currency.network.backendId } - ?.toSet() - - return addToPortfolioBSContentUMFactory.create( - currentState = currentState().addToPortfolioBSConfig, - portfolioData = portfolioData, - portfolioUIData = portfolioUIData, - selectedWallet = selectedWallet, - alreadyAddedNetworks = alreadyAddedNetworks, - artworks = artworks, - ) - } - - private fun Map>.getAddButtonState( - availableNetworks: Set?, - ): MyPortfolioUM.Tokens.AddButtonState { - if (availableNetworks == null) return MyPortfolioUM.Tokens.AddButtonState.Loading - - val networkIds = availableNetworks.map { it.networkId } - - val isAllAvailableNetworksAdded = this - // User can add currencies only in multi-currency wallets - .filterKeys(UserWallet::isMultiCurrency) - .mapValues { entry -> entry.value.map { it.status.currency.network.backendId } } - // Each wallets contains all available networks? - .all { it.value.containsAll(networkIds) } - - return if (isAllAvailableNetworksAdded) { - MyPortfolioUM.Tokens.AddButtonState.Unavailable - } else { - MyPortfolioUM.Tokens.AddButtonState.Available - } - } - - /** Filter map values by available networks [networks] */ - private fun Map>.filterAvailableNetworks( - networks: Set, - ): Map> { - return mapValues { entry -> entry.value.filterAvailableNetworks(networks) } - } - - /** Filter list of [CryptoCurrencyStatus] by available networks [networks] */ - private fun List.filterAvailableNetworks( - networks: Set, - ): List { - val networkIds = networks.map(TokenMarketInfo.Network::networkId) - - return mapNotNull { cryptoCurrencyData -> - cryptoCurrencyData.takeIf { networkIds.contains(it.status.currency.network.backendId) } - } - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioBSVisibilityModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioBSVisibilityModel.kt deleted file mode 100644 index dd3f13b3e7..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioBSVisibilityModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -/** - * Model for portfolio bottom sheet visibility - * - * @property isAddToPortfolioBSVisible visibility of add to portfolio bottom sheet - * @property isWalletSelectorBSVisible visibility of wallet selector bottom sheet - * -[REDACTED_AUTHOR] - */ -internal data class PortfolioBSVisibilityModel( - val isAddToPortfolioBSVisible: Boolean = false, - val isWalletSelectorBSVisible: Boolean = false, -) \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioUIData.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioUIData.kt deleted file mode 100644 index 894b86dc98..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/PortfolioUIData.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.domain.models.wallet.UserWalletId - -/** - * Portfolio UI data. Combined data from all UI flows that required to setup portfolio - * - * @property portfolioBSVisibilityModel portfolio bottom sheet visibility model - * @property selectedWalletId selected wallet id - * @property addToPortfolioData add to portfolio data - * @property isNeededColdWalletInteraction flag that indicates if user has missed derivations and has a cold wallet - * -[REDACTED_AUTHOR] - */ -internal data class PortfolioUIData( - val portfolioBSVisibilityModel: PortfolioBSVisibilityModel, - val selectedWalletId: UserWalletId?, - val addToPortfolioData: AddToPortfolioManager.AddToPortfolioData, - val isNeededColdWalletInteraction: Boolean, -) \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/SelectNetworkUMConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/SelectNetworkUMConverter.kt deleted file mode 100644 index 5f9c6b282e..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/SelectNetworkUMConverter.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.core.ui.components.rows.model.BlockchainRowUM -import com.tangem.domain.markets.TokenMarketInfo -import com.tangem.domain.markets.TokenMarketParams -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.SelectNetworkUM -import com.tangem.utils.converter.Converter -import kotlinx.collections.immutable.toImmutableList - -/** - * Converter from [TokenMarketParams] to [SelectNetworkUM] - * - * @property networksWithToggle map of networks with toggles - * @property alreadyAddedNetworks already added networks - * @property onNetworkSwitchClick callback is called when network switch is clicked - * -[REDACTED_AUTHOR] - */ -internal class SelectNetworkUMConverter( - private val networksWithToggle: Map, - private val alreadyAddedNetworks: Set, - private val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit, -) : Converter { - - override fun convert(value: TokenMarketParams): SelectNetworkUM { - return SelectNetworkUM( - tokenId = value.id.value, - iconUrl = value.imageUrl, - tokenName = value.name, - tokenCurrencySymbol = value.symbol, - networks = BlockchainRowUMConverter(alreadyAddedNetworks) - .convertList(networksWithToggle.toList()) - .toImmutableList(), - onNetworkSwitchClick = { um, isChecked -> onNetworkSwitchClick(um, isChecked) }, - ) - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt deleted file mode 100644 index 249415e21f..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/model/TokensPortfolioUMConverter.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.model - -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.features.feed.components.market.details.portfolio.impl.loader.PortfolioData -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.MyPortfolioUM -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.PortfolioTokenUM -import com.tangem.utils.Provider -import com.tangem.utils.converter.Converter -import com.tangem.utils.extensions.isZero -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList - -/** - * Converter from [Map] of [UserWallet] and [CryptoCurrencyStatus] to [MyPortfolioUM.Tokens] - * -[REDACTED_AUTHOR] - */ -@Suppress("LongParameterList") -internal class TokensPortfolioUMConverter( - private val appCurrency: AppCurrency, - private val isBalanceHidden: Boolean, - private val addButtonState: MyPortfolioUM.Tokens.AddButtonState, - private val bsConfig: TangemBottomSheetConfig, - private val onAddClick: () -> Unit, - private val quickActionsIntents: TokenActionsHandler, - private val currentState: Provider, - private val updateTokens: ((ImmutableList) -> ImmutableList) -> Unit, -) : Converter>, MyPortfolioUM.Tokens> { - - override fun convert(value: Map>): MyPortfolioUM.Tokens { - val currentTokensState = currentState() as? MyPortfolioUM.Tokens - - return MyPortfolioUM.Tokens( - tokens = value - .flatMap { entry -> entry.value } - .map { cryptoData -> - PortfolioTokenUMConverter( - appCurrency = appCurrency, - isBalanceHidden = isBalanceHidden, - onTokenItemClick = { toggleQuickActions(cryptoData) }, - tokenActionsHandler = quickActionsIntents, - ).convert(value = cryptoData) to cryptoData - } - .setQuickActionsVisibility(currentState = currentTokensState) - .toImmutableList(), - buttonState = addButtonState, - addToPortfolioBSConfig = bsConfig, - onAddClick = onAddClick, - ) - } - - private fun List>.setQuickActionsVisibility( - currentState: MyPortfolioUM.Tokens?, - ): List { - return when { - // if there is only one token and it has empty balance, show quick actions for it - currentState == null && this.size == 1 && isEmptyBalance(this.first().second) -> { - this.map { (token, _) -> - token.copy(isQuickActionsShown = true) - } - } - // if there is no previous state, hide quick actions for all tokens - currentState == null -> { - this.map { (token, _) -> - token.copy(isQuickActionsShown = false) - } - } - else -> { - val previousList = currentState.tokens - - // otherwise, keep previous state - this.map { (token, _) -> - token.copy( - isQuickActionsShown = previousList - .firstOrNull { it.matchWith(token) } - ?.isQuickActionsShown == true, - ) - } - } - } - } - - private fun isEmptyBalance(cryptoData: PortfolioData.CryptoCurrencyData): Boolean { - return cryptoData.status.value.amount?.isZero() == true - } - - private fun toggleQuickActions(cryptoData: PortfolioData.CryptoCurrencyData) { - updateTokens { tokenList -> - tokenList.map { portfolioTokenUM -> - portfolioTokenUM.copy( - isQuickActionsShown = if (portfolioTokenUM.matchWith(cryptoData)) { - !portfolioTokenUM.isQuickActionsShown - } else { - false - }, - ) - }.toImmutableList() - } - } - - private fun PortfolioTokenUM.matchWith(token: PortfolioTokenUM): Boolean { - return this.walletId == token.walletId && this.tokenItemState.id == token.tokenItemState.id - } - - private fun PortfolioTokenUM.matchWith(cryptoData: PortfolioData.CryptoCurrencyData): Boolean { - return this.walletId == cryptoData.userWallet.walletId && - this.tokenItemState.id == cryptoData.status.currency.id.value - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/AddToPortfolioBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/AddToPortfolioBottomSheet.kt deleted file mode 100644 index 16993ea52d..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/AddToPortfolioBottomSheet.kt +++ /dev/null @@ -1,383 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui - -import android.content.res.Configuration -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.Button -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewParameter -import androidx.compose.ui.util.fastForEachIndexed -import com.tangem.common.ui.userwallet.UserWalletItem -import com.tangem.core.ui.components.* -import com.tangem.core.ui.components.block.TangemBlockCardColors -import com.tangem.core.ui.components.block.information.InformationBlock -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.components.buttons.common.TangemButton -import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition -import com.tangem.core.ui.components.buttons.common.TangemButtonSize -import com.tangem.core.ui.components.buttons.common.TangemButtonsDefaults -import com.tangem.core.ui.components.currency.icon.CoinIcon -import com.tangem.core.ui.components.rows.ArrowRow -import com.tangem.core.ui.components.rows.BlockchainRow -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.core.ui.haptic.TangemHapticEffect -import com.tangem.core.ui.res.LocalHapticManager -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewAddToPortfolioBSContentProvider -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.AddToPortfolioBSContentUM -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.SelectNetworkUM -import com.tangem.features.feed.impl.R -import kotlinx.coroutines.delay - -@Composable -internal fun AddToPortfolioBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet( - config = config, - containerColor = TangemTheme.colors.background.tertiary, - addBottomInsets = false, - titleText = resourceReference(R.string.common_add_to_portfolio), - ) { contentUM -> - Content( - modifier = Modifier.fillMaxWidth(), - state = contentUM, - ) - - WalletSelectorBottomSheet(contentUM.walletSelectorConfig) - } -} - -@Composable -private fun Content(state: AddToPortfolioBSContentUM, modifier: Modifier = Modifier) { - var continueButtonAreaHeight by remember { mutableIntStateOf(0) } - val density = LocalDensity.current - val scrollState = rememberScrollState() - - Box(modifier = modifier) { - Column( - modifier = Modifier - .verticalScroll(state = scrollState) - .padding(horizontal = TangemTheme.dimens.spacing16), - ) { - if (state.isWalletBlockVisible) { - UserWalletItem( - state = state.selectedWallet, - blockColors = TangemBlockCardColors.copy( - containerColor = TangemTheme.colors.background.action, - disabledContainerColor = TangemTheme.colors.background.action, - ), - ) - SpacerH12() - } - - NetworkSelection( - modifier = Modifier.fillMaxWidth(), - state = state.selectNetworkUM, - ) - - SpacerH12() - - AnimatedVisibility( - visible = state.isScanCardNotificationVisible, - modifier = Modifier.fillMaxWidth(), - ) { - Column { - ScanWalletWarning(modifier = Modifier.fillMaxWidth()) - SpacerH12() - } - - // Scroll to the bottom when the notification appears and the scroll is at the bottom - LaunchedEffect(Unit) { - if (scrollState.canScrollForward.not()) { - delay(timeMillis = 500) - scrollState.animateScrollTo(scrollState.maxValue) - } - } - } - - SpacerH(with(density) { continueButtonAreaHeight.toDp() }) - } - - AnimatedVisibility( - visible = scrollState.canScrollForward, - enter = fadeIn(), - exit = fadeOut(), - modifier = Modifier.align(Alignment.BottomCenter), - ) { - BottomFade(Modifier.align(Alignment.BottomCenter)) - } - - ContinueButton( - modifier = Modifier - .align(Alignment.BottomCenter) - .onGloballyPositioned { - continueButtonAreaHeight = it.size.height - }, - enabled = state.isContinueButtonEnabled, - isTangemIconVisible = state.isScanCardNotificationVisible, - onClick = state.onContinueButtonClick, - ) - } -} - -@Composable -private fun ContinueButton( - enabled: Boolean, - isTangemIconVisible: Boolean, - onClick: () -> Unit, - modifier: Modifier = Modifier, -) { - TangemButton( - enabled = enabled, - modifier = modifier - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ) - .navigationBarsPadding() - .fillMaxWidth(), - text = stringResourceSafe(R.string.common_continue), - icon = if (enabled && isTangemIconVisible) { - TangemButtonIconPosition.End(R.drawable.ic_tangem_24) - } else { - TangemButtonIconPosition.None - }, - showProgress = false, - size = TangemButtonSize.Default, - colors = TangemButtonsDefaults.primaryButtonColors, - textStyle = TangemTheme.typography.subtitle1, - onClick = onClick, - animateContentChange = true, - ) -} - -@Suppress("LongMethod") -@Composable -private fun NetworkSelection(state: SelectNetworkUM, modifier: Modifier = Modifier) { - val hapticManager = LocalHapticManager.current - - InformationBlock( - modifier = modifier, - title = { - Text( - text = stringResourceSafe(R.string.markets_select_network), - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) - }, - ) { - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = TangemTheme.dimens.spacing14), - verticalAlignment = Alignment.CenterVertically, - ) { - CoinIcon( - modifier = Modifier.size(TangemTheme.dimens.size36), - url = state.iconUrl, - alpha = 1f, - colorFilter = null, - fallbackResId = R.drawable.ic_custom_token_44, - ) - SpacerW12() - Text( - modifier = Modifier - .align(Alignment.CenterVertically) - .weight(1f, fill = false) - .alignByBaseline(), - text = state.tokenName, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.primary1, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) - SpacerW6() - Text( - modifier = Modifier - .align(Alignment.CenterVertically) - .alignByBaseline(), - text = state.tokenCurrencySymbol, - style = TangemTheme.typography.body1, - color = TangemTheme.colors.text.tertiary, - overflow = TextOverflow.Visible, - maxLines = 1, - ) - } - - state.networks.fastForEachIndexed { index, network -> - ArrowRow( - isLastItem = index == state.networks.lastIndex, - content = { - BlockchainRow( - modifier = Modifier.padding(end = TangemTheme.dimens.spacing4), - model = network, - action = { - TangemSwitch( - checked = network.isSelected, - checkedColor = if (network.isEnabled) { - TangemTheme.colors.control.checked - } else { - TangemTheme.colors.icon.inactive - }, - onCheckedChange = { checked -> - if (checked) { - hapticManager.perform(TangemHapticEffect.View.ToggleOn) - } else { - hapticManager.perform(TangemHapticEffect.View.ToggleOff) - } - - state.onNetworkSwitchClick(network, checked) - }, - enabled = network.isEnabled, - ) - }, - ) - }, - ) - } - } - } -} - -@Composable -private fun ScanWalletWarning(modifier: Modifier = Modifier) { - Row( - modifier = modifier - .background( - color = TangemTheme.colors.button.disabled, - shape = TangemTheme.shapes.roundedCornersXMedium, - ) - .padding(TangemTheme.dimens.spacing12), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing10), - ) { - Icon( - modifier = Modifier.requiredSize(TangemTheme.dimens.size20), - imageVector = ImageVector.vectorResource(R.drawable.ic_tangem_24), - tint = TangemTheme.colors.icon.primary1, - contentDescription = null, - ) - Text( - text = stringResourceSafe(R.string.markets_generate_addresses_notification), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.tertiary, - ) - } -} - -@Composable -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun Preview( - @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM, -) { - TangemThemePreview { - AddToPortfolioBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - content = content, - onDismissRequest = {}, - ), - ) - } -} - -@Composable -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun PreviewContent( - @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM, -) { - TangemThemePreview { - Content( - modifier = Modifier - .background(TangemTheme.colors.background.tertiary) - .fillMaxWidth(), - state = content, - ) - } -} - -@Composable -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun PreviewContentRtl( - @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM, -) { - TangemThemePreview(rtl = true) { - Content( - modifier = Modifier - .background(TangemTheme.colors.background.tertiary) - .fillMaxWidth(), - state = content, - ) - } -} - -// For on device testing -@Composable -@Preview -private fun PreviewContentTestOnDevice( - @PreviewParameter(PreviewAddToPortfolioBSContentProvider::class) content: AddToPortfolioBSContentUM, -) { - TangemThemePreview( - alwaysShowBottomSheets = false, - ) { - var isShow by remember { mutableStateOf(false) } - - var contentState by remember { - mutableStateOf(content) - } - - LaunchedEffect(Unit) { - contentState = content.copy( - onContinueButtonClick = { - contentState = contentState.copy( - isScanCardNotificationVisible = !contentState.isScanCardNotificationVisible, - ) - }, - isContinueButtonEnabled = true, - selectedWallet = content.selectedWallet.copy( - onClick = { - contentState = contentState.copy( - isContinueButtonEnabled = !contentState.isContinueButtonEnabled, - ) - }, - ), - ) - } - - AddToPortfolioBottomSheet( - config = TangemBottomSheetConfig( - isShown = isShow, - content = contentState, - onDismissRequest = { isShow = false }, - ), - ) - - Button( - onClick = { isShow = !isShow }, - ) { - Text(text = "Toggle") - } - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt index 8d6911c448..c7c885eca6 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/MyPortfolio.kt @@ -70,11 +70,6 @@ internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) { is MyPortfolioUM.Content -> PortfolioList(state = state) } } - - val bsConfig = state.addToPortfolioBSConfig - if (bsConfig != null) { - AddToPortfolioBottomSheet(config = bsConfig) - } } @Composable diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/TokenActionsBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/TokenActionsBottomSheet.kt deleted file mode 100644 index 7b4a266b9e..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/TokenActionsBottomSheet.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui - -import android.content.res.Configuration -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.core.ui.components.SimpleSettingsRow -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetTitle -import com.tangem.core.ui.decorations.roundedShapeItemDecoration -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.TokenActionsBSContentUM -import kotlinx.collections.immutable.toImmutableList - -@Composable -fun TokenActionsBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet( - config = config, - title = { content -> - TangemBottomSheetTitle(content.title) - }, - containerColor = TangemTheme.colors.background.tertiary, - content = { Content(it) }, - ) -} - -@Composable -private fun Content(content: TokenActionsBSContentUM) { - Column( - modifier = Modifier - .padding( - start = TangemTheme.dimens.spacing16, - end = TangemTheme.dimens.spacing16, - bottom = TangemTheme.dimens.spacing16, - ), - ) { - content.actions.forEachIndexed { index, action -> - Box( - modifier = Modifier - .roundedShapeItemDecoration( - currentIndex = index, - lastIndex = content.actions.lastIndex, - addDefaultPadding = false, - ) - .background(TangemTheme.colors.background.action), - ) { - SimpleSettingsRow( - title = action.text.resolveReference(), - icon = action.iconRes, - redesign = true, - onItemsClick = { content.onActionClick(action) }, - ) - } - } - } -} - -@Preview(widthDp = 360, heightDp = 640) -@Preview(widthDp = 360, heightDp = 640, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview() { - TangemThemePreview( - alwaysShowBottomSheets = true, - ) { - Box(Modifier.background(TangemTheme.colors.background.secondary)) { - TokenActionsBottomSheet( - TangemBottomSheetConfig( - isShown = true, - onDismissRequest = {}, - content = TokenActionsBSContentUM( - title = "Wallet 1", - actions = TokenActionsBSContentUM.Action.entries.toImmutableList(), - onActionClick = {}, - ), - ), - ) - } - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/WalletSelectorBottomSheet.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/WalletSelectorBottomSheet.kt deleted file mode 100644 index 530786cf30..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/WalletSelectorBottomSheet.kt +++ /dev/null @@ -1,145 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui - -import android.content.res.Configuration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.runtime.Composable -import androidx.compose.runtime.key -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.tooling.preview.Preview -import com.tangem.common.ui.userwallet.UserWalletItem -import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.SpacerH -import com.tangem.core.ui.components.appbar.TangemTopAppBar -import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight -import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM -import com.tangem.core.ui.components.block.BlockCard -import com.tangem.core.ui.components.block.TangemBlockCardColors -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview.PreviewAddToPortfolioBSContentProvider -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.WalletSelectorBSContentUM -import com.tangem.features.feed.impl.R -import kotlinx.collections.immutable.persistentListOf - -@Composable -internal fun WalletSelectorBottomSheet(config: TangemBottomSheetConfig) { - TangemBottomSheet( - config = config, - containerColor = TangemTheme.colors.background.tertiary, - addBottomInsets = false, - title = { content -> - TangemTopAppBar( - title = resourceReference(R.string.common_choose_wallet), - titleAlignment = Alignment.CenterHorizontally, - startButton = TopAppBarButtonUM.Back(content.onBack), - height = TangemTopAppBarHeight.BOTTOM_SHEET, - ) - }, - ) { content -> - Content( - modifier = Modifier - .fillMaxSize() - .padding( - horizontal = TangemTheme.dimens.spacing16, - vertical = TangemTheme.dimens.spacing8, - ), - state = content, - ) - } -} - -@Composable -private fun Content(state: WalletSelectorBSContentUM, modifier: Modifier = Modifier) { - val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() } - - Column( - modifier = modifier - .verticalScroll(rememberScrollState()), - ) { - BlockCard( - modifier = Modifier.fillMaxSize(), - colors = TangemBlockCardColors.copy( - containerColor = TangemTheme.colors.background.action, - disabledContainerColor = TangemTheme.colors.background.action, - ), - ) { - state.userWallets.forEach { state -> - key(state.id) { - UserWalletItem( - modifier = Modifier.fillMaxWidth(), - blockColors = TangemBlockCardColors.copy( - containerColor = TangemTheme.colors.background.action, - disabledContainerColor = TangemTheme.colors.background.action, - ), - state = state, - ) - } - } - } - SpacerH(bottomBarHeight) - } -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun Preview() { - TangemThemePreview { - WalletSelectorBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = {}, - content = WalletSelectorBSContentUM( - userWallets = persistentListOf( - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.None, - ), - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.Checkmark, - ), - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.None, - ), - ), - onBack = {}, - ), - ), - ) - } -} - -@Preview -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -private fun PreviewContent() { - TangemThemePreview { - Content( - state = WalletSelectorBSContentUM( - userWallets = persistentListOf( - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.None, - ), - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.Checkmark, - ), - PreviewAddToPortfolioBSContentProvider().userWallet.copy( - endIcon = UserWalletItemUM.EndIcon.None, - ), - ), - onBack = {}, - ), - ) - } -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt deleted file mode 100644 index 4de442ca27..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewAddToPortfolioBSContentProvider.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui.preview - -import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.rows.model.BlockchainRowUM -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.stringReference -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.AddToPortfolioBSContentUM -import com.tangem.features.feed.components.market.details.portfolio.impl.ui.state.SelectNetworkUM -import com.tangem.features.feed.impl.R -import kotlinx.collections.immutable.persistentListOf - -internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider { - - private val blockchainRow = BlockchainRowUM( - id = "1", - name = "Etherium 3", - type = "TEST", - iconResId = R.drawable.ic_eth_16, - isMainNetwork = false, - isSelected = false, - ) - - val userWallet = UserWalletItemUM( - id = "1", - name = stringReference("Wallet 1"), - information = UserWalletItemUM.Information.Loaded(TextReference.Str("3 cards")), - balance = UserWalletItemUM.Balance.Loading, - isEnabled = true, - endIcon = UserWalletItemUM.EndIcon.Arrow, - onClick = {}, - ) - - override val values: Sequence - get() = sequenceOf( - AddToPortfolioBSContentUM( - selectedWallet = userWallet, - selectNetworkUM = SelectNetworkUM( - tokenId = "etherium", - tokenName = "Etherium", - tokenCurrencySymbol = "ETH", - networks = persistentListOf( - blockchainRow.copy( - type = "MAIN", - isMainNetwork = true, - isSelected = true, - ), - blockchainRow, - blockchainRow, - ), - onNetworkSwitchClick = { _, _ -> }, - iconUrl = null, - ), - isScanCardNotificationVisible = true, - isWalletBlockVisible = true, - isContinueButtonEnabled = true, - onContinueButtonClick = {}, - walletSelectorConfig = TangemBottomSheetConfig.Empty, - ), - AddToPortfolioBSContentUM( - selectedWallet = userWallet, - selectNetworkUM = SelectNetworkUM( - tokenId = "etherium", - tokenName = "Etherium Etherium Etherium Etherium", - tokenCurrencySymbol = "ETH", - networks = persistentListOf( - blockchainRow.copy( - type = "MAIN", - isMainNetwork = true, - isSelected = true, - ).copy(name = "Etherium Etherium Etherium Etherium"), - *Array(25) { blockchainRow }, - ), - - onNetworkSwitchClick = { _, _ -> }, - iconUrl = null, - ), - isScanCardNotificationVisible = true, - isWalletBlockVisible = false, - isContinueButtonEnabled = false, - onContinueButtonClick = {}, - walletSelectorConfig = TangemBottomSheetConfig.Empty, - ), - ) -} \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt index 8c6bf7a7a1..f84d983885 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt @@ -3,7 +3,6 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.ui.pre import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.common.ui.account.AccountIconPreviewData import com.tangem.common.ui.account.AccountTitleUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.components.token.state.TokenItemState.FiatAmountState @@ -22,25 +21,19 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider Unit, - val walletSelectorConfig: TangemBottomSheetConfig, -) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/MyPortfolioUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/MyPortfolioUM.kt index d567f3095f..b9d4593c30 100644 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/MyPortfolioUM.kt +++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/MyPortfolioUM.kt @@ -1,16 +1,12 @@ package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import kotlinx.collections.immutable.ImmutableList @Immutable internal sealed class MyPortfolioUM { - abstract val addToPortfolioBSConfig: TangemBottomSheetConfig? - data class Tokens( - override val addToPortfolioBSConfig: TangemBottomSheetConfig, val tokens: ImmutableList, val buttonState: AddButtonState, val onAddClick: () -> Unit, @@ -27,25 +23,15 @@ internal sealed class MyPortfolioUM { val items: ImmutableList, val buttonState: Tokens.AddButtonState, val onAddClick: () -> Unit, - ) : MyPortfolioUM() { - - override val addToPortfolioBSConfig: TangemBottomSheetConfig = TangemBottomSheetConfig.Empty - } + ) : MyPortfolioUM() data class AddFirstToken( - override val addToPortfolioBSConfig: TangemBottomSheetConfig, val onAddClick: () -> Unit, ) : MyPortfolioUM() - data object Loading : MyPortfolioUM() { - override val addToPortfolioBSConfig: TangemBottomSheetConfig? = null - } + data object Loading : MyPortfolioUM() - data object Unavailable : MyPortfolioUM() { - override val addToPortfolioBSConfig: TangemBottomSheetConfig? = null - } + data object Unavailable : MyPortfolioUM() - data object UnavailableForWallet : MyPortfolioUM() { - override val addToPortfolioBSConfig: TangemBottomSheetConfig? = null - } + data object UnavailableForWallet : MyPortfolioUM() } \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/SelectNetworkUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/SelectNetworkUM.kt deleted file mode 100644 index 4ee09913a4..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/SelectNetworkUM.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state - -import com.tangem.core.ui.components.rows.model.BlockchainRowUM -import kotlinx.collections.immutable.ImmutableList - -internal data class SelectNetworkUM( - val tokenId: String, - val iconUrl: String?, - val tokenName: String, - val tokenCurrencySymbol: String, - val networks: ImmutableList, - val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit, -) \ No newline at end of file diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt deleted file mode 100644 index e93a65ddcd..0000000000 --- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/market/details/portfolio/impl/ui/state/WalletSelectorBSContentUM.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.feed.components.market.details.portfolio.impl.ui.state - -import com.tangem.common.ui.userwallet.state.UserWalletItemUM -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import kotlinx.collections.immutable.ImmutableList - -internal data class WalletSelectorBSContentUM( - val userWallets: ImmutableList, - val onBack: () -> Unit, -) : TangemBottomSheetConfigContent \ No newline at end of file