Updated on 2026-08-14
This commit is contained in:
parent
006669ae0d
commit
b92bb00c7f
10 changed files with 110 additions and 18 deletions
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.models.tokenlist.TokenList
|
|||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Utility object for calculating the [PriceChange] of a cryptocurrency portfolio.
|
||||
|
|
@ -50,11 +51,11 @@ object PriceChangeCalculator {
|
|||
}
|
||||
|
||||
val total = statuses.sumOf {
|
||||
val weight = it.value.fiatAmount.orZero().divide(balance)
|
||||
val weight = it.value.fiatAmount.orZero().divide(balance, 2, RoundingMode.HALF_UP)
|
||||
val priceChange = it.value.priceChange.orZero()
|
||||
|
||||
weight * priceChange
|
||||
}
|
||||
}.stripTrailingZeros()
|
||||
|
||||
return PriceChange(value = total, source = walletTotalFiatBalance.source).lceContent()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.send.v2.api.subcomponents.destination.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
|
|
@ -15,6 +16,7 @@ sealed class DestinationUM {
|
|||
val recent: ImmutableList<DestinationRecipientListUM>,
|
||||
val wallets: ImmutableList<DestinationRecipientListUM>,
|
||||
val networkName: String,
|
||||
val accountTitleUM: AccountTitleUM?,
|
||||
val isValidating: Boolean = false,
|
||||
val isInitialized: Boolean = false,
|
||||
val isRecentHidden: Boolean,
|
||||
|
|
|
|||
|
|
@ -140,8 +140,8 @@ internal class SendAmountModel @Inject constructor(
|
|||
flow2 = params.accountFlow,
|
||||
flow3 = params.isAccountModeFlow,
|
||||
) { newCryptoCurrencyStatus, account, isAccountsMode ->
|
||||
maxAmountBoundary = MaxEnterAmountConverter().convert(newCryptoCurrencyStatus)
|
||||
cryptoCurrencyStatus = newCryptoCurrencyStatus
|
||||
maxAmountBoundary = MaxEnterAmountConverter().convert(cryptoCurrencyStatus)
|
||||
initMinBoundary(cryptoCurrencyStatus, account, isAccountsMode)
|
||||
}.flowOn(dispatchers.default)
|
||||
.launchIn(modelScope)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@ import com.tangem.core.decompose.model.Model
|
|||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
|
||||
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
|
||||
import com.tangem.domain.models.network.CryptoCurrencyAddress
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -64,7 +67,10 @@ internal class SendDestinationModel @Inject constructor(
|
|||
private val isSelfSendAvailableUseCase: IsSelfSendAvailableUseCase,
|
||||
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
|
||||
private val parseQrCodeUseCase: ParseQrCodeUseCase,
|
||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val accountsFeatureToggles: AccountsFeatureToggles,
|
||||
) : Model(), SendDestinationClickIntents {
|
||||
private val params: SendDestinationComponentParams = paramsContainer.require()
|
||||
|
||||
|
|
@ -142,7 +148,7 @@ internal class SendDestinationModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun saveResult() {
|
||||
private fun saveResult() {
|
||||
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
|
||||
params.callback.onDestinationResult(uiState.value)
|
||||
}
|
||||
|
|
@ -193,12 +199,17 @@ internal class SendDestinationModel @Inject constructor(
|
|||
).getOrElse { flowOf(emptyList()) }.map {
|
||||
waitForDelay(RECENT_LOAD_DELAY) { it }
|
||||
}.conflate(),
|
||||
) { destinationWalletList, txHistoryList ->
|
||||
flow3 = isAccountsModeEnabledUseCase().distinctUntilChanged(),
|
||||
flow4 = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
getAccountCurrencyStatusUseCase(userWalletId, cryptoCurrency).distinctUntilChanged()
|
||||
} else {
|
||||
flowOf(null)
|
||||
},
|
||||
) { destinationWalletList, txHistoryList, isAccountsMode, accountCurrencyStatus ->
|
||||
val isSelfSendAvailable = isSelfSendAvailableUseCase.invokeSync(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
)
|
||||
|
||||
_uiState.update(
|
||||
SendDestinationRecentListTransformer(
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
|
|
@ -206,9 +217,11 @@ internal class SendDestinationModel @Inject constructor(
|
|||
isSelfSendAvailable = isSelfSendAvailable,
|
||||
destinationWalletList = destinationWalletList,
|
||||
txHistoryList = txHistoryList,
|
||||
account = accountCurrencyStatus?.account,
|
||||
isAccountsMode = isAccountsMode,
|
||||
),
|
||||
)
|
||||
}.launchIn(modelScope)
|
||||
}.flowOn(dispatchers.default).launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun List<UserWallet>.toAvailableWallets(): List<DestinationWalletUM> {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal class SendDestinationInitialStateTransformer(
|
|||
isValuePasted = false,
|
||||
)
|
||||
},
|
||||
accountTitleUM = null,
|
||||
wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT),
|
||||
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
|
||||
networkName = cryptoCurrency.network.name,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,42 @@
|
|||
package com.tangem.features.send.v2.subcomponents.destination.model.transformers
|
||||
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.features.send.v2.api.subcomponents.destination.entity.DestinationUM
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientHistoryListConverter
|
||||
import com.tangem.features.send.v2.subcomponents.destination.model.converters.SendRecipientWalletListConverter
|
||||
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationWalletUM
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.transformer.Transformer
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SendDestinationRecentListTransformer(
|
||||
private val senderAddress: String?,
|
||||
private val cryptoCurrency: CryptoCurrency,
|
||||
private val isSelfSendAvailable: Boolean,
|
||||
private val destinationWalletList: List<DestinationWalletUM>,
|
||||
private val txHistoryList: List<TxInfo>,
|
||||
private val account: Account.CryptoPortfolio?,
|
||||
private val isAccountsMode: Boolean,
|
||||
) : Transformer<DestinationUM> {
|
||||
override fun transform(prevState: DestinationUM): DestinationUM {
|
||||
val state = prevState as? DestinationUM.Content ?: return prevState
|
||||
|
||||
return state.copy(
|
||||
accountTitleUM = if (account != null && isAccountsMode) {
|
||||
AccountTitleUM.Account(
|
||||
name = account.accountName.toUM().value,
|
||||
icon = account.icon.toUM(),
|
||||
prefixText = stringReference(StringsSigns.DOT),
|
||||
)
|
||||
} else {
|
||||
AccountTitleUM.Text(TextReference.EMPTY)
|
||||
},
|
||||
wallets = SendRecipientWalletListConverter(
|
||||
senderAddress = senderAddress,
|
||||
isSelfSendAvailable = isSelfSendAvailable,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider<Destina
|
|||
isValidating = false,
|
||||
isInitialized = true,
|
||||
isRecentHidden = false,
|
||||
accountTitleUM = null,
|
||||
)
|
||||
|
||||
override val values: Sequence<DestinationUM.Content>
|
||||
|
|
|
|||
|
|
@ -23,15 +23,22 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.account.AccountNameUM
|
||||
import com.tangem.common.ui.account.AccountTitle
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.atoms.text.EllipsisText
|
||||
import com.tangem.core.ui.components.atoms.text.TextEllipsis
|
||||
import com.tangem.core.ui.components.icons.identicon.IdentIcon
|
||||
import com.tangem.core.ui.extensions.rememberHapticFeedback
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
import com.tangem.utils.StringsSigns
|
||||
|
||||
/**
|
||||
* Row item with title and subtitle
|
||||
|
|
@ -50,6 +57,7 @@ fun ListItemWithIcon(
|
|||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
accountTitleUM: AccountTitleUM? = null,
|
||||
info: String? = null,
|
||||
subtitleEndOffset: Int = 0,
|
||||
@DrawableRes subtitleIconRes: Int? = null,
|
||||
|
|
@ -68,6 +76,7 @@ fun ListItemWithIcon(
|
|||
subtitle = subtitle,
|
||||
onClick = onClick,
|
||||
info = info,
|
||||
accountTitleUM = accountTitleUM,
|
||||
subtitleEndOffset = subtitleEndOffset,
|
||||
subtitleIconRes = subtitleIconRes,
|
||||
modifier = modifier,
|
||||
|
|
@ -80,6 +89,7 @@ fun ListItemWithIcon(
|
|||
private fun ListItemWithIcon(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
accountTitleUM: AccountTitleUM?,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
info: String? = null,
|
||||
|
|
@ -115,7 +125,7 @@ private fun ListItemWithIcon(
|
|||
ellipsis = TextEllipsis.Middle,
|
||||
modifier = Modifier,
|
||||
)
|
||||
Row {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (subtitleIconRes != null) {
|
||||
Icon(
|
||||
painter = painterResource(id = subtitleIconRes),
|
||||
|
|
@ -142,6 +152,13 @@ private fun ListItemWithIcon(
|
|||
color = TangemTheme.colors.text.tertiary,
|
||||
ellipsis = TextEllipsis.OffsetEnd(offsetEnd = offset),
|
||||
)
|
||||
if (accountTitleUM != null) {
|
||||
AccountTitle(
|
||||
accountTitleUM = accountTitleUM,
|
||||
textStyle = TangemTheme.typography.caption2,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +212,7 @@ private fun ListItemWithIconPreview(
|
|||
ListItemWithIcon(
|
||||
title = config.title,
|
||||
subtitle = config.subtitle,
|
||||
accountTitleUM = config.accountTitleUM,
|
||||
subtitleEndOffset = config.subtitleEndOffset,
|
||||
subtitleIconRes = config.iconRes,
|
||||
onClick = {},
|
||||
|
|
@ -206,6 +224,7 @@ private fun ListItemWithIconPreview(
|
|||
private data class ListItemWithIconPreviewConfig(
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val accountTitleUM: AccountTitleUM.Account? = null,
|
||||
val info: String? = null,
|
||||
val subtitleEndOffset: Int = 0,
|
||||
val iconRes: Int? = null,
|
||||
|
|
@ -240,6 +259,15 @@ private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvid
|
|||
iconRes = R.drawable.ic_arrow_down_24,
|
||||
isLoading = true,
|
||||
),
|
||||
ListItemWithIconPreviewConfig(
|
||||
title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
subtitle = "Wallet",
|
||||
accountTitleUM = AccountTitleUM.Account(
|
||||
name = AccountNameUM.DefaultMain.value,
|
||||
icon = CryptoPortfolioIcon.ofDefaultCustomAccount().toUM(),
|
||||
prefixText = stringReference(StringsSigns.DOT),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
//endregion
|
||||
|
|
@ -21,7 +21,9 @@ import androidx.compose.ui.text.buildAnnotatedString
|
|||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.account.AccountTitleUM
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.containers.FooterContainer
|
||||
import com.tangem.core.ui.components.inputrow.InputRowRecipient
|
||||
import com.tangem.core.ui.extensions.*
|
||||
|
|
@ -46,7 +48,6 @@ internal fun SendDestinationContent(
|
|||
if (state !is DestinationUM.Content) return
|
||||
val recipients = state.recent
|
||||
val wallets = state.wallets
|
||||
val memoField = state.memoTextField
|
||||
val address = state.addressTextField
|
||||
val isValidating by remember(state.isValidating) { derivedStateOf { state.isValidating } }
|
||||
val isError by remember(address.isError) { derivedStateOf { address.isError } }
|
||||
|
|
@ -64,18 +65,23 @@ internal fun SendDestinationContent(
|
|||
onQrCodeClick = clickIntents::onQrCodeScanClick,
|
||||
)
|
||||
memoField(
|
||||
memoField = memoField,
|
||||
memoField = state.memoTextField,
|
||||
onMemoChange = clickIntents::onRecipientMemoValueChange,
|
||||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recipient_wallets_title,
|
||||
titleRes = when (state.accountTitleUM) {
|
||||
is AccountTitleUM.Account -> R.string.common_accounts
|
||||
else -> R.string.send_recipient_wallets_title
|
||||
},
|
||||
isLoading = state.accountTitleUM == null,
|
||||
isVisible = wallets.isNotEmpty() && wallets.first().isVisible && !state.isRecentHidden,
|
||||
isFirst = true,
|
||||
)
|
||||
listItem(
|
||||
list = wallets,
|
||||
isLast = recipients.any { !it.isVisible },
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
accountTitleUM = state.accountTitleUM,
|
||||
isBalanceHidden = false,
|
||||
isRecentHidden = state.isRecentHidden,
|
||||
onClick = { title ->
|
||||
clickIntents.onRecipientAddressValueChange(
|
||||
|
|
@ -86,6 +92,7 @@ internal fun SendDestinationContent(
|
|||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recent_transactions,
|
||||
isLoading = state.accountTitleUM == null,
|
||||
isVisible = recipients.isNotEmpty() && recipients.first().isVisible && !state.isRecentHidden,
|
||||
isFirst = wallets.any { !it.isVisible },
|
||||
)
|
||||
|
|
@ -180,7 +187,12 @@ private fun LazyListScope.memoField(
|
|||
}
|
||||
}
|
||||
|
||||
private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Boolean, isFirst: Boolean) {
|
||||
private fun LazyListScope.listHeaderItem(
|
||||
@StringRes titleRes: Int,
|
||||
isVisible: Boolean,
|
||||
isLoading: Boolean,
|
||||
isFirst: Boolean,
|
||||
) {
|
||||
item(key = titleRes) {
|
||||
AnimateRecentAppearance(isVisible) {
|
||||
val (topPadding, paddingFromTop) = if (isFirst) {
|
||||
|
|
@ -189,10 +201,8 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo
|
|||
0.dp to 8.dp
|
||||
}
|
||||
val topRadius = if (isFirst) 16.dp else 0.dp
|
||||
Text(
|
||||
text = stringResourceSafe(titleRes),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
AnimatedContent(
|
||||
targetState = isLoading,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = topPadding)
|
||||
|
|
@ -209,7 +219,22 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo
|
|||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
),
|
||||
)
|
||||
) { currentIsLoading ->
|
||||
if (currentIsLoading) {
|
||||
Box {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
text = stringResourceSafe(titleRes),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResourceSafe(titleRes),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -220,6 +245,7 @@ private fun LazyListScope.listItem(
|
|||
isBalanceHidden: Boolean,
|
||||
isRecentHidden: Boolean,
|
||||
onClick: (String) -> Unit,
|
||||
accountTitleUM: AccountTitleUM? = null,
|
||||
) {
|
||||
items(
|
||||
count = list.size,
|
||||
|
|
@ -233,6 +259,7 @@ private fun LazyListScope.listItem(
|
|||
ListItemWithIcon(
|
||||
title = title,
|
||||
subtitle = item.subtitle.orMaskWithStars(isBalanceHidden).resolveReference(),
|
||||
accountTitleUM = accountTitleUM,
|
||||
info = item.timestamp?.resolveReference(),
|
||||
subtitleEndOffset = item.subtitleEndOffset,
|
||||
subtitleIconRes = item.subtitleIconRes,
|
||||
|
|
|
|||
|
|
@ -310,6 +310,7 @@ private fun SendWithSwapSuccessContent_Preview() {
|
|||
isValidating = false,
|
||||
isInitialized = false,
|
||||
isRecentHidden = false,
|
||||
accountTitleUM = AccountTitleUM.Text(resourceReference(R.string.send_recipient_wallets_title)),
|
||||
),
|
||||
feeSelectorUM = FeeSelectorUM.Content(
|
||||
fees = TransactionFee.Single(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue