Updated on 2026-08-14
This commit is contained in:
commit
0e4ee4a7fe
11 changed files with 294 additions and 120 deletions
|
|
@ -422,7 +422,7 @@
|
|||
<string name="send_additional_field_already_included">Already included in the entered address</string>
|
||||
<string name="send_alert_fee_coverage_subract_text">Subtract</string>
|
||||
<string name="send_alert_fee_coverage_title">Not enough funds to cover the network fee. Do you want to subtract the amount required to cover the fee?</string>
|
||||
<string name="send_alert_fee_too_high_text">The commission amount is %@ times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_alert_fee_too_high_text">The commission amount is %s times the recommended amount. Make sure that the custom settings are correct.</string>
|
||||
<string name="send_alert_fee_too_low_text">You specified a commission below the recommended amount, which could cause a delay in your transaction. Continue?</string>
|
||||
<string name="send_alert_transaction_failed_text">Reason: %1$s\nCode: %2$s</string>
|
||||
<string name="send_alert_transaction_failed_title">The transaction is not completed</string>
|
||||
|
|
|
|||
|
|
@ -33,4 +33,15 @@ class GetFixedTxHistoryItemsUseCase(
|
|||
}
|
||||
}.mapLeft { TxHistoryListError.DataError(it) }
|
||||
}
|
||||
|
||||
suspend fun getSync(
|
||||
userWalletId: UserWalletId,
|
||||
currency: CryptoCurrency,
|
||||
pageSize: Int = DEFAULT_PAGE_SIZE,
|
||||
refresh: Boolean = false,
|
||||
): Either<TxHistoryListError, List<TxHistoryItem>> {
|
||||
return Either.catch {
|
||||
repository.getFixedSizeTxHistoryItems(userWalletId, currency, pageSize, refresh)
|
||||
}.mapLeft { TxHistoryListError.DataError(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,11 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
|
||||
data class SendRecipientListContent(
|
||||
val id: String,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
val title: TextReference = TextReference.EMPTY,
|
||||
val subtitle: TextReference = TextReference.EMPTY,
|
||||
val timestamp: TextReference? = null,
|
||||
val subtitleEndOffset: Int = 0,
|
||||
@DrawableRes val subtitleIconRes: Int? = null,
|
||||
val isVisible: Boolean = true,
|
||||
val isLoading: Boolean = false,
|
||||
)
|
||||
|
|
@ -4,6 +4,7 @@ import arrow.core.getOrElse
|
|||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.core.ui.components.currency.tokenicon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -17,12 +18,14 @@ import com.tangem.features.send.impl.presentation.state.amount.SendAmountSubtrac
|
|||
import com.tangem.features.send.impl.presentation.state.confirm.SendConfirmStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fee.SendFeeStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.fields.SendAmountFieldConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientListConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientHistoryListConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientStateConverter
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.SendRecipientWalletListConverter
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
@ -77,9 +80,11 @@ internal class SendStateFactory(
|
|||
isTapHelpPreviewEnabledProvider = isTapHelpPreviewEnabledProvider,
|
||||
)
|
||||
}
|
||||
private val recipientListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientListConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
private val recipientWalletListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientWalletListConverter()
|
||||
}
|
||||
private val recipientHistoryListStateConverter by lazy(LazyThreadSafetyMode.NONE) {
|
||||
SendRecipientHistoryListConverter(
|
||||
cryptoCurrencyStatusProvider = cryptoCurrencyStatusProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -123,11 +128,23 @@ internal class SendStateFactory(
|
|||
//endregion
|
||||
|
||||
//region recipient
|
||||
fun onLoadedRecipientList(wallets: List<AvailableWallet?>, txHistory: List<TxHistoryItem>): SendUiState =
|
||||
recipientListStateConverter.convert(
|
||||
wallets = wallets,
|
||||
txHistory = txHistory,
|
||||
fun onLoadedWalletsList(wallets: List<AvailableWallet?>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
wallets = recipientWalletListStateConverter.convert(wallets),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onLoadedHistoryList(txHistory: List<TxHistoryItem>): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
return state.copy(
|
||||
recipientState = state.recipientState?.copy(
|
||||
recent = recipientHistoryListStateConverter.convert(txHistory),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun onRecipientAddressValueChange(value: String, isXAddress: Boolean = false): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
|
|
@ -228,6 +245,22 @@ internal class SendStateFactory(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getHiddenRecentListState(isAddressInWallet: Boolean, isValidAddress: Boolean): SendUiState {
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
val isNotValid = isAddressInWallet || !isValidAddress
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { recent ->
|
||||
recent.copy(isVisible = isNotValid && (recent.isLoading || recent.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { wallet ->
|
||||
wallet.copy(isVisible = isNotValid && (wallet.isLoading || wallet.title != TextReference.EMPTY))
|
||||
}.toPersistentList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region send
|
||||
|
|
|
|||
|
|
@ -10,49 +10,26 @@ import com.tangem.domain.tokens.model.CryptoCurrency
|
|||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.features.send.impl.R
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import com.tangem.features.send.impl.presentation.state.SendUiState
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_DEFAULT_COUNT
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.RECENT_KEY_TAG
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.toFormattedCurrencyString
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendRecipientListConverter(
|
||||
private val currentStateProvider: Provider<SendUiState>,
|
||||
internal class SendRecipientHistoryListConverter(
|
||||
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus>,
|
||||
) {
|
||||
) : Converter<List<TxHistoryItem>, ImmutableList<SendRecipientListContent>> {
|
||||
|
||||
fun convert(wallets: List<AvailableWallet?>, txHistory: List<TxHistoryItem>): SendUiState {
|
||||
override fun convert(value: List<TxHistoryItem>): ImmutableList<SendRecipientListContent> {
|
||||
val cryptoCurrency = cryptoCurrencyStatusProvider().currency
|
||||
val state = currentStateProvider()
|
||||
val recipientState = state.recipientState ?: return state
|
||||
|
||||
return state.copy(
|
||||
recipientState = recipientState.copy(
|
||||
wallets = wallets.filterWallets(),
|
||||
recent = txHistory.filterRecipients(cryptoCurrency),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<AvailableWallet?>.filterWallets() = this.filterNotNull()
|
||||
.groupBy { item -> item.name }
|
||||
.values.map {
|
||||
it.mapIndexed { index, item ->
|
||||
val name = if (it.size > 1) {
|
||||
"${item.name} ${index.inc()}"
|
||||
} else {
|
||||
item.name
|
||||
}
|
||||
SendRecipientListContent(
|
||||
id = item.address,
|
||||
title = TextReference.Str(item.address),
|
||||
subtitle = TextReference.Str(name),
|
||||
)
|
||||
}
|
||||
return value.filterRecipients(cryptoCurrency).ifEmpty {
|
||||
emptyListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT)
|
||||
}
|
||||
.flatten()
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
private fun List<TxHistoryItem>.filterRecipients(cryptoCurrency: CryptoCurrency) = this.filter { item ->
|
||||
val isTransfer = item.type == TxHistoryItem.TransactionType.Transfer
|
||||
|
|
@ -65,9 +42,9 @@ internal class SendRecipientListConverter(
|
|||
isTransfer && isSingleAddress && isNotContract
|
||||
}
|
||||
.take(RECENT_LIST_SIZE)
|
||||
.map { tx ->
|
||||
.mapIndexed { index, tx ->
|
||||
SendRecipientListContent(
|
||||
id = tx.txHash,
|
||||
id = "$RECENT_KEY_TAG$index",
|
||||
title = tx.extractAddress(),
|
||||
subtitle = stringReference(tx.getAmount(cryptoCurrency).trim()),
|
||||
timestamp = tx.extractTimestamp(),
|
||||
|
|
@ -2,10 +2,10 @@ package com.tangem.features.send.impl.presentation.state.recipient
|
|||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.features.send.impl.presentation.state.SendStates
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.*
|
||||
import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal class SendRecipientStateConverter(
|
||||
private val clickIntents: SendClickIntents,
|
||||
|
|
@ -26,8 +26,8 @@ internal class SendRecipientStateConverter(
|
|||
memoTextField = memoFieldConverter.convertOrNull(value.memo),
|
||||
network = cryptoCurrencyStatusProvider().currency.network.name,
|
||||
isPrimaryButtonEnabled = false,
|
||||
wallets = persistentListOf(),
|
||||
recent = persistentListOf(),
|
||||
wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT),
|
||||
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.send.impl.presentation.domain.AvailableWallet
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_DEFAULT_COUNT
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.WALLET_KEY_TAG
|
||||
import com.tangem.features.send.impl.presentation.state.recipient.utils.emptyListState
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class SendRecipientWalletListConverter :
|
||||
Converter<List<AvailableWallet?>, PersistentList<SendRecipientListContent>> {
|
||||
override fun convert(value: List<AvailableWallet?>): PersistentList<SendRecipientListContent> {
|
||||
return value.filterWallets().ifEmpty {
|
||||
emptyListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<AvailableWallet?>.filterWallets() = this.filterNotNull()
|
||||
.groupBy { item -> item.name }
|
||||
.values.map {
|
||||
it.mapIndexed { index, item ->
|
||||
val name = if (it.size > 1) {
|
||||
"${item.name} ${index.inc()}"
|
||||
} else {
|
||||
item.name
|
||||
}
|
||||
SendRecipientListContent(
|
||||
id = "${WALLET_KEY_TAG}$index",
|
||||
title = TextReference.Str(item.address),
|
||||
subtitle = TextReference.Str(name),
|
||||
)
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
.toPersistentList()
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.tangem.features.send.impl.presentation.state.recipient.utils
|
||||
|
||||
import com.tangem.features.send.impl.presentation.domain.SendRecipientListContent
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal const val WALLET_DEFAULT_COUNT = 1
|
||||
internal const val RECENT_DEFAULT_COUNT = 3
|
||||
internal const val WALLET_KEY_TAG = "wallet"
|
||||
internal const val RECENT_KEY_TAG = "recent"
|
||||
|
||||
internal fun loadingListState(tag: String, count: Int) = buildList {
|
||||
repeat(count) {
|
||||
add(
|
||||
SendRecipientListContent(
|
||||
id = "$tag$it",
|
||||
isLoading = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
||||
internal fun emptyListState(tag: String, count: Int) = buildList {
|
||||
repeat(count) {
|
||||
add(
|
||||
SendRecipientListContent(
|
||||
id = "$tag$it",
|
||||
isLoading = false,
|
||||
isVisible = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}.toPersistentList()
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
package com.tangem.features.send.impl.presentation.ui.recipient
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -9,6 +13,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
|
@ -16,6 +21,8 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
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
|
||||
|
|
@ -43,9 +50,42 @@ fun ListItemWithIcon(
|
|||
info: String? = null,
|
||||
subtitleEndOffset: Int = 0,
|
||||
@DrawableRes subtitleIconRes: Int? = null,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = isLoading,
|
||||
label = "Recent List Content Animation",
|
||||
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
|
||||
) { isLoadingState ->
|
||||
if (isLoadingState) {
|
||||
ListItemLoading(modifier = modifier)
|
||||
} else {
|
||||
ListItemWithIcon(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
onClick = onClick,
|
||||
info = info,
|
||||
subtitleEndOffset = subtitleEndOffset,
|
||||
subtitleIconRes = subtitleIconRes,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ListItemWithIcon(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
info: String? = null,
|
||||
subtitleEndOffset: Int = 0,
|
||||
@DrawableRes subtitleIconRes: Int? = null,
|
||||
) {
|
||||
val hapticFeedback = rememberHapticFeedback(state = title, onAction = onClick)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { hapticFeedback() }
|
||||
|
|
@ -55,13 +95,12 @@ fun ListItemWithIcon(
|
|||
address = title,
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size40)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius20)),
|
||||
.size(TangemTheme.dimens.size36)
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius18)),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing10)
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
EllipsisText(
|
||||
text = title,
|
||||
|
|
@ -102,6 +141,43 @@ fun ListItemWithIcon(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ListItemLoading(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
CircleShimmer(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing8)
|
||||
.size(TangemTheme.dimens.size36),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.height(TangemTheme.dimens.size32)
|
||||
.padding(start = TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.spacing70,
|
||||
height = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
RectangleShimmer(
|
||||
radius = TangemTheme.dimens.radius3,
|
||||
modifier = Modifier.size(
|
||||
width = TangemTheme.dimens.spacing52,
|
||||
height = TangemTheme.dimens.spacing12,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region preview
|
||||
@Preview
|
||||
@Composable
|
||||
|
|
@ -115,6 +191,7 @@ private fun ListItemWithIconPreview_Light(
|
|||
subtitleEndOffset = config.subtitleEndOffset,
|
||||
subtitleIconRes = config.iconRes,
|
||||
onClick = {},
|
||||
isLoading = config.isLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -131,6 +208,7 @@ private fun ListItemWithIconPreview_Dark(
|
|||
subtitleEndOffset = config.subtitleEndOffset,
|
||||
subtitleIconRes = config.iconRes,
|
||||
onClick = {},
|
||||
isLoading = config.isLoading,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,6 +219,7 @@ private data class ListItemWithIconPreviewConfig(
|
|||
val info: String? = null,
|
||||
val subtitleEndOffset: Int = 0,
|
||||
val iconRes: Int? = null,
|
||||
val isLoading: Boolean = false,
|
||||
)
|
||||
|
||||
private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvider<ListItemWithIconPreviewConfig>(
|
||||
|
|
@ -163,6 +242,14 @@ private class ListItemWithIconPreviewProvider : CollectionPreviewParameterProvid
|
|||
title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
subtitle = "Wallet",
|
||||
),
|
||||
ListItemWithIconPreviewConfig(
|
||||
title = "0x34B4492A412D84A6E606288f3Bd714b89135D4dE",
|
||||
subtitle = "0.000000000000000000000000000000 BTC",
|
||||
info = "0.0.0000 at 00:00",
|
||||
subtitleEndOffset = "BTC".length,
|
||||
iconRes = R.drawable.ic_arrow_down_24,
|
||||
isLoading = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
//endregion
|
||||
|
|
@ -2,8 +2,8 @@ package com.tangem.features.send.impl.presentation.ui.recipient
|
|||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -76,13 +76,13 @@ internal fun SendRecipientContent(
|
|||
listItem(
|
||||
list = wallets,
|
||||
clickIntents = clickIntents,
|
||||
isLast = recipients.isEmpty(),
|
||||
isLast = recipients.any { !it.isVisible },
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
)
|
||||
listHeaderItem(
|
||||
titleRes = R.string.send_recent_transactions,
|
||||
isVisible = recipients.isNotEmpty() && recipients.first().isVisible,
|
||||
isFirst = wallets.isEmpty(),
|
||||
isFirst = wallets.any { !it.isVisible },
|
||||
)
|
||||
listItem(
|
||||
list = recipients,
|
||||
|
|
@ -143,20 +143,9 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Boolean, isFirst: Boolean) {
|
||||
item(
|
||||
key = titleRes,
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
label = "Header Appearance Animation",
|
||||
enter = slideInVertically() + fadeIn(),
|
||||
exit = slideOutVertically() + fadeOut(),
|
||||
modifier = Modifier
|
||||
.animateItemPlacement()
|
||||
.animateContentSize(),
|
||||
) {
|
||||
item(key = titleRes) {
|
||||
AnimateRecentAppearance(isVisible) {
|
||||
val (topPadding, paddingFromTop) = if (isFirst) {
|
||||
TangemTheme.dimens.spacing20 to TangemTheme.dimens.spacing12
|
||||
} else {
|
||||
|
|
@ -192,7 +181,6 @@ private fun LazyListScope.listHeaderItem(@StringRes titleRes: Int, isVisible: Bo
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
private fun LazyListScope.listItem(
|
||||
list: ImmutableList<SendRecipientListContent>,
|
||||
clickIntents: SendClickIntents,
|
||||
|
|
@ -206,22 +194,20 @@ private fun LazyListScope.listItem(
|
|||
) { index ->
|
||||
val item = list[index]
|
||||
val title = item.title.resolveReference()
|
||||
AnimatedVisibility(
|
||||
visible = item.isVisible,
|
||||
label = "Header Appearance Animation",
|
||||
enter = slideInVertically() + fadeIn(),
|
||||
exit = slideOutVertically() + fadeOut(),
|
||||
modifier = Modifier
|
||||
.animateItemPlacement()
|
||||
.animateContentSize(),
|
||||
) {
|
||||
AnimateRecentAppearance(item.isVisible) {
|
||||
ListItemWithIcon(
|
||||
title = title,
|
||||
subtitle = if (isBalanceHidden) STARS else item.subtitle.resolveReference(),
|
||||
info = item.timestamp?.resolveReference(),
|
||||
subtitleEndOffset = item.subtitleEndOffset,
|
||||
subtitleIconRes = item.subtitleIconRes,
|
||||
onClick = { clickIntents.onRecipientAddressValueChange(title, EnterAddressSource.RecentAddress) },
|
||||
onClick = {
|
||||
clickIntents.onRecipientAddressValueChange(
|
||||
title,
|
||||
EnterAddressSource.RecentAddress,
|
||||
)
|
||||
},
|
||||
isLoading = item.isLoading,
|
||||
modifier = Modifier
|
||||
.then(
|
||||
if (isLast && index == list.lastIndex) {
|
||||
|
|
@ -240,4 +226,22 @@ private fun LazyListScope.listItem(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimateRecentAppearance(isVisible: Boolean, content: @Composable () -> Unit) {
|
||||
AnimatedContent(
|
||||
targetState = isVisible,
|
||||
label = "Item Appearance Animation",
|
||||
transitionSpec = {
|
||||
(slideInHorizontally() + fadeIn())
|
||||
.togetherWith(slideOutVertically() + fadeOut())
|
||||
},
|
||||
) {
|
||||
if (it) {
|
||||
content()
|
||||
} else {
|
||||
Box(modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ import com.tangem.domain.transaction.usecase.CreateTransactionUseCase
|
|||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.IsFeeApproximateUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.txhistory.models.TxHistoryItem
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.txhistory.usecase.GetFixedTxHistoryItemsUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -56,7 +55,6 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import timber.log.Timber
|
||||
|
|
@ -183,7 +181,6 @@ internal class SendViewModel @Inject constructor(
|
|||
|
||||
private var balanceJobHolder = JobHolder()
|
||||
private var balanceHidingJobHolder = JobHolder()
|
||||
private var recipientsJobHolder = JobHolder()
|
||||
private var feeJobHolder = JobHolder()
|
||||
private var addressValidationJobHolder = JobHolder()
|
||||
private var memoValidationJobHolder = JobHolder()
|
||||
|
|
@ -374,34 +371,33 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getWalletsAndRecent() {
|
||||
combine(
|
||||
flow = getUserWallets().conflate(),
|
||||
flow2 = getTxHistory().conflate(),
|
||||
) { wallets, txHistory ->
|
||||
uiState = stateFactory.onLoadedRecipientList(
|
||||
wallets = wallets,
|
||||
txHistory = txHistory,
|
||||
)
|
||||
getUserWallets()
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
getTxHistory()
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.launchIn(viewModelScope)
|
||||
.saveIn(recipientsJobHolder)
|
||||
}
|
||||
|
||||
private fun getUserWallets(): Flow<List<AvailableWallet?>> {
|
||||
return getWalletsUseCase()
|
||||
private fun getUserWallets() {
|
||||
getWalletsUseCase()
|
||||
.conflate()
|
||||
.distinctUntilChanged()
|
||||
.map { userWallets ->
|
||||
.onEach { userWallets ->
|
||||
coroutineScope {
|
||||
userWallets
|
||||
.filterNot { it.walletId == userWalletId || it.isLocked }
|
||||
.map { wallet ->
|
||||
async(dispatchers.io) {
|
||||
wallet.toAvailableWallet()
|
||||
}
|
||||
}
|
||||
}.awaitAll()
|
||||
runCatching {
|
||||
userWallets
|
||||
.filterNot { it.walletId == userWalletId || it.isLocked }
|
||||
.map { wallet ->
|
||||
async(dispatchers.io) { wallet.toAvailableWallet() }
|
||||
}.awaitAll()
|
||||
}.onSuccess { result ->
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = result)
|
||||
}.onFailure {
|
||||
uiState = stateFactory.onLoadedWalletsList(wallets = emptyList())
|
||||
}
|
||||
}
|
||||
}
|
||||
.flowOn(dispatchers.main)
|
||||
.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
private suspend fun UserWallet.toAvailableWallet(): AvailableWallet? {
|
||||
|
|
@ -431,14 +427,12 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getTxHistory(): Flow<List<TxHistoryItem>> {
|
||||
return getFixedTxHistoryItemsUseCase(
|
||||
private suspend fun getTxHistory() {
|
||||
val txHistoryList = getFixedTxHistoryItemsUseCase.getSync(
|
||||
userWalletId = userWalletId,
|
||||
currency = cryptoCurrency,
|
||||
).fold(
|
||||
ifRight = { it.distinctUntilChanged() },
|
||||
ifLeft = { emptyFlow() },
|
||||
)
|
||||
).getOrElse { emptyList() }
|
||||
uiState = stateFactory.onLoadedHistoryList(txHistory = txHistoryList)
|
||||
}
|
||||
|
||||
private fun onStateActive() {
|
||||
|
|
@ -636,13 +630,9 @@ internal class SendViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onEnteredValidAddress(isValidAddress: Boolean, isAddressInWallet: Boolean) {
|
||||
val recipientState = uiState.recipientState ?: return
|
||||
val isVisible = isAddressInWallet || !isValidAddress
|
||||
uiState = uiState.copy(
|
||||
recipientState = recipientState.copy(
|
||||
recent = recipientState.recent.map { it.copy(isVisible = isVisible) }.toPersistentList(),
|
||||
wallets = recipientState.wallets.map { it.copy(isVisible = isVisible) }.toPersistentList(),
|
||||
),
|
||||
uiState = stateFactory.getHiddenRecentListState(
|
||||
isAddressInWallet = isAddressInWallet,
|
||||
isValidAddress = isValidAddress,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue