diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculator.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculator.kt index 19c51a1f07..41ba3d4e24 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculator.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/PriceChangeCalculator.kt @@ -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() } diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt index b8fa5480a5..8a0a3d2976 100644 --- a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/subcomponents/destination/entity/DestinationUM.kt @@ -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, val wallets: ImmutableList, val networkName: String, + val accountTitleUM: AccountTitleUM?, val isValidating: Boolean = false, val isInitialized: Boolean = false, val isRecentHidden: Boolean, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt index 82b98239dc..cd838ffa32 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/amount/model/SendAmountModel.kt @@ -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) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt index 713ea50d93..dd7319a481 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt @@ -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.toAvailableWallets(): List { diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt index d90eeef6ee..f9ad07bde8 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt @@ -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, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt index c1c579cbd8..51b11db3df 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationRecentListTransformer.kt @@ -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, private val txHistoryList: List, + private val account: Account.CryptoPortfolio?, + private val isAccountsMode: Boolean, ) : Transformer { 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, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt index 134fbad1fb..54989a861c 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt @@ -144,6 +144,7 @@ private class DestinationBlockPreviewProvider : PreviewParameterProvider diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt index 458fa9d397..bd9b01c007 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/ListItemWithIcon.kt @@ -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 \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt index 2b1b2e0033..4b68b252ac 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/SendDestinationContent.kt @@ -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, diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt index 5493ad0d2d..98710745ed 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/success/ui/SendWithSwapSuccessContent.kt @@ -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(