Updated on 2026-08-14
This commit is contained in:
parent
7fe610ec37
commit
435e579a61
21 changed files with 78 additions and 1949 deletions
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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<PortfolioData> {
|
||||
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<Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>> {
|
||||
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<UserWalletId>,
|
||||
): Flow<Map<UserWalletId, Lce<TokenListError, TotalFiatBalance>>> {
|
||||
return combine(
|
||||
flows = ids
|
||||
.map { userWalletId ->
|
||||
getWalletTotalBalanceUseCase(userWalletId)
|
||||
.map { userWalletId to it }
|
||||
.distinctUntilChanged()
|
||||
},
|
||||
transform = { it.toMap() },
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.onEmpty { ids.associateWith { Lce.Loading<TotalFiatBalance>(partialContent = 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<TokenMarketInfo.Network>) -> 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<String>?,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): 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<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): 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) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UserWalletId, Set<TokenMarketInfo.Network>>
|
||||
|
||||
/**
|
||||
* Manager for tracking changing networks in AddToPortfolio
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class AddToPortfolioManager @Inject constructor(
|
||||
private val filterAvailableNetworksForWalletUseCase: FilterAvailableNetworksForWalletUseCase,
|
||||
) {
|
||||
|
||||
val availableNetworks = MutableStateFlow<Set<TokenMarketInfo.Network>?>(value = null)
|
||||
private val addedNetworks = MutableStateFlow<WalletsWithNetworks>(value = emptyMap())
|
||||
private val removedNetworks = MutableStateFlow<WalletsWithNetworks>(value = emptyMap())
|
||||
|
||||
/** Get [AddToPortfolioData] as flow */
|
||||
fun getAddToPortfolioData(): Flow<AddToPortfolioData> {
|
||||
return combine(
|
||||
flow = availableNetworks,
|
||||
flow2 = addedNetworks,
|
||||
flow3 = removedNetworks,
|
||||
transform = ::AddToPortfolioData,
|
||||
)
|
||||
}
|
||||
|
||||
/** Set available networks [networks] */
|
||||
fun setAvailableNetworks(networks: List<TokenMarketInfo.Network>) {
|
||||
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<String>,
|
||||
addToPortfolioData: AddToPortfolioData,
|
||||
): Map<TokenMarketInfo.Network, Boolean> {
|
||||
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<WalletsWithNetworks>.cancelPrevChangeIfExist(
|
||||
userWalletId: UserWalletId,
|
||||
networkId: String,
|
||||
) {
|
||||
if (value[userWalletId].orEmpty().any { it.networkId == networkId }) remove(userWalletId, networkId)
|
||||
}
|
||||
|
||||
private fun MutableStateFlow<WalletsWithNetworks>.add(userWalletId: UserWalletId, networkId: String) {
|
||||
change(userWalletId = userWalletId, networkId = networkId, isAddAction = true)
|
||||
}
|
||||
|
||||
private fun MutableStateFlow<WalletsWithNetworks>.remove(userWalletId: UserWalletId, networkId: String) {
|
||||
change(userWalletId = userWalletId, networkId = networkId, isAddAction = false)
|
||||
}
|
||||
|
||||
private fun MutableStateFlow<WalletsWithNetworks>.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<TokenMarketInfo.Network>?,
|
||||
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<String>,
|
||||
): Set<TokenMarketInfo.Network> {
|
||||
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<String>,
|
||||
): Set<TokenMarketInfo.Network> {
|
||||
val removedNetworksByUser = removedNetworks[userWalletId].orEmpty()
|
||||
|
||||
return alreadyAddedNetworkIds
|
||||
.minus(removedNetworksByUser.map { it.networkId }.toSet())
|
||||
.mapNotNull { networkId -> removedNetworksByUser.firstOrNull { it.networkId == networkId } }
|
||||
.toSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MyPortfolioUM> = 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<MyPortfolioUM.Tokens.AddButtonState>,
|
||||
onAddClick: () -> Unit,
|
||||
): NewMarketsPortfolioDelegate
|
||||
): MarketsPortfolioDelegate
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<MyPortfolioUM> = MutableStateFlow(value = MyPortfolioUM.Loading)
|
||||
val state: StateFlow<MyPortfolioUM> get() = _state
|
||||
val state: StateFlow<MyPortfolioUM>
|
||||
field = MutableStateFlow<MyPortfolioUM>(value = MyPortfolioUM.Loading)
|
||||
|
||||
private val params = paramsContainer.require<MarketsPortfolioComponent.Params>()
|
||||
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<UserWalletId?>(value = null)
|
||||
|
||||
private val portfolioBSVisibilityModelFlow = MutableStateFlow(value = PortfolioBSVisibilityModel())
|
||||
val addToPortfolioManager: AddToPortfolioManager = createAddToPortfolioManager()
|
||||
private val marketsPortfolioDelegate: MarketsPortfolioDelegate = createMarketsPortfolioDelegate()
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<MarketsPortfolioRoute> = 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<TokenMarketInfo.Network>) {
|
||||
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<Pair<PortfolioData, Map<UserWalletId, UserWalletItemUM.ImageState>>> {
|
||||
val wallets = Channel<Set<UserWallet>>()
|
||||
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<PortfolioUIData> {
|
||||
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<TokenMarketInfo.Network>) {
|
||||
modelScope.launch {
|
||||
saveMarketTokensUseCase(
|
||||
userWalletId = userWalletId,
|
||||
tokenMarketParams = params.token,
|
||||
addedNetworks = addedNetworks,
|
||||
removedNetworks = emptySet(),
|
||||
private fun createAppCurrencyFlow(): StateFlow<AppCurrency> {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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<MyPortfolioUM>,
|
||||
private val updateTokens: ((ImmutableList<PortfolioTokenUM>) -> ImmutableList<PortfolioTokenUM>) -> Unit,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
portfolioData: PortfolioData,
|
||||
portfolioUIData: PortfolioUIData,
|
||||
artworks: Map<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): 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<UserWalletId, UserWalletItemUM.ImageState>,
|
||||
): 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<UserWallet, List<PortfolioData.CryptoCurrencyData>>.getAddButtonState(
|
||||
availableNetworks: Set<TokenMarketInfo.Network>?,
|
||||
): 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<UserWallet, List<PortfolioData.CryptoCurrencyData>>.filterAvailableNetworks(
|
||||
networks: Set<TokenMarketInfo.Network>,
|
||||
): Map<UserWallet, List<PortfolioData.CryptoCurrencyData>> {
|
||||
return mapValues { entry -> entry.value.filterAvailableNetworks(networks) }
|
||||
}
|
||||
|
||||
/** Filter list of [CryptoCurrencyStatus] by available networks [networks] */
|
||||
private fun List<PortfolioData.CryptoCurrencyData>.filterAvailableNetworks(
|
||||
networks: Set<TokenMarketInfo.Network>,
|
||||
): List<PortfolioData.CryptoCurrencyData> {
|
||||
val networkIds = networks.map(TokenMarketInfo.Network::networkId)
|
||||
|
||||
return mapNotNull { cryptoCurrencyData ->
|
||||
cryptoCurrencyData.takeIf { networkIds.contains(it.status.currency.network.backendId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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<TokenMarketInfo.Network, Boolean>,
|
||||
private val alreadyAddedNetworks: Set<String>,
|
||||
private val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit,
|
||||
) : Converter<TokenMarketParams, SelectNetworkUM> {
|
||||
|
||||
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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MyPortfolioUM>,
|
||||
private val updateTokens: ((ImmutableList<PortfolioTokenUM>) -> ImmutableList<PortfolioTokenUM>) -> Unit,
|
||||
) : Converter<Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>, MyPortfolioUM.Tokens> {
|
||||
|
||||
override fun convert(value: Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>): 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<Pair<PortfolioTokenUM, PortfolioData.CryptoCurrencyData>>.setQuickActionsVisibility(
|
||||
currentState: MyPortfolioUM.Tokens?,
|
||||
): List<PortfolioTokenUM> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AddToPortfolioBSContentUM>(
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<TokenActionsBSContentUM>(
|
||||
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 = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<WalletSelectorBSContentUM>(
|
||||
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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AddToPortfolioBSContentUM> {
|
||||
|
||||
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<AddToPortfolioBSContentUM>
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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<MyPortfol
|
|||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken.copy(isQuickActionsShown = true)),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Unavailable,
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken.copy(isQuickActionsShown = true), sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Loading,
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.AddFirstToken(
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.AddFirstToken(onAddClick = {}),
|
||||
MyPortfolioUM.Content(
|
||||
items = persistentListOf(
|
||||
walletPortfolioHeader,
|
||||
|
|
|
|||
|
|
@ -1,15 +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.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
||||
internal data class AddToPortfolioBSContentUM(
|
||||
val selectedWallet: UserWalletItemUM,
|
||||
val selectNetworkUM: SelectNetworkUM,
|
||||
val isWalletBlockVisible: Boolean,
|
||||
val isScanCardNotificationVisible: Boolean,
|
||||
val isContinueButtonEnabled: Boolean,
|
||||
val onContinueButtonClick: () -> Unit,
|
||||
val walletSelectorConfig: TangemBottomSheetConfig,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
|
@ -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<PortfolioTokenUM>,
|
||||
val buttonState: AddButtonState,
|
||||
val onAddClick: () -> Unit,
|
||||
|
|
@ -27,25 +23,15 @@ internal sealed class MyPortfolioUM {
|
|||
val items: ImmutableList<PortfolioListItem>,
|
||||
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()
|
||||
}
|
||||
|
|
@ -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<BlockchainRowUM>,
|
||||
val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit,
|
||||
)
|
||||
|
|
@ -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<UserWalletItemUM>,
|
||||
val onBack: () -> Unit,
|
||||
) : TangemBottomSheetConfigContent
|
||||
Loading…
Add table
Add a link
Reference in a new issue