Updated on 2026-08-14
This commit is contained in:
parent
d7374fb83f
commit
9de7841edd
38 changed files with 687 additions and 372 deletions
|
|
@ -5,7 +5,6 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
|
|
@ -48,11 +47,6 @@ object TradeCryptoMiddleware {
|
|||
is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId)
|
||||
is TradeCryptoAction.Buy -> proceedBuyAction(state, action)
|
||||
is TradeCryptoAction.Sell -> proceedSellAction(action)
|
||||
is TradeCryptoAction.Stake -> openStaking(
|
||||
userWalletId = action.userWalletId,
|
||||
cryptoCurrencyId = action.cryptoCurrencyId,
|
||||
yield = action.yield,
|
||||
)
|
||||
is TradeCryptoAction.SendToken -> handleNewSendToken(action = action)
|
||||
is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action)
|
||||
}
|
||||
|
|
@ -140,18 +134,6 @@ object TradeCryptoMiddleware {
|
|||
)?.let { store.dispatchOpenUrl(it) }
|
||||
}
|
||||
|
||||
private fun openStaking(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, yield: Yield) {
|
||||
store.dispatchNavigationAction {
|
||||
push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrencyId,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleNewSendToken(action: TradeCryptoAction.SendToken) {
|
||||
handleNewSend(
|
||||
userWalletId = action.userWallet.walletId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.common.ui.tokens
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
|
||||
fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference {
|
||||
return when (val unavailabilityReason = this) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_send,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.EmptyBalance -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_send,
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_sell,
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.BuyUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_buy_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotExchangeable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_not_exchangeable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_sell_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.Unreachable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,4 +11,9 @@ data class TangemBottomSheetConfig(
|
|||
val isShow: Boolean,
|
||||
val onDismissRequest: () -> Unit,
|
||||
val content: TangemBottomSheetConfigContent,
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
val Empty = TangemBottomSheetConfig(false, {}, TangemBottomSheetConfigContent.Empty)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui.components.token
|
|||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -47,12 +48,18 @@ private enum class LayoutId {
|
|||
* >Figma Component</a>
|
||||
*/
|
||||
@Composable
|
||||
fun TokenItem(state: TokenItemState, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
|
||||
fun TokenItem(
|
||||
state: TokenItemState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
itemPaddingValues: PaddingValues = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
TokenItem(
|
||||
state = state,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifier,
|
||||
reorderableTokenListState = null,
|
||||
itemPaddingValues = itemPaddingValues,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +70,7 @@ fun TokenItem(state: TokenItemState, isBalanceHidden: Boolean, modifier: Modifie
|
|||
* @param isBalanceHidden flag that shows/hides balance
|
||||
* @param reorderableTokenListState reorderable token list state
|
||||
* @param modifier modifier
|
||||
* @param itemPaddingValues padding values
|
||||
*
|
||||
* @see <a href = "https://www.figma.com/design/14ISV23YB1yVW1uNVwqrKv/Android?node-id=1051-866&t=ew8mbGp2lacuJfFm-4"
|
||||
* >Figma Component</a>
|
||||
|
|
@ -73,12 +81,15 @@ fun TokenItem(
|
|||
isBalanceHidden: Boolean,
|
||||
reorderableTokenListState: ReorderableLazyListState?,
|
||||
modifier: Modifier = Modifier,
|
||||
itemPaddingValues: PaddingValues = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
val betweenRowsMargin = TangemTheme.dimens.spacing2
|
||||
|
||||
CustomContainer(
|
||||
state = state,
|
||||
modifier = modifier.tokenClickable(state = state),
|
||||
modifier = modifier
|
||||
.tokenClickable(state = state)
|
||||
.padding(itemPaddingValues),
|
||||
) {
|
||||
CurrencyIcon(
|
||||
state = state.iconState,
|
||||
|
|
@ -163,10 +174,7 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
Layout(content = content, modifier = modifier) { measurables, constraints ->
|
||||
|
||||
val layoutWidth = constraints.maxWidth
|
||||
val horizontalPadding = with(density) { dimens.size12.roundToPx() }
|
||||
val verticalPadding = with(density) { dimens.size15.roundToPx() }
|
||||
val layoutWidthWithoutPaddings = layoutWidth - 2 * horizontalPadding
|
||||
|
||||
val titleMinWidth = (layoutWidth * TITLE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
val priceChangeMinWidth = (layoutWidth * PRICE_MIN_WIDTH_COEFFICIENT).toInt()
|
||||
|
||||
|
|
@ -202,35 +210,35 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
-> {
|
||||
fiatAmount = measurables.measureFiatAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - titleMinWidth,
|
||||
maxWidth = layoutWidth - icon.width - titleMinWidth,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
cryptoAmount = measurables.measureCryptoAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - priceChangeMinWidth,
|
||||
maxWidth = layoutWidth - icon.width - priceChangeMinWidth,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - fiatAmount.width
|
||||
secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - cryptoAmount.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - fiatAmount.width
|
||||
secondRowRemainingFreeSpace = layoutWidth - icon.width - cryptoAmount.width
|
||||
}
|
||||
is TokenItemState.Draggable -> {
|
||||
cryptoAmount = measurables.measureCryptoAmount(
|
||||
state = state,
|
||||
maxWidth = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width,
|
||||
maxWidth = layoutWidth - icon.width - nonFiatContent.width,
|
||||
defaultConstraints = constraints,
|
||||
)
|
||||
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
}
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
-> {
|
||||
firstRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
|
||||
firstRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
|
||||
if (state.subtitleState != null) {
|
||||
secondRowRemainingFreeSpace = layoutWidthWithoutPaddings - icon.width - nonFiatContent.width
|
||||
secondRowRemainingFreeSpace = layoutWidth - icon.width - nonFiatContent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -262,10 +270,10 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
)
|
||||
|
||||
layout(width = constraints.maxWidth, height = layoutHeight) {
|
||||
icon.placeRelative(x = horizontalPadding, y = (layoutHeight - icon.height).div(other = 2))
|
||||
icon.placeRelative(x = 0, y = (layoutHeight - icon.height).div(other = 2))
|
||||
|
||||
title.placeRelative(
|
||||
x = horizontalPadding + icon.width,
|
||||
x = icon.width,
|
||||
y = when (state) {
|
||||
is TokenItemState.NoAddress,
|
||||
is TokenItemState.Unreachable,
|
||||
|
|
@ -280,23 +288,23 @@ private fun CustomContainer(state: TokenItemState, modifier: Modifier = Modifier
|
|||
},
|
||||
)
|
||||
|
||||
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width - horizontalPadding, y = verticalPadding)
|
||||
fiatAmount?.placeRelative(x = layoutWidth - fiatAmount.width, y = verticalPadding)
|
||||
|
||||
priceChange?.placeRelative(
|
||||
x = horizontalPadding + icon.width,
|
||||
x = icon.width,
|
||||
y = layoutHeight - priceChange.height - verticalPadding,
|
||||
)
|
||||
|
||||
cryptoAmount?.placeRelative(
|
||||
x = when (state) {
|
||||
is TokenItemState.Draggable -> horizontalPadding + icon.width
|
||||
else -> layoutWidth - cryptoAmount.width - horizontalPadding
|
||||
is TokenItemState.Draggable -> icon.width
|
||||
else -> layoutWidth - cryptoAmount.width
|
||||
},
|
||||
y = layoutHeight - cryptoAmount.height - verticalPadding,
|
||||
)
|
||||
|
||||
nonFiatContent.placeRelative(
|
||||
x = layoutWidth - nonFiatContent.width - horizontalPadding,
|
||||
x = layoutWidth - nonFiatContent.width,
|
||||
y = (layoutHeight - nonFiatContent.height).div(other = 2),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,11 +130,24 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
// staking
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
if (isStakingAvailable(cryptoCurrency)) {
|
||||
activeList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.None))
|
||||
val yield = kotlin.runCatching {
|
||||
stakingRepository.getYield(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
)
|
||||
}.getOrNull()
|
||||
|
||||
activeList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Stake(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.StakingUnavailable(cryptoCurrency.name),
|
||||
yield = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -254,7 +267,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
actionsList.add(TokenActionsState.ActionState.Receive(scenario))
|
||||
}
|
||||
if (stakingFeatureToggles.isStakingEnabled) {
|
||||
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(TokenActionsState.ActionState.Stake(ScenarioUnavailabilityReason.Unreachable, null))
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.HideToken(ScenarioUnavailabilityReason.None))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.domain.tokens.legacy
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import org.rekotlin.Action
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
|
@ -24,6 +22,7 @@ sealed class TradeCryptoAction : Action {
|
|||
val appCurrencyCode: String,
|
||||
) : TradeCryptoAction()
|
||||
|
||||
@Deprecated("Use AppRoute instead")
|
||||
data class SendToken(
|
||||
val userWallet: UserWallet,
|
||||
val tokenCurrency: CryptoCurrency.Token,
|
||||
|
|
@ -33,6 +32,7 @@ sealed class TradeCryptoAction : Action {
|
|||
val transactionInfo: TransactionInfo? = null,
|
||||
) : TradeCryptoAction()
|
||||
|
||||
@Deprecated("Use AppRoute instead")
|
||||
data class SendCoin(
|
||||
val userWallet: UserWallet,
|
||||
val coinStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -40,12 +40,6 @@ sealed class TradeCryptoAction : Action {
|
|||
val transactionInfo: TransactionInfo? = null,
|
||||
) : TradeCryptoAction()
|
||||
|
||||
data class Stake(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrencyId: CryptoCurrency.ID,
|
||||
val yield: Yield,
|
||||
) : TradeCryptoAction()
|
||||
|
||||
data class TransactionInfo(
|
||||
val transactionId: String,
|
||||
val destinationAddress: String,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.tokens.model
|
||||
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
data class TokenActionsState(
|
||||
|
|
@ -20,7 +21,10 @@ data class TokenActionsState(
|
|||
|
||||
data class Receive(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
data class Stake(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
data class Stake(
|
||||
override val unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
val yield: Yield?,
|
||||
) : ActionState()
|
||||
|
||||
data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
|
||||
// FIXME [REDACTED_TASK_KEY]
|
||||
// Remove the "Buy" and "Sell" actions from the redux middleware.
|
||||
// Instead, create some kind of interface for such cases.
|
||||
/* Redux -_- */
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(deps.reKotlin)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.foundation)
|
||||
|
|
|
|||
|
|
@ -346,9 +346,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
|
||||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = getChartTypeByPercent(percent),
|
||||
)
|
||||
it.copy(type = percent.percentChangeType().toChartType())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -356,13 +354,12 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
private suspend fun updateQuotes(newQuotes: TokenQuotes) {
|
||||
quotesStateUpdater.updateQuotes(newQuotes)
|
||||
|
||||
val percent = newQuotes.getPercentByInterval(interval = state.value.selectedInterval)
|
||||
val percent = newQuotes
|
||||
.getPercentByInterval(interval = state.value.selectedInterval)
|
||||
|
||||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = getChartTypeByPercent(percent),
|
||||
)
|
||||
it.copy(type = percent.percentChangeType().toChartType())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -430,7 +427,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
chartDataProducer.runTransaction {
|
||||
updateLook {
|
||||
it.copy(
|
||||
type = getChartTypeByPercent(percent),
|
||||
type = percent.percentChangeType().toChartType(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -477,7 +474,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
launch {
|
||||
while (true) {
|
||||
delay(timeMillis)
|
||||
// Update quotes only when content visible on the screen
|
||||
// Update quotes only when content is visible on the screen
|
||||
isVisibleOnScreen.first { it }
|
||||
|
||||
loadQuotes()
|
||||
|
|
|
|||
|
|
@ -87,15 +87,4 @@ internal fun PriceChangeType.toChartType(): MarketChartLook.Type {
|
|||
PriceChangeType.DOWN -> MarketChartLook.Type.Falling
|
||||
PriceChangeType.NEUTRAL -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal fun getChartTypeByPercent(percent: BigDecimal?): MarketChartLook.Type {
|
||||
val scaled = percent?.setScale(4, RoundingMode.HALF_UP)
|
||||
return when {
|
||||
scaled == null -> return MarketChartLook.Type.Neutral
|
||||
scaled > BigDecimal.ZERO -> MarketChartLook.Type.Growing
|
||||
scaled < BigDecimal.ZERO -> MarketChartLook.Type.Falling
|
||||
else -> MarketChartLook.Type.Neutral
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.tokens.error.TokenListError
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -11,7 +12,7 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
/**
|
||||
* Portfolio data. Combined data from all flows that required to setup portfolio
|
||||
*
|
||||
* @property walletsWithCurrencyStatuses wallets with crypto currency statuses
|
||||
* @property walletsWithCurrencies wallets with crypto currency statuses
|
||||
* @property appCurrency app currency
|
||||
* @property isBalanceHidden flag that indicates if balance should be hidden
|
||||
* @property walletsWithBalance wallets with total balance
|
||||
|
|
@ -19,8 +20,14 @@ import com.tangem.domain.wallets.models.UserWalletId
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal data class PortfolioData(
|
||||
val walletsWithCurrencyStatuses: Map<UserWallet, List<CryptoCurrencyStatus>>,
|
||||
val walletsWithCurrencies: Map<UserWallet, List<CryptoCurrencyData>>,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val walletsWithBalance: Map<UserWalletId, Lce<TokenListError, TotalFiatBalance>>,
|
||||
)
|
||||
) {
|
||||
data class CryptoCurrencyData(
|
||||
val userWallet: UserWallet,
|
||||
val status: CryptoCurrencyStatus,
|
||||
val actions: List<TokenActionsState.ActionState>,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
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.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.TotalFiatBalance
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -32,18 +32,19 @@ internal class PortfolioDataLoader @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||
private val getWalletTotalBalanceUseCase: GetWalletTotalBalanceUseCase,
|
||||
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
|
||||
) {
|
||||
|
||||
/** Load data by [currencyRawId] */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun load(currencyRawId: String): Flow<PortfolioData> {
|
||||
return combine(
|
||||
flow = getAllWalletsCryptoCurrencyStatusesFlow(currencyRawId = currencyRawId),
|
||||
flow = getAllWalletsCryptoCurrenciesData(currencyRawId = currencyRawId),
|
||||
flow2 = getSelectedAppCurrencyFlow(),
|
||||
flow3 = getBalanceHidingSettingsFlow(),
|
||||
) { walletsWithCurrencyStatuses, appCurrency, isBalanceHidden ->
|
||||
) { walletsWithCurrencies, appCurrency, isBalanceHidden ->
|
||||
PortfolioData(
|
||||
walletsWithCurrencyStatuses = walletsWithCurrencyStatuses,
|
||||
walletsWithCurrencies = walletsWithCurrencies,
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
walletsWithBalance = emptyMap(),
|
||||
|
|
@ -52,15 +53,16 @@ internal class PortfolioDataLoader @Inject constructor(
|
|||
// setup balances for wallets from walletsWithCurrencyStatuses
|
||||
.flatMapLatest { portfolioData ->
|
||||
getWalletsWithTotalBalanceFlow(
|
||||
ids = portfolioData.walletsWithCurrencyStatuses.keys.map(UserWallet::walletId),
|
||||
ids = portfolioData.walletsWithCurrencies.keys.map(UserWallet::walletId),
|
||||
)
|
||||
.map { portfolioData.copy(walletsWithBalance = it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAllWalletsCryptoCurrencyStatusesFlow(
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private fun getAllWalletsCryptoCurrenciesData(
|
||||
currencyRawId: String,
|
||||
): Flow<Map<UserWallet, List<CryptoCurrencyStatus>>> {
|
||||
): Flow<Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>> {
|
||||
return getAllWalletsCryptoCurrencyStatusesUseCase(currencyRawId)
|
||||
.distinctUntilChanged()
|
||||
.map { walletsWithMaybeStatuses ->
|
||||
|
|
@ -68,6 +70,30 @@ internal class PortfolioDataLoader @Inject constructor(
|
|||
entry.value.mapNotNull { it.getOrNull() }
|
||||
}
|
||||
}
|
||||
.flatMapLatest { walletsWithStatuses ->
|
||||
val actionsFlows = walletsWithStatuses.flatMap { (wallet, statuses) ->
|
||||
statuses.map { status ->
|
||||
getCryptoCurrencyActionsUseCase(wallet, status)
|
||||
.map {
|
||||
PortfolioData.CryptoCurrencyData(
|
||||
userWallet = wallet,
|
||||
status = status,
|
||||
actions = it.states,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combine(actionsFlows) { actions ->
|
||||
walletsWithStatuses.mapValues { entry ->
|
||||
entry.value.mapNotNull { status ->
|
||||
actions.firstOrNull {
|
||||
it.userWallet == entry.key && it.status == status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedAppCurrencyFlow(): Flow<AppCurrency> {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ internal class AddToPortfolioBSContentUMFactory(
|
|||
portfolioData = portfolioData,
|
||||
selectedWalletId = selectedWallet.walletId,
|
||||
),
|
||||
// TODO maybe do not show wallets with already added token
|
||||
isWalletBlockVisible = portfolioData.walletsWithBalance.size > 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -86,7 +88,7 @@ internal class AddToPortfolioBSContentUMFactory(
|
|||
isShow = isShow,
|
||||
onDismissRequest = { onWalletSelectorVisibilityChange(false) },
|
||||
content = WalletSelectorBSContentUM(
|
||||
userWallets = portfolioData.walletsWithCurrencyStatuses
|
||||
userWallets = portfolioData.walletsWithCurrencies
|
||||
.filterKeys(UserWallet::isMultiCurrency)
|
||||
.map { it.key }
|
||||
.map { userWallet ->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import com.tangem.core.decompose.di.ComponentScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.card.HasMissedDerivationsUseCase
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
|
|
@ -14,20 +16,24 @@ import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
|||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioDataLoader
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class MarketsPortfolioModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val portfolioDataLoader: PortfolioDataLoader,
|
||||
private val hasMissedDerivationsUseCase: HasMissedDerivationsUseCase,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tokenActionsIntentsFactory: TokenActionsHandler.Factory,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<MyPortfolioUM> get() = _state
|
||||
|
|
@ -45,7 +51,17 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
|
||||
private val portfolioBSVisibilityModelFlow = MutableStateFlow(value = PortfolioBSVisibilityModel())
|
||||
|
||||
private val factory = MyPortfolioUMMFactory(
|
||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}
|
||||
.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
|
||||
private val factory = MyPortfolioUMFactory(
|
||||
onAddClick = { onAddToPortfolioBSVisibilityChange(isShow = true) },
|
||||
onTokenItemClick = ::onTokenItemClick,
|
||||
addToPortfolioBSContentUMFactory = AddToPortfolioBSContentUMFactory(
|
||||
|
|
@ -56,6 +72,15 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
onWalletSelect = ::onWalletSelect,
|
||||
onContinueClick = ::onContinueClick,
|
||||
),
|
||||
tokenActionsHandler = tokenActionsIntentsFactory.create(
|
||||
currentAppCurrency = Provider { currentAppCurrency.value },
|
||||
updateTokenActionsBSConfig = { updateBlock ->
|
||||
updateTokensState { it.copy(tokenActionsBSConfig = updateBlock(it.tokenActionsBSConfig)) }
|
||||
},
|
||||
updateTokenReceiveBSConfig = { updateBlock ->
|
||||
updateTokensState { it.copy(tokenReceiveBSConfig = updateBlock(it.tokenReceiveBSConfig)) }
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
|
|
@ -128,9 +153,7 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onTokenItemClick(index: Int, id: CryptoCurrency.ID) {
|
||||
_state.update {
|
||||
val state = _state.value as? MyPortfolioUM.Tokens ?: return@update it
|
||||
|
||||
updateTokensState { state ->
|
||||
state.copy(
|
||||
tokens = state.tokens
|
||||
.mapIndexed { i, tokenUM ->
|
||||
|
|
@ -138,7 +161,7 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
if (index == i && id.value == tokenUM.tokenItemState.id) {
|
||||
tokenUM.copy(isQuickActionsShown = !tokenUM.isQuickActionsShown)
|
||||
} else {
|
||||
tokenUM
|
||||
tokenUM.copy(isQuickActionsShown = false)
|
||||
}
|
||||
}
|
||||
.toImmutableList(),
|
||||
|
|
@ -194,4 +217,11 @@ internal class MarketsPortfolioModel @Inject constructor(
|
|||
it.copy(addToPortfolioBSVisibility = true, walletSelectorBSVisibility = isShow)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTokensState(block: (MyPortfolioUM.Tokens) -> MyPortfolioUM) {
|
||||
_state.update { stateToUpdate ->
|
||||
val tokensState = stateToUpdate as? MyPortfolioUM.Tokens ?: return@update stateToUpdate
|
||||
block(tokensState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,11 @@ import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class MyPortfolioUMMFactory(
|
||||
internal class MyPortfolioUMFactory(
|
||||
private val onAddClick: () -> Unit,
|
||||
private val onTokenItemClick: (Int, CryptoCurrency.ID) -> Unit,
|
||||
private val addToPortfolioBSContentUMFactory: AddToPortfolioBSContentUMFactory,
|
||||
private val tokenActionsHandler: TokenActionsHandler,
|
||||
) {
|
||||
|
||||
fun create(
|
||||
|
|
@ -31,16 +32,16 @@ internal class MyPortfolioUMMFactory(
|
|||
|
||||
if (availableNetworks.isEmpty()) return MyPortfolioUM.Unavailable
|
||||
|
||||
val walletsWithStatuses = portfolioData.walletsWithCurrencyStatuses
|
||||
val walletsWithCurrencies = portfolioData.walletsWithCurrencies
|
||||
.filterAvailableNetworks(networks = availableNetworks)
|
||||
|
||||
val isPortfolioEmpty = walletsWithStatuses.flatMap { it.value }.isEmpty()
|
||||
val isPortfolioEmpty = walletsWithCurrencies.flatMap { it.value }.isEmpty()
|
||||
if (isPortfolioEmpty) {
|
||||
val hasMultiWallets = walletsWithStatuses.filterKeys(UserWallet::isMultiCurrency).isNotEmpty()
|
||||
val hasMultiWallets = walletsWithCurrencies.filterKeys(UserWallet::isMultiCurrency).isNotEmpty()
|
||||
|
||||
return if (hasMultiWallets) {
|
||||
MyPortfolioUM.AddFirstToken(
|
||||
bsConfig = createAddToPortfolioBSConfig(
|
||||
addToPortfolioBSConfig = createAddToPortfolioBSConfig(
|
||||
portfolioData = portfolioData,
|
||||
portfolioUIData = portfolioUIData,
|
||||
availableNetworks = availableNetworks,
|
||||
|
|
@ -55,7 +56,7 @@ internal class MyPortfolioUMMFactory(
|
|||
return TokensPortfolioUMConverter(
|
||||
appCurrency = portfolioData.appCurrency,
|
||||
isBalanceHidden = portfolioData.isBalanceHidden,
|
||||
isAllAvailableNetworksAdded = walletsWithStatuses.isAllAvailableNetworksAdded(availableNetworks),
|
||||
isAllAvailableNetworksAdded = walletsWithCurrencies.isAllAvailableNetworksAdded(availableNetworks),
|
||||
bsConfig = createAddToPortfolioBSConfig(
|
||||
portfolioData = portfolioData,
|
||||
portfolioUIData = portfolioUIData,
|
||||
|
|
@ -63,8 +64,9 @@ internal class MyPortfolioUMMFactory(
|
|||
),
|
||||
onAddClick = onAddClick,
|
||||
onTokenItemClick = onTokenItemClick,
|
||||
quickActionsIntents = tokenActionsHandler,
|
||||
)
|
||||
.convert(walletsWithStatuses)
|
||||
.convert(walletsWithCurrencies)
|
||||
}
|
||||
|
||||
private fun createAddToPortfolioBSConfig(
|
||||
|
|
@ -73,21 +75,21 @@ internal class MyPortfolioUMMFactory(
|
|||
availableNetworks: List<TokenMarketInfo.Network>,
|
||||
): TangemBottomSheetConfig {
|
||||
val walletId = portfolioUIData.selectedWalletId
|
||||
?: portfolioData.walletsWithCurrencyStatuses.keys.firstOrNull { it.isMultiCurrency }?.walletId
|
||||
?: portfolioData.walletsWithCurrencies.keys.firstOrNull { it.isMultiCurrency }?.walletId
|
||||
|
||||
val selectedWallet = portfolioData.walletsWithCurrencyStatuses.keys
|
||||
val selectedWallet = portfolioData.walletsWithCurrencies.keys
|
||||
.firstOrNull { it.walletId == walletId }
|
||||
?: error("portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId")
|
||||
|
||||
val changedNetworks = portfolioUIData.walletsWithChangedNetworks[portfolioUIData.selectedWalletId]
|
||||
val alreadyAddedNetworks = requireNotNull(
|
||||
value = portfolioData.walletsWithCurrencyStatuses[selectedWallet],
|
||||
value = portfolioData.walletsWithCurrencies[selectedWallet],
|
||||
lazyMessage = {
|
||||
"portfolioModel.walletsWithCurrencyStatuses doesn't contain selected wallet: $walletId"
|
||||
},
|
||||
)
|
||||
.filterAvailableNetworks(availableNetworks)
|
||||
.map { it.currency.network.backendId }
|
||||
.map { it.status.currency.network.backendId }
|
||||
|
||||
return addToPortfolioBSContentUMFactory.create(
|
||||
portfolioData = portfolioData,
|
||||
|
|
@ -117,7 +119,7 @@ internal class MyPortfolioUMMFactory(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Map<UserWallet, List<CryptoCurrencyStatus>>.isAllAvailableNetworksAdded(
|
||||
private fun Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>.isAllAvailableNetworksAdded(
|
||||
availableNetworks: List<TokenMarketInfo.Network>,
|
||||
): Boolean {
|
||||
val networkIds = availableNetworks.map { it.networkId }
|
||||
|
|
@ -125,26 +127,26 @@ internal class MyPortfolioUMMFactory(
|
|||
return this
|
||||
// User can add currencies only in multi-currency wallets
|
||||
.filterKeys(UserWallet::isMultiCurrency)
|
||||
.mapValues { it.value.map { it.currency.network.backendId } }
|
||||
.mapValues { entry -> entry.value.map { it.status.currency.network.backendId } }
|
||||
// Each wallets contains all available networks?
|
||||
.all { it.value.containsAll(networkIds) }
|
||||
}
|
||||
|
||||
/** Filter map values by available networks [networks] */
|
||||
private fun Map<UserWallet, List<CryptoCurrencyStatus>>.filterAvailableNetworks(
|
||||
private fun Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>.filterAvailableNetworks(
|
||||
networks: List<TokenMarketInfo.Network>,
|
||||
): Map<UserWallet, List<CryptoCurrencyStatus>> {
|
||||
): Map<UserWallet, List<PortfolioData.CryptoCurrencyData>> {
|
||||
return mapValues { entry -> entry.value.filterAvailableNetworks(networks) }
|
||||
}
|
||||
|
||||
/** Filter list of [CryptoCurrencyStatus] by available networks [networks] */
|
||||
private fun List<CryptoCurrencyStatus>.filterAvailableNetworks(
|
||||
private fun List<PortfolioData.CryptoCurrencyData>.filterAvailableNetworks(
|
||||
networks: List<TokenMarketInfo.Network>,
|
||||
): List<CryptoCurrencyStatus> {
|
||||
): List<PortfolioData.CryptoCurrencyData> {
|
||||
val networkIds = networks.map(TokenMarketInfo.Network::networkId)
|
||||
|
||||
return mapNotNull {
|
||||
it.takeIf { networkIds.contains(it.currency.network.backendId) }
|
||||
it.takeIf { networkIds.contains(it.status.currency.network.backendId) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,15 @@ import com.tangem.common.ui.tokens.TokenItemStateConverter
|
|||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.QuickActionUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Converter from [UserWallet] and [CryptoCurrencyStatus] to [PortfolioTokenUM]
|
||||
|
|
@ -17,27 +23,75 @@ internal class PortfolioTokenUMConverter(
|
|||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val onTokenItemClick: (CryptoCurrencyStatus) -> Unit,
|
||||
) : Converter<Pair<UserWallet, CryptoCurrencyStatus>, PortfolioTokenUM> {
|
||||
|
||||
override fun convert(value: Pair<UserWallet, CryptoCurrencyStatus>): PortfolioTokenUM {
|
||||
val (userWallet, cryptoCurrencyStatus) = value
|
||||
private val tokenActionsHandler: TokenActionsHandler,
|
||||
) : Converter<PortfolioData.CryptoCurrencyData, PortfolioTokenUM> {
|
||||
|
||||
override fun convert(value: PortfolioData.CryptoCurrencyData): PortfolioTokenUM {
|
||||
val tokenItemStateConverter = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
titleStateProvider = { TokenItemState.TitleState.Content(text = userWallet.name) },
|
||||
titleStateProvider = { TokenItemState.TitleState.Content(text = value.userWallet.name) },
|
||||
subtitleStateProvider = {
|
||||
TokenItemState.SubtitleState.TextContent(value = cryptoCurrencyStatus.currency.name)
|
||||
TokenItemState.SubtitleState.TextContent(value = value.status.currency.name)
|
||||
},
|
||||
onItemClick = onTokenItemClick,
|
||||
onItemLongClick = null,
|
||||
onItemLongClick = {
|
||||
tokenActionsHandler.onTokenLongClick(cryptoCurrencyData = value)
|
||||
},
|
||||
)
|
||||
|
||||
return PortfolioTokenUM(
|
||||
tokenItemState = tokenItemStateConverter.convert(value = cryptoCurrencyStatus),
|
||||
tokenItemState = tokenItemStateConverter.convert(value = value.status),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
isQuickActionsShown = false,
|
||||
quickActions = quickActions(cryptoData = value),
|
||||
)
|
||||
}
|
||||
|
||||
private fun quickActions(cryptoData: PortfolioData.CryptoCurrencyData): PortfolioTokenUM.QuickActions {
|
||||
return PortfolioTokenUM.QuickActions(
|
||||
actions = listOfNotNull(
|
||||
QuickActionUM.Buy.takeIf {
|
||||
cryptoData.actions.any {
|
||||
it is TokenActionsState.ActionState.Buy &&
|
||||
it.unavailabilityReason == ScenarioUnavailabilityReason.None
|
||||
}
|
||||
},
|
||||
QuickActionUM.Exchange.takeIf {
|
||||
cryptoData.actions.any {
|
||||
it is TokenActionsState.ActionState.Swap &&
|
||||
it.unavailabilityReason == ScenarioUnavailabilityReason.None
|
||||
}
|
||||
},
|
||||
QuickActionUM.Receive.takeIf {
|
||||
cryptoData.actions.any {
|
||||
it is TokenActionsState.ActionState.Receive &&
|
||||
it.unavailabilityReason == ScenarioUnavailabilityReason.None
|
||||
}
|
||||
},
|
||||
).toImmutableList(),
|
||||
onQuickActionClick = {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
when (it) {
|
||||
QuickActionUM.Buy -> tokenActionsHandler.handle(
|
||||
action = TokenActionsBSContentUM.Action.Buy,
|
||||
cryptoCurrencyData = cryptoData,
|
||||
)
|
||||
QuickActionUM.Exchange -> tokenActionsHandler.handle(
|
||||
action = TokenActionsBSContentUM.Action.Exchange,
|
||||
cryptoCurrencyData = cryptoData,
|
||||
)
|
||||
QuickActionUM.Receive -> tokenActionsHandler.handle(
|
||||
action = TokenActionsBSContentUM.Action.Receive,
|
||||
cryptoCurrencyData = cryptoData,
|
||||
)
|
||||
}
|
||||
},
|
||||
onQuickActionLongClick = {
|
||||
if (it == QuickActionUM.Receive) {
|
||||
tokenActionsHandler.handle(
|
||||
action = TokenActionsBSContentUM.Action.CopyAddress,
|
||||
cryptoCurrencyData = cryptoData,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,199 @@
|
|||
package com.tangem.features.markets.portfolio.impl.model
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModels
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ComponentScoped
|
||||
internal class TokenActionsHandler @AssistedInject constructor(
|
||||
private val router: Router,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
@Assisted
|
||||
private val currentAppCurrency: Provider<AppCurrency>,
|
||||
@Assisted("updateTokenReceiveBSConfig")
|
||||
private val updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||
@Assisted("updateTokenActionsBSConfig")
|
||||
private val updateTokenActionsBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||
) {
|
||||
|
||||
fun handle(action: TokenActionsBSContentUM.Action, cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
when (action) {
|
||||
TokenActionsBSContentUM.Action.Buy -> onBuyClick(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.Exchange -> onExchangeClick(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.Receive -> onReceiveClick(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.CopyAddress -> onCopyAddress(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.Sell -> onSellClick(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.Send -> onSendClick(cryptoCurrencyData)
|
||||
TokenActionsBSContentUM.Action.Stake -> onStakeClick(cryptoCurrencyData)
|
||||
}
|
||||
}
|
||||
|
||||
fun onTokenLongClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
updateTokenActionsBSConfig {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = {
|
||||
updateTokenActionsBSConfig {
|
||||
it.copy(isShow = false)
|
||||
}
|
||||
},
|
||||
content = TokenActionsBSContentUM(
|
||||
title = cryptoCurrencyData.userWallet.name,
|
||||
actions = cryptoCurrencyData.actions
|
||||
.mapNotNull { it.toAction(cryptoCurrencyData) }
|
||||
.sortedBy { it.order }
|
||||
.toImmutableList(),
|
||||
onActionClick = { action ->
|
||||
handle(action, cryptoCurrencyData)
|
||||
updateTokenActionsBSConfig {
|
||||
it.copy(isShow = false)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onReceiveClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyData.status
|
||||
val currency = cryptoCurrencyStatus.currency
|
||||
val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return
|
||||
|
||||
updateTokenReceiveBSConfig {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = {
|
||||
updateTokenReceiveBSConfig {
|
||||
it.copy(isShow = false)
|
||||
}
|
||||
},
|
||||
content = TokenReceiveBottomSheetConfig(
|
||||
name = currency.name,
|
||||
symbol = currency.symbol,
|
||||
network = currency.network.name,
|
||||
addresses = networkAddress.availableAddresses
|
||||
.mapToAddressModels(currency)
|
||||
.toImmutableList(),
|
||||
onCopyClick = {},
|
||||
onShareClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCopyAddress(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
val cryptoCurrencyStatus = cryptoCurrencyData.status
|
||||
val networkAddress = cryptoCurrencyStatus.value.networkAddress ?: return
|
||||
val addresses = networkAddress.availableAddresses
|
||||
.mapToAddressModels(cryptoCurrencyStatus.currency)
|
||||
.toImmutableList()
|
||||
val defaultAddress = addresses.firstOrNull()?.value ?: return
|
||||
|
||||
clipboardManager.setText(text = defaultAddress)
|
||||
uiMessageSender.send(SnackbarMessage(resourceReference(R.string.wallet_notification_address_copied)))
|
||||
}
|
||||
|
||||
private fun onBuyClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.Buy(
|
||||
userWallet = cryptoCurrencyData.userWallet,
|
||||
cryptoCurrencyStatus = cryptoCurrencyData.status,
|
||||
appCurrencyCode = currentAppCurrency().code,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSellClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.Sell(
|
||||
cryptoCurrencyStatus = cryptoCurrencyData.status,
|
||||
appCurrencyCode = currentAppCurrency().code,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onExchangeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
router.push(
|
||||
AppRoute.Swap(
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSendClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
router.push(
|
||||
AppRoute.Send(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
currency = cryptoCurrencyData.status.currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onStakeClick(cryptoCurrencyData: PortfolioData.CryptoCurrencyData) {
|
||||
val yield = cryptoCurrencyData.actions.firstOrNull { it is TokenActionsState.ActionState.Stake }
|
||||
?.let { it as TokenActionsState.ActionState.Stake }
|
||||
?.yield ?: return
|
||||
|
||||
router.push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = cryptoCurrencyData.userWallet.walletId,
|
||||
cryptoCurrencyId = cryptoCurrencyData.status.currency.id,
|
||||
yield = yield,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun TokenActionsState.ActionState.toAction(
|
||||
cryptoCurrencyData: PortfolioData.CryptoCurrencyData,
|
||||
): TokenActionsBSContentUM.Action? = when (this) {
|
||||
is TokenActionsState.ActionState.Buy -> TokenActionsBSContentUM.Action.Buy
|
||||
is TokenActionsState.ActionState.CopyAddress -> TokenActionsBSContentUM.Action.CopyAddress
|
||||
is TokenActionsState.ActionState.HideToken -> null
|
||||
is TokenActionsState.ActionState.Receive -> TokenActionsBSContentUM.Action.Receive
|
||||
is TokenActionsState.ActionState.Sell -> TokenActionsBSContentUM.Action.Sell
|
||||
is TokenActionsState.ActionState.Send -> {
|
||||
TokenActionsBSContentUM.Action.Send.takeIf {
|
||||
cryptoCurrencyData.status.value.amount.isNullOrZero().not()
|
||||
}
|
||||
}
|
||||
is TokenActionsState.ActionState.Stake -> TokenActionsBSContentUM.Action.Stake
|
||||
is TokenActionsState.ActionState.Swap -> TokenActionsBSContentUM.Action.Exchange
|
||||
}
|
||||
.takeIf { this.unavailabilityReason == ScenarioUnavailabilityReason.None }
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(
|
||||
currentAppCurrency: Provider<AppCurrency>,
|
||||
@Assisted("updateTokenReceiveBSConfig")
|
||||
updateTokenReceiveBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||
@Assisted("updateTokenActionsBSConfig")
|
||||
updateTokenActionsBSConfig: ((TangemBottomSheetConfig) -> TangemBottomSheetConfig) -> Unit,
|
||||
): TokenActionsHandler
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.markets.portfolio.impl.loader.PortfolioData
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
|
@ -15,6 +16,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
internal class TokensPortfolioUMConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
|
|
@ -22,25 +24,27 @@ internal class TokensPortfolioUMConverter(
|
|||
private val bsConfig: TangemBottomSheetConfig,
|
||||
private val onAddClick: () -> Unit,
|
||||
private val onTokenItemClick: (index: Int, id: CryptoCurrency.ID) -> Unit,
|
||||
) : Converter<Map<UserWallet, List<CryptoCurrencyStatus>>, MyPortfolioUM.Tokens> {
|
||||
private val quickActionsIntents: TokenActionsHandler,
|
||||
) : Converter<Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>, MyPortfolioUM.Tokens> {
|
||||
|
||||
override fun convert(value: Map<UserWallet, List<CryptoCurrencyStatus>>): MyPortfolioUM.Tokens {
|
||||
override fun convert(value: Map<UserWallet, List<PortfolioData.CryptoCurrencyData>>): MyPortfolioUM.Tokens {
|
||||
return MyPortfolioUM.Tokens(
|
||||
tokens = value
|
||||
.flatMap { entry ->
|
||||
entry.value.map { entry.key to it }
|
||||
}
|
||||
.mapIndexed { index, status ->
|
||||
.flatMap { entry -> entry.value }
|
||||
.mapIndexed { index, cryptoData ->
|
||||
PortfolioTokenUMConverter(
|
||||
appCurrency = appCurrency,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onTokenItemClick = { onTokenItemClick(index, it.currency.id) },
|
||||
).convert(status)
|
||||
tokenActionsHandler = quickActionsIntents,
|
||||
).convert(cryptoData)
|
||||
}
|
||||
.toImmutableList(),
|
||||
buttonState = if (isAllAvailableNetworksAdded) AddButtonState.Unavailable else AddButtonState.Available,
|
||||
bsConfig = bsConfig,
|
||||
addToPortfolioBSConfig = bsConfig,
|
||||
onAddClick = onAddClick,
|
||||
tokenReceiveBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenActionsBSConfig = TangemBottomSheetConfig.Empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -73,15 +73,16 @@ private fun Content(state: AddToPortfolioBSContentUM, modifier: Modifier = Modif
|
|||
.verticalScroll(state = scrollState)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
) {
|
||||
UserWalletItem(
|
||||
state = state.selectedWallet,
|
||||
blockColors = TangemBlockCardColors.copy(
|
||||
containerColor = TangemTheme.colors.background.action,
|
||||
disabledContainerColor = TangemTheme.colors.background.action,
|
||||
),
|
||||
)
|
||||
|
||||
SpacerH12()
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.ui.components.PrimaryButton
|
|||
import com.tangem.core.ui.components.SmallButtonShimmer
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet
|
||||
import com.tangem.core.ui.components.buttons.SecondarySmallButton
|
||||
import com.tangem.core.ui.components.buttons.SmallButtonConfig
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition
|
||||
|
|
@ -24,8 +25,8 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.portfolio.impl.ui.preview.PreviewMyPortfolioUMProvider
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM.Tokens.AddButtonState
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
|
||||
@Composable
|
||||
internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
|
||||
|
|
@ -54,8 +55,8 @@ internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
|
|||
}
|
||||
|
||||
when (state) {
|
||||
is MyPortfolioUM.AddFirstToken -> AddToPortfolioBottomSheet(config = state.bsConfig)
|
||||
is MyPortfolioUM.Tokens -> AddToPortfolioBottomSheet(config = state.bsConfig)
|
||||
is MyPortfolioUM.AddFirstToken -> AddToPortfolioBottomSheet(config = state.addToPortfolioBSConfig)
|
||||
is MyPortfolioUM.Tokens -> AddToPortfolioBottomSheet(config = state.addToPortfolioBSConfig)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +106,9 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
TokenReceiveBottomSheet(config = state.tokenReceiveBSConfig)
|
||||
TokenActionsBottomSheet(config = state.tokenActionsBSConfig)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.markets.portfolio.impl.ui
|
|||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -25,11 +26,15 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
|
|||
state = state.tokenItemState,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
modifier = Modifier.background(color = TangemTheme.colors.background.action),
|
||||
itemPaddingValues = PaddingValues(
|
||||
start = TangemTheme.dimens.spacing10,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
|
||||
PortfolioQuickActions(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12)
|
||||
.background(color = TangemTheme.colors.background.action)
|
||||
.padding(
|
||||
bottom = if (lastInList) {
|
||||
TangemTheme.dimens.spacing12
|
||||
|
|
@ -37,8 +42,10 @@ internal fun PortfolioItem(state: PortfolioTokenUM, lastInList: Boolean, modifie
|
|||
TangemTheme.dimens.spacing24
|
||||
},
|
||||
),
|
||||
actions = state.quickActions.actions,
|
||||
isVisible = state.isQuickActionsShown,
|
||||
onActionClick = state.onQuickActionClick,
|
||||
onActionClick = state.quickActions.onQuickActionClick,
|
||||
onActionLongClick = state.quickActions.onQuickActionLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import android.content.res.Configuration
|
|||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Button
|
||||
|
|
@ -15,7 +13,6 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
|
|
@ -23,6 +20,7 @@ import androidx.compose.ui.res.vectorResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.tangem.core.ui.components.SpacerH4
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
|
|
@ -30,34 +28,33 @@ 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.markets.portfolio.impl.ui.state.QuickActionUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
internal fun PortfolioQuickActions(
|
||||
actions: ImmutableList<QuickActionUM>,
|
||||
isVisible: Boolean,
|
||||
onActionClick: (QuickActionUM) -> Unit,
|
||||
onActionLongClick: (QuickActionUM) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (actions.isEmpty()) return
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = expandVertically(expandFrom = Alignment.Top),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top),
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Buy,
|
||||
onClick = { onActionClick(QuickActionUM.Buy) },
|
||||
)
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Exchange,
|
||||
onClick = { onActionClick(QuickActionUM.Exchange) },
|
||||
)
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = QuickActionUM.Receive,
|
||||
onClick = { onActionClick(QuickActionUM.Receive) },
|
||||
)
|
||||
actions.fastForEach { action ->
|
||||
LineSeparator()
|
||||
QuickActionItem(
|
||||
state = action,
|
||||
onClick = { onActionClick(action) },
|
||||
onLongClick = { onActionLongClick(action) }.takeIf { action.longClickAvailable },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,10 +65,9 @@ private fun AnimatedVisibilityScope.LineSeparator(modifier: Modifier = Modifier)
|
|||
val lineColor = TangemTheme.colors.stroke.primary
|
||||
val strokeWidth = TangemTheme.dimens.size1
|
||||
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
|
||||
val verticalPadding = TangemTheme.dimens.spacing2
|
||||
val startPadding = TangemTheme.dimens.spacing18
|
||||
val startPadding = TangemTheme.dimens.spacing30
|
||||
|
||||
val height = TangemTheme.dimens.size16 + verticalPadding * 2
|
||||
val height = TangemTheme.dimens.size16
|
||||
|
||||
Canvas(
|
||||
modifier = modifier
|
||||
|
|
@ -96,31 +92,42 @@ private fun AnimatedVisibilityScope.LineSeparator(modifier: Modifier = Modifier)
|
|||
|
||||
drawLine(
|
||||
color = lineColor,
|
||||
start = Offset(x, verticalPadding.toPx()),
|
||||
end = Offset(x, size.height - verticalPadding.toPx()),
|
||||
start = Offset(x, 0f),
|
||||
end = Offset(x, size.height),
|
||||
strokeWidth = strokeWidth.toPx(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@OptIn(ExperimentalAnimationApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun AnimatedVisibilityScope.QuickActionItem(
|
||||
state: QuickActionUM,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val hapticManager = LocalHapticManager.current
|
||||
val onLongClickInternal: (() -> Unit)? = if (onLongClick != null) {
|
||||
{
|
||||
hapticManager.perform(TangemHapticEffect.View.LongPress)
|
||||
onLongClick()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(TangemTheme.shapes.roundedCornersMedium)
|
||||
.clickable {
|
||||
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
|
||||
onClick()
|
||||
}
|
||||
.padding(horizontal = TangemTheme.dimens.spacing2, vertical = TangemTheme.dimens.spacing3),
|
||||
.combinedClickable(
|
||||
onLongClick = onLongClickInternal,
|
||||
onClick = {
|
||||
hapticManager.perform(TangemHapticEffect.View.SegmentTick)
|
||||
onClick()
|
||||
},
|
||||
)
|
||||
.padding(horizontal = TangemTheme.dimens.spacing14, vertical = TangemTheme.dimens.spacing4),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18),
|
||||
) {
|
||||
|
|
@ -190,8 +197,10 @@ private fun Preview() {
|
|||
modifier = Modifier.background(color = TangemTheme.colors.background.action),
|
||||
) {
|
||||
PortfolioQuickActions(
|
||||
actions = QuickActionUM.entries.toImmutableList(),
|
||||
isVisible = isVisible,
|
||||
onActionClick = {},
|
||||
onActionLongClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -205,8 +214,10 @@ private fun PreviewRtl() {
|
|||
TangemThemePreview(rtl = true) {
|
||||
Box(modifier = Modifier.background(color = TangemTheme.colors.background.action)) {
|
||||
PortfolioQuickActions(
|
||||
actions = QuickActionUM.entries.toImmutableList(),
|
||||
isVisible = true,
|
||||
onActionClick = {},
|
||||
onActionLongClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.features.markets.portfolio.impl.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -10,12 +9,12 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.core.ui.components.SimpleSettingsRow
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
|
||||
import com.tangem.core.ui.components.inputrow.InputRowChecked
|
||||
import com.tangem.core.ui.components.inputrow.inner.DividerContainer
|
||||
import com.tangem.core.ui.components.rows.CornersToRound
|
||||
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.markets.portfolio.impl.ui.state.TokenActionsBSContentUM
|
||||
|
|
@ -50,16 +49,15 @@ private fun Content(content: TokenActionsBSContentUM) {
|
|||
else -> CornersToRound.ZERO
|
||||
}
|
||||
|
||||
DividerContainer(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(cornersToRound.getShape())
|
||||
.background(TangemTheme.colors.background.action)
|
||||
.clickable { content.onActionClick(action) },
|
||||
showDivider = index != content.actions.lastIndex,
|
||||
.background(TangemTheme.colors.background.action),
|
||||
) {
|
||||
InputRowChecked(
|
||||
text = action.text,
|
||||
checked = false,
|
||||
SimpleSettingsRow(
|
||||
title = action.text.resolveReference(),
|
||||
icon = action.iconRes,
|
||||
onItemsClick = { content.onActionClick(action) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.markets.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.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -54,13 +53,10 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
|
|||
iconUrl = null,
|
||||
),
|
||||
isScanCardNotificationVisible = true,
|
||||
isWalletBlockVisible = true,
|
||||
continueButtonEnabled = true,
|
||||
onContinueButtonClick = {},
|
||||
walletSelectorConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
walletSelectorConfig = TangemBottomSheetConfig.Empty,
|
||||
),
|
||||
AddToPortfolioBSContentUM(
|
||||
selectedWallet = userWallet,
|
||||
|
|
@ -81,13 +77,10 @@ internal class PreviewAddToPortfolioBSContentProvider : PreviewParameterProvider
|
|||
iconUrl = null,
|
||||
),
|
||||
isScanCardNotificationVisible = true,
|
||||
isWalletBlockVisible = false,
|
||||
continueButtonEnabled = false,
|
||||
onContinueButtonClick = {},
|
||||
walletSelectorConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
walletSelectorConfig = TangemBottomSheetConfig.Empty,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -2,12 +2,13 @@ package com.tangem.features.markets.portfolio.impl.ui.preview
|
|||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.MyPortfolioUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.PortfolioTokenUM
|
||||
import com.tangem.features.markets.portfolio.impl.ui.state.QuickActionUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfolioUM> {
|
||||
|
||||
|
|
@ -16,39 +17,29 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfol
|
|||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Available,
|
||||
bsConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenReceiveBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenActionsBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken, sampleToken.copy(isQuickActionsShown = true)),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Unavailable,
|
||||
bsConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenReceiveBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenActionsBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Tokens(
|
||||
tokens = persistentListOf(sampleToken.copy(isQuickActionsShown = true), sampleToken),
|
||||
buttonState = MyPortfolioUM.Tokens.AddButtonState.Loading,
|
||||
bsConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenReceiveBSConfig = TangemBottomSheetConfig.Empty,
|
||||
tokenActionsBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.AddFirstToken(
|
||||
bsConfig = TangemBottomSheetConfig(
|
||||
isShow = false,
|
||||
onDismissRequest = {},
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
addToPortfolioBSConfig = TangemBottomSheetConfig.Empty,
|
||||
onAddClick = {},
|
||||
),
|
||||
MyPortfolioUM.Loading,
|
||||
|
|
@ -67,7 +58,11 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider<MyPortfol
|
|||
onItemLongClick = {},
|
||||
),
|
||||
isQuickActionsShown = false,
|
||||
onQuickActionClick = {},
|
||||
quickActions = PortfolioTokenUM.QuickActions(
|
||||
actions = QuickActionUM.entries.toImmutableList(),
|
||||
onQuickActionClick = {},
|
||||
onQuickActionLongClick = {},
|
||||
),
|
||||
isBalanceHidden = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ 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 continueButtonEnabled: Boolean,
|
||||
val onContinueButtonClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ internal sealed class MyPortfolioUM {
|
|||
data class Tokens(
|
||||
val tokens: ImmutableList<PortfolioTokenUM>,
|
||||
val buttonState: AddButtonState,
|
||||
val bsConfig: TangemBottomSheetConfig,
|
||||
val addToPortfolioBSConfig: TangemBottomSheetConfig,
|
||||
val tokenReceiveBSConfig: TangemBottomSheetConfig,
|
||||
val tokenActionsBSConfig: TangemBottomSheetConfig,
|
||||
val onAddClick: () -> Unit,
|
||||
) : MyPortfolioUM() {
|
||||
|
||||
|
|
@ -22,7 +24,7 @@ internal sealed class MyPortfolioUM {
|
|||
}
|
||||
|
||||
data class AddFirstToken(
|
||||
val bsConfig: TangemBottomSheetConfig,
|
||||
val addToPortfolioBSConfig: TangemBottomSheetConfig,
|
||||
val onAddClick: () -> Unit,
|
||||
) : MyPortfolioUM()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class PortfolioTokenUM(
|
||||
val tokenItemState: TokenItemState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isQuickActionsShown: Boolean,
|
||||
val onQuickActionClick: (QuickActionUM) -> Unit,
|
||||
)
|
||||
val quickActions: QuickActions,
|
||||
) {
|
||||
|
||||
data class QuickActions(
|
||||
val actions: ImmutableList<QuickActionUM>,
|
||||
val onQuickActionClick: (QuickActionUM) -> Unit,
|
||||
val onQuickActionLongClick: (QuickActionUM) -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ internal enum class QuickActionUM(
|
|||
val title: TextReference,
|
||||
val description: TextReference,
|
||||
@DrawableRes val icon: Int,
|
||||
val longClickAvailable: Boolean = false,
|
||||
) {
|
||||
Buy(
|
||||
title = resourceReference(R.string.common_buy),
|
||||
|
|
@ -26,5 +27,6 @@ internal enum class QuickActionUM(
|
|||
title = resourceReference(R.string.common_receive),
|
||||
description = resourceReference(R.string.receive_token_description),
|
||||
icon = R.drawable.ic_arrow_down_24,
|
||||
longClickAvailable = true,
|
||||
),
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.markets.portfolio.impl.ui.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -16,13 +17,38 @@ internal data class TokenActionsBSContentUM(
|
|||
@Immutable
|
||||
enum class Action(
|
||||
val text: TextReference,
|
||||
@DrawableRes val iconRes: Int,
|
||||
) {
|
||||
CopyAddress(text = resourceReference(R.string.common_copy_address)),
|
||||
Receive(text = resourceReference(R.string.common_receive)),
|
||||
Sell(text = resourceReference(R.string.common_sell)),
|
||||
Buy(text = resourceReference(R.string.common_buy)),
|
||||
Send(text = resourceReference(R.string.common_send)),
|
||||
Exchange(text = resourceReference(R.string.common_exchange)),
|
||||
Stake(text = resourceReference(R.string.common_stake)),
|
||||
CopyAddress(
|
||||
text = resourceReference(R.string.common_copy_address),
|
||||
iconRes = R.drawable.ic_copy_24,
|
||||
),
|
||||
Send(
|
||||
text = resourceReference(R.string.common_send),
|
||||
iconRes = R.drawable.ic_arrow_up_24,
|
||||
),
|
||||
Receive(
|
||||
text = resourceReference(R.string.common_receive),
|
||||
iconRes = R.drawable.ic_arrow_down_24,
|
||||
),
|
||||
Buy(
|
||||
text = resourceReference(R.string.common_buy),
|
||||
iconRes = R.drawable.ic_plus_24,
|
||||
),
|
||||
Sell(
|
||||
text = resourceReference(R.string.common_sell),
|
||||
iconRes = R.drawable.ic_currency_24,
|
||||
),
|
||||
Exchange(
|
||||
text = resourceReference(R.string.common_exchange),
|
||||
iconRes = R.drawable.ic_exchange_horizontal_24,
|
||||
),
|
||||
Stake(
|
||||
text = resourceReference(R.string.common_stake),
|
||||
iconRes = R.drawable.ic_staking_24,
|
||||
),
|
||||
;
|
||||
|
||||
val order: Int = ordinal
|
||||
}
|
||||
}
|
||||
|
|
@ -121,16 +121,18 @@ internal class MarketsTokenItemConverter(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun TokenMarket.getTrendType(): PriceChangeType {
|
||||
val percent = when (currentTrendInterval) {
|
||||
TrendInterval.H24 -> tokenQuotesShort.h24ChangePercent
|
||||
TrendInterval.D7 -> tokenQuotesShort.weekChangePercent
|
||||
TrendInterval.M1 -> tokenQuotesShort.monthChangePercent
|
||||
}.setScale(2, RoundingMode.UP)
|
||||
|
||||
return when (percent.compareTo(BigDecimal.ZERO)) {
|
||||
1 -> PriceChangeType.UP
|
||||
-1 -> PriceChangeType.DOWN
|
||||
}
|
||||
val scaled = percent.setScale(4, RoundingMode.HALF_UP)
|
||||
return when {
|
||||
scaled == null -> PriceChangeType.NEUTRAL
|
||||
scaled > BigDecimal.ZERO -> PriceChangeType.UP
|
||||
scaled < BigDecimal.ZERO -> PriceChangeType.DOWN
|
||||
else -> PriceChangeType.NEUTRAL
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ dependencies {
|
|||
implementation(projects.core.deepLinks.global)
|
||||
implementation(projects.core.featuretoggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
implementation(projects.libs.crypto)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
|||
|
||||
import androidx.paging.PagingData
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.ui.tokens.getUnavailabilityReasonText
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
|
||||
|
|
@ -12,7 +13,6 @@ import com.tangem.core.ui.event.consumedEvent
|
|||
import com.tangem.core.ui.event.triggeredEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.card.NetworkHasDerivationUseCase
|
||||
|
|
@ -216,7 +216,7 @@ internal class TokenDetailsStateFactory(
|
|||
isShow = true,
|
||||
onDismissRequest = clickIntents::onDismissDialog,
|
||||
content = TokenDetailsDialogConfig.DialogContentConfig.DisabledButtonReasonDialogConfig(
|
||||
text = getUnavailabilityReasonText(unavailabilityReason),
|
||||
text = unavailabilityReason.getUnavailabilityReasonText(),
|
||||
onConfirmClick = clickIntents::onDismissDialog,
|
||||
),
|
||||
),
|
||||
|
|
@ -408,66 +408,4 @@ internal class TokenDetailsStateFactory(
|
|||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
|
||||
return when (unavailabilityReason) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_send,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.EmptyBalance -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_send,
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_sell,
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.BuyUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_buy_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotExchangeable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_not_exchangeable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_sell_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.Unreachable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.analytics)
|
||||
implementation(projects.domain.visa)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.staking.models)
|
||||
|
||||
//TODO: Create api/impl modules for onboarding [REDACTED_JIRA]
|
||||
implementation(projects.features.onboarding)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.arkivanov.decompose.defaultComponentContext
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.utils.asRouter
|
||||
import com.tangem.core.decompose.context.DefaultAppComponentContext
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
|
|
@ -44,6 +46,9 @@ internal class WalletFragment : ComposeFragment() {
|
|||
@Inject
|
||||
internal lateinit var marketsFeatureToggles: MarketsFeatureToggles
|
||||
|
||||
@Inject
|
||||
internal lateinit var appRouter: AppRouter
|
||||
|
||||
private var marketsEntryComponent: MarketsEntryComponent? = null
|
||||
|
||||
private val _walletRouter: InnerWalletRouter
|
||||
|
|
@ -60,6 +65,7 @@ internal class WalletFragment : ComposeFragment() {
|
|||
messageHandler = uiDependencies.eventMessageHandler,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
hiltComponentBuilder = componentBuilder,
|
||||
replaceRouter = appRouter.asRouter(),
|
||||
)
|
||||
|
||||
marketsEntryComponent = marketsEntryComponentFactory.create(appContext)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ internal class MultiWalletCurrencyActionsConverter(
|
|||
is TokenActionsState.ActionState.Stake -> {
|
||||
title = resourceReference(R.string.common_stake)
|
||||
icon = R.drawable.ic_staking_24
|
||||
action = { clickIntents.onStakeClick(cryptoCurrencyStatus) }
|
||||
action = { clickIntents.onStakeClick(cryptoCurrencyStatus, actionsState.yield) }
|
||||
}
|
||||
is TokenActionsState.ActionState.Sell -> {
|
||||
title = resourceReference(R.string.common_sell)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.common.address.AddressType
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.tokens.getUnavailabilityReasonText
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.ui.clipboard.ClipboardManager
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
|
|
@ -14,7 +14,6 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.mapToAddressModel
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.WrappedList
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
|
|
@ -22,7 +21,7 @@ import com.tangem.domain.appcurrency.extenstions.unwrap
|
|||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.staking.GetYieldUseCase
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -67,7 +66,7 @@ interface WalletCurrencyActionsClickIntents {
|
|||
|
||||
fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?)
|
||||
|
||||
fun onCopyAddressLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus): TextReference?
|
||||
|
||||
|
|
@ -100,7 +99,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val vibratorHapticManager: VibratorHapticManager,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
private val getYieldUseCase: GetYieldUseCase,
|
||||
private val appRouter: AppRouter,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
|
|
@ -380,25 +378,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
showErrorIfDemoModeOrElse(action = ::openExplorer)
|
||||
}
|
||||
|
||||
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||
override fun onStakeClick(cryptoCurrencyStatus: CryptoCurrencyStatus, yield: Yield?) {
|
||||
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
|
||||
stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId))
|
||||
|
||||
viewModelScope.launch {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val yield = getYieldUseCase.invoke(
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
).getOrElse {
|
||||
error("Staking is unavailable for ${cryptoCurrency.name}")
|
||||
}
|
||||
|
||||
reduxStateHolder.dispatch(
|
||||
TradeCryptoAction.Stake(
|
||||
appRouter.push(
|
||||
AppRoute.Staking(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyId = cryptoCurrency.id,
|
||||
yield = yield,
|
||||
yield = yield ?: return@launch,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -487,14 +479,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private fun handleUnavailabilityReason(unavailabilityReason: ScenarioUnavailabilityReason): Boolean {
|
||||
if (unavailabilityReason == ScenarioUnavailabilityReason.None) return false
|
||||
|
||||
val unavailabilityReasonText = getUnavailabilityReasonText(unavailabilityReason)
|
||||
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
walletEventSender.send(
|
||||
event = WalletEvent.ShowAlert(
|
||||
state = WalletAlertState.DefaultAlert(
|
||||
title = null,
|
||||
message = unavailabilityReasonText,
|
||||
message = unavailabilityReason.getUnavailabilityReasonText(),
|
||||
onConfirmClick = null,
|
||||
),
|
||||
),
|
||||
|
|
@ -503,66 +493,4 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getUnavailabilityReasonText(unavailabilityReason: ScenarioUnavailabilityReason): TextReference {
|
||||
return when (unavailabilityReason) {
|
||||
is ScenarioUnavailabilityReason.StakingUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_staking_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.PendingTransaction -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_send,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_pending_transaction_sell,
|
||||
formatArgs = wrappedList(unavailabilityReason.networkName),
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.EmptyBalance -> {
|
||||
when (unavailabilityReason.withdrawalScenario) {
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SEND -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_send,
|
||||
)
|
||||
ScenarioUnavailabilityReason.WithdrawalScenario.SELL -> resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_empty_balance_sell,
|
||||
)
|
||||
}
|
||||
}
|
||||
is ScenarioUnavailabilityReason.BuyUnavailable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_buy_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotExchangeable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_not_exchangeable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
is ScenarioUnavailabilityReason.NotSupportedBySellService -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_reason_sell_unavailable,
|
||||
formatArgs = wrappedList(unavailabilityReason.cryptoCurrencyName),
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.Unreachable -> {
|
||||
resourceReference(
|
||||
id = R.string.token_button_unavailability_generic_description,
|
||||
)
|
||||
}
|
||||
ScenarioUnavailabilityReason.UnassociatedAsset -> resourceReference(
|
||||
id = R.string.warning_receive_blocked_hedera_token_association_required_message,
|
||||
)
|
||||
ScenarioUnavailabilityReason.None -> {
|
||||
throw IllegalArgumentException("The unavailability reason must be other than None")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue