Updated on 2026-08-14
This commit is contained in:
parent
74e4b644f7
commit
0680fb9e7e
9 changed files with 64 additions and 569 deletions
|
|
@ -1,220 +0,0 @@
|
|||
package com.tangem.features.txhistory.converter
|
||||
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.utils.toTimeFormat
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.network.TxInfo.TransactionType
|
||||
import com.tangem.features.txhistory.impl.R
|
||||
import com.tangem.features.txhistory.utils.TxHistoryUiActions
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.annotations.RemoveWithToggle
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.isZero
|
||||
import com.tangem.utils.toBriefAddressFormat
|
||||
|
||||
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]. Produces pre-redesign TransactionState.")
|
||||
@RemoveWithToggle("APP_REDESIGN_ENABLED")
|
||||
internal class TxHistoryItemToTransactionStateConverter(
|
||||
private val currency: CryptoCurrency,
|
||||
private val txHistoryUiActions: TxHistoryUiActions,
|
||||
) : Converter<TxInfo, TransactionState> {
|
||||
override fun convert(value: TxInfo): TransactionState {
|
||||
return TransactionState.Content(
|
||||
txHash = value.txHash,
|
||||
amount = value.getAmount(),
|
||||
time = value.timestampInMillis.toTimeFormat(),
|
||||
status = value.status.toUiStatus(),
|
||||
direction = value.extractDirection(),
|
||||
iconRes = value.extractIcon(),
|
||||
title = value.extractTitle(),
|
||||
subtitle = value.extractSubtitle(),
|
||||
timestamp = value.timestampInMillis,
|
||||
onClick = { txHistoryUiActions.openTxInExplorer(value.txHash) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractIcon(): Int = if (status == TxInfo.TransactionStatus.Failed) {
|
||||
R.drawable.ic_close_24
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.DeployContract,
|
||||
is TransactionType.Approve,
|
||||
-> R.drawable.ic_doc_24
|
||||
is TransactionType.Staking.Stake,
|
||||
is TransactionType.Staking.Vote,
|
||||
is TransactionType.Staking.Restake,
|
||||
-> R.drawable.ic_transaction_history_staking_24
|
||||
is TransactionType.Staking.ClaimRewards,
|
||||
-> R.drawable.ic_transaction_history_claim_rewards_24
|
||||
is TransactionType.Staking.Unstake,
|
||||
is TransactionType.Staking.Withdraw,
|
||||
-> R.drawable.ic_transaction_history_unstaking_24
|
||||
is TransactionType.YieldSupply.Enter -> R.drawable.ic_connect_24
|
||||
is TransactionType.YieldSupply.InitializeToken -> R.drawable.ic_gear_24
|
||||
is TransactionType.YieldSupply.ReactivateToken -> R.drawable.ic_refresh_24
|
||||
is TransactionType.YieldSupply.Exit -> R.drawable.ic_disconnect_24
|
||||
is TransactionType.Operation,
|
||||
is TransactionType.Swap,
|
||||
is TransactionType.Transfer,
|
||||
is TransactionType.UnknownOperation,
|
||||
is TransactionType.YieldSupply.Send,
|
||||
TransactionType.YieldSupply.Topup,
|
||||
TransactionType.GaslessFee,
|
||||
-> if (isOutgoing) R.drawable.ic_arrow_up_24 else R.drawable.ic_arrow_down_24
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun TxInfo.extractTitle(): TextReference = when (val type = type) {
|
||||
is TransactionType.Approve -> resourceReference(R.string.common_approval)
|
||||
is TransactionType.Operation -> stringReference(type.name)
|
||||
is TransactionType.Swap -> resourceReference(R.string.common_swap)
|
||||
is TransactionType.Transfer -> resourceReference(R.string.common_transfer)
|
||||
is TransactionType.Staking.Stake -> resourceReference(R.string.common_stake)
|
||||
is TransactionType.Staking.Unstake -> resourceReference(R.string.common_unstake)
|
||||
is TransactionType.Staking.Vote -> resourceReference(R.string.staking_vote)
|
||||
is TransactionType.Staking.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
|
||||
is TransactionType.Staking.Withdraw -> resourceReference(R.string.staking_withdraw)
|
||||
is TransactionType.Staking.Restake -> resourceReference(R.string.staking_restake)
|
||||
is TransactionType.YieldSupply -> when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> resourceReference(R.string.yield_module_transaction_enter)
|
||||
is TransactionType.YieldSupply.Exit -> resourceReference(R.string.yield_module_transaction_exit)
|
||||
TransactionType.YieldSupply.Topup -> resourceReference(R.string.yield_module_transaction_topup)
|
||||
is TransactionType.YieldSupply.Send -> {
|
||||
if (type.isYieldSupplyWithdraw || isOutgoing) {
|
||||
resourceReference(R.string.yield_module_transaction_withdraw)
|
||||
} else {
|
||||
resourceReference(R.string.common_transfer)
|
||||
}
|
||||
}
|
||||
is TransactionType.YieldSupply.DeployContract -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_deploy_contract,
|
||||
)
|
||||
is TransactionType.YieldSupply.InitializeToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_initialize,
|
||||
)
|
||||
is TransactionType.YieldSupply.ReactivateToken -> resourceReference(
|
||||
R.string
|
||||
.yield_module_transaction_reactivate,
|
||||
)
|
||||
}
|
||||
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
|
||||
TransactionType.GaslessFee -> resourceReference(R.string.gasless_transaction_fee)
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitle(): TextReference {
|
||||
return when (val type = this.type) {
|
||||
is TransactionType.YieldSupply -> if (currency is CryptoCurrency.Coin) {
|
||||
if (type is TransactionType.YieldSupply.Send) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
resourceReference(
|
||||
R.string.transaction_history_transaction_for_address,
|
||||
wrappedList(type.address?.toBriefAddressFormat().orEmpty()),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
when (type) {
|
||||
is TransactionType.YieldSupply.Enter -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_enter_subtitle, wrappedList(amount))
|
||||
}
|
||||
TransactionType.YieldSupply.Topup -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_topup_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Exit -> {
|
||||
val amount = amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(R.string.yield_module_transaction_exit_subtitle, wrappedList(amount))
|
||||
}
|
||||
is TransactionType.YieldSupply.Send -> {
|
||||
if (isOutgoing || !type.isYieldSupplyWithdraw) {
|
||||
extractSubtitleByAddressType()
|
||||
} else {
|
||||
val amount =
|
||||
amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
resourceReference(
|
||||
R.string.yield_module_transaction_exit_subtitle,
|
||||
wrappedList(amount),
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
else -> extractSubtitleByAddressType()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxInfo.extractSubtitleByAddressType(): TextReference =
|
||||
when (val interactionAddress = interactionAddressType) {
|
||||
is TxInfo.InteractionAddressType.Contract -> resourceReference(
|
||||
id = R.string.transaction_history_contract_address,
|
||||
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
|
||||
)
|
||||
is TxInfo.InteractionAddressType.Multiple -> resourceReference(
|
||||
id = if (isOutgoing) {
|
||||
R.string.transaction_history_transaction_to_address
|
||||
} else {
|
||||
R.string.transaction_history_transaction_from_address
|
||||
},
|
||||
formatArgs = wrappedList(resourceReference(R.string.transaction_history_multiple_addresses)),
|
||||
)
|
||||
is TxInfo.InteractionAddressType.User -> resourceReference(
|
||||
id = if (isOutgoing) {
|
||||
R.string.transaction_history_transaction_to_address
|
||||
} else {
|
||||
R.string.transaction_history_transaction_from_address
|
||||
},
|
||||
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
|
||||
)
|
||||
is TxInfo.InteractionAddressType.Validator -> resourceReference(
|
||||
id = R.string.transaction_history_transaction_validator,
|
||||
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
|
||||
)
|
||||
null -> {
|
||||
TextReference.EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
private fun TxInfo.extractDirection() =
|
||||
if (isOutgoing) TransactionState.Content.Direction.OUTGOING else TransactionState.Content.Direction.INCOMING
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
private fun TxInfo.getAmount(): String {
|
||||
when (type) {
|
||||
is TransactionType.Staking.Vote,
|
||||
TransactionType.Staking.ClaimRewards,
|
||||
TransactionType.Staking.Withdraw,
|
||||
-> return ""
|
||||
|
||||
is TransactionType.YieldSupply -> {
|
||||
if (currency is CryptoCurrency.Token && type == TransactionType.YieldSupply.Send && !isOutgoing) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
val prefix = when {
|
||||
status == TxInfo.TransactionStatus.Failed -> ""
|
||||
this.amount.isZero() -> ""
|
||||
else -> if (isOutgoing) StringsSigns.MINUS else StringsSigns.PLUS
|
||||
}
|
||||
return prefix + amount.format { crypto(symbol = currency.symbol, decimals = currency.decimals) }
|
||||
}
|
||||
|
||||
private fun TxInfo.TransactionStatus.toUiStatus() = when (this) {
|
||||
TxInfo.TransactionStatus.Confirmed -> TransactionState.Content.Status.Confirmed
|
||||
TxInfo.TransactionStatus.Failed -> TransactionState.Content.Status.Failed
|
||||
TxInfo.TransactionStatus.Unconfirmed -> TransactionState.Content.Status.Unconfirmed
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus
|
||||
|
|
@ -27,7 +26,6 @@ import com.tangem.features.txhistory.component.TxHistoryComponent
|
|||
import com.tangem.features.txhistory.converter.ExpressTxToTransactionItemUMConverter
|
||||
import com.tangem.features.txhistory.converter.TxHistoryInfoToTransactionItemUMConverter
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionItemUMConverter
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
|
||||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUpdateListener
|
||||
import com.tangem.features.txhistory.state.TxHistoryItemsSnapshot
|
||||
|
|
@ -56,7 +54,6 @@ internal class TxHistoryModel @Inject constructor(
|
|||
private val urlOpener: UrlOpener,
|
||||
private val txHistoryUpdateListener: TxHistoryUpdateListener,
|
||||
private val stateController: TxHistoryStateController,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val txHistoryFeatureToggle: TxHistoryFeatureToggles,
|
||||
private val historyTxListManagerFactory: HistoryTxListManager.Factory,
|
||||
private val appTxHistoryFetcher: AppTxHistoryFetcher,
|
||||
|
|
@ -67,17 +64,9 @@ internal class TxHistoryModel @Inject constructor(
|
|||
|
||||
private val params: TxHistoryComponent.Params = paramsContainer.require()
|
||||
|
||||
private val lookupDataFlow: Flow<TxHistoryLookupContext> = if (designFeatureToggles.isRedesignEnabled) {
|
||||
ownerLookupProducer()
|
||||
.flowOn(dispatchers.default)
|
||||
.shareIn(modelScope, SharingStarted.WhileSubscribed(), replay = 1)
|
||||
} else {
|
||||
emptyFlow()
|
||||
}
|
||||
|
||||
@RemoveWithToggle("APP_REDESIGN_ENABLED")
|
||||
private val legacyTxHistoryItemConverter =
|
||||
TxHistoryItemToTransactionStateConverter(currency = params.currency, txHistoryUiActions = this)
|
||||
private val lookupDataFlow: Flow<TxHistoryLookupContext> = ownerLookupProducer()
|
||||
.flowOn(dispatchers.default)
|
||||
.shareIn(modelScope, SharingStarted.WhileSubscribed(), replay = 1)
|
||||
|
||||
@RemoveWithToggle("AND_15767_NEW_TX_HISTORY_ENABLED")
|
||||
private val txHistoryListManager: TxHistoryListManager? = if (!txHistoryFeatureToggle.isNewTxHistoryEnabled) {
|
||||
|
|
@ -86,10 +75,8 @@ internal class TxHistoryModel @Inject constructor(
|
|||
dispatchers = dispatchers,
|
||||
userWalletId = params.userWalletId,
|
||||
currency = params.currency,
|
||||
designFeatureToggles = designFeatureToggles,
|
||||
txHistoryUiActions = this,
|
||||
lookupDataFlow = lookupDataFlow,
|
||||
legacyTxHistoryItemConverter = legacyTxHistoryItemConverter,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
@ -297,11 +284,7 @@ internal class TxHistoryModel @Inject constructor(
|
|||
.distinctUntilChanged()
|
||||
|
||||
val combined: Flow<Pair<Option<CryptoCurrencyStatus>, TxHistoryLookupContext?>> =
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
combine(statusFlow, lookupDataFlow) { status, lookup -> status to lookup }
|
||||
} else {
|
||||
statusFlow.map { it to null }
|
||||
}
|
||||
combine(statusFlow, lookupDataFlow) { status, lookup -> status to lookup }
|
||||
|
||||
combined
|
||||
.onEach { (status, lookup) -> handlePendingTxsChanges(status, lookup) }
|
||||
|
|
@ -324,7 +307,6 @@ internal class TxHistoryModel @Inject constructor(
|
|||
)
|
||||
pending.map(converter::convert).toPersistentList()
|
||||
},
|
||||
legacyPendingTxs = { pending.map(legacyTxHistoryItemConverter::convert).toPersistentList() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
package com.tangem.features.txhistory.state
|
||||
|
||||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* Snapshot of transaction history items emitted by [TxHistoryListManager]. Wraps either the
|
||||
* primary or legacy item list so that one [Flow] can carry both pipelines, with the active
|
||||
* variant chosen via the design feature toggle.
|
||||
* Snapshot of transaction history items emitted by [TxHistoryListManager].
|
||||
*/
|
||||
internal sealed interface TxHistoryItemsSnapshot {
|
||||
|
||||
data class Items(val items: ImmutableList<TxHistoryItemsUM.TxHistoryItemUM>) : TxHistoryItemsSnapshot
|
||||
|
||||
data class LegacyItems(val items: ImmutableList<TxHistoryUM.TxHistoryItemUM>) : TxHistoryItemsSnapshot
|
||||
}
|
||||
|
|
@ -1,113 +1,71 @@
|
|||
package com.tangem.features.txhistory.state
|
||||
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionItemUM
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Owns the transaction history UI state and routes updates to either [legacyUiState] or
|
||||
* [uiState] based on [DesignFeatureToggles.isRedesignEnabled]. Only the active pipeline gets
|
||||
* emitted to; the inactive flow stays at its initial Loading value.
|
||||
* Owns the transaction history UI state and routes updates to [uiState].
|
||||
*/
|
||||
@ModelScoped
|
||||
internal class TxHistoryStateController @Inject constructor(
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
) {
|
||||
internal class TxHistoryStateController @Inject constructor() {
|
||||
|
||||
private val _legacyUiState: MutableStateFlow<TxHistoryUM> =
|
||||
MutableStateFlow(TxHistoryUM.Loading(isBalanceHidden = true, onExploreClick = {}))
|
||||
val legacyUiState: StateFlow<TxHistoryUM> = _legacyUiState
|
||||
/**
|
||||
* Pre-redesign state. Kept only to satisfy consumers that still read the legacy UI (they are
|
||||
* dead at runtime while the redesign is enabled); no longer populated.
|
||||
*/
|
||||
val legacyUiState: StateFlow<TxHistoryUM> =
|
||||
MutableStateFlow(TxHistoryUM.Loading(isBalanceHidden = true, onExploreClick = {})).asStateFlow()
|
||||
|
||||
private val _uiState: MutableStateFlow<TxHistoryItemsUM> =
|
||||
MutableStateFlow(TxHistoryItemsUM.Loading(isBalanceHidden = true, onExploreClick = {}))
|
||||
val uiState: StateFlow<TxHistoryItemsUM> = _uiState
|
||||
|
||||
val isNotSupported: Boolean
|
||||
get() = if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.value is TxHistoryItemsUM.NotSupported
|
||||
} else {
|
||||
_legacyUiState.value is TxHistoryUM.NotSupported
|
||||
}
|
||||
get() = _uiState.value is TxHistoryItemsUM.NotSupported
|
||||
|
||||
fun setLoading(isBalanceHidden: Boolean, onExploreClick: () -> Unit) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.value = TxHistoryItemsUM.Loading(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
} else {
|
||||
_legacyUiState.value = TxHistoryUM.Loading(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
_uiState.value = TxHistoryItemsUM.Loading(
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
|
||||
fun setLoadingIfNotContent(onExploreClick: () -> Unit) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.update { state ->
|
||||
state as? TxHistoryItemsUM.Content ?: TxHistoryItemsUM.Loading(state.isBalanceHidden, onExploreClick)
|
||||
}
|
||||
} else {
|
||||
_legacyUiState.update { state ->
|
||||
state as? TxHistoryUM.Content ?: TxHistoryUM.Loading(state.isBalanceHidden, onExploreClick)
|
||||
}
|
||||
_uiState.update { state ->
|
||||
state as? TxHistoryItemsUM.Content ?: TxHistoryItemsUM.Loading(state.isBalanceHidden, onExploreClick)
|
||||
}
|
||||
}
|
||||
|
||||
fun setError(onReloadClick: () -> Unit, onExploreClick: () -> Unit) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.value = TxHistoryItemsUM.Error(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
onReloadClick = onReloadClick,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
} else {
|
||||
_legacyUiState.value = TxHistoryUM.Error(
|
||||
isBalanceHidden = _legacyUiState.value.isBalanceHidden,
|
||||
onReloadClick = onReloadClick,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
_uiState.value = TxHistoryItemsUM.Error(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
onReloadClick = onReloadClick,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
|
||||
fun setEmpty(onExploreClick: () -> Unit) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.value = TxHistoryItemsUM.Empty(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
} else {
|
||||
_legacyUiState.value = TxHistoryUM.Empty(
|
||||
isBalanceHidden = _legacyUiState.value.isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
_uiState.value = TxHistoryItemsUM.Empty(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
|
||||
fun setNotSupported(onExploreClick: () -> Unit) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.value = TxHistoryItemsUM.NotSupported(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
pendingTransactions = persistentListOf(),
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
} else {
|
||||
_legacyUiState.value = TxHistoryUM.NotSupported(
|
||||
isBalanceHidden = _legacyUiState.value.isBalanceHidden,
|
||||
pendingTransactions = persistentListOf(),
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
_uiState.value = TxHistoryItemsUM.NotSupported(
|
||||
isBalanceHidden = _uiState.value.isBalanceHidden,
|
||||
pendingTransactions = persistentListOf(),
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
}
|
||||
|
||||
fun setContent(snapshot: TxHistoryItemsSnapshot, loadMore: () -> Boolean, onExploreClick: () -> Unit) {
|
||||
|
|
@ -129,27 +87,10 @@ internal class TxHistoryStateController @Inject constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
is TxHistoryItemsSnapshot.LegacyItems -> _legacyUiState.update { state ->
|
||||
if (snapshot.items.none { it is TxHistoryUM.TxHistoryItemUM.Transaction }) {
|
||||
TxHistoryUM.Empty(
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
} else if (state is TxHistoryUM.Content) {
|
||||
state.copy(items = snapshot.items)
|
||||
} else {
|
||||
TxHistoryUM.Content(
|
||||
items = snapshot.items,
|
||||
isBalanceHidden = state.isBalanceHidden,
|
||||
loadMore = loadMore,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLoadingMore(isLoadingMore: Boolean) {
|
||||
if (!designFeatureToggles.isRedesignEnabled) return
|
||||
_uiState.update { state ->
|
||||
if (state is TxHistoryItemsUM.Content && state.isLoadingMore != isLoadingMore) {
|
||||
state.copy(isLoadingMore = isLoadingMore)
|
||||
|
|
@ -160,32 +101,15 @@ internal class TxHistoryStateController @Inject constructor(
|
|||
}
|
||||
|
||||
fun updateBalanceHidden(isBalanceHidden: Boolean) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.update { state -> state.copySealed(isBalanceHidden = isBalanceHidden) }
|
||||
} else {
|
||||
_legacyUiState.update { state -> state.copySealed(isBalanceHidden = isBalanceHidden) }
|
||||
}
|
||||
_uiState.update { state -> state.copySealed(isBalanceHidden = isBalanceHidden) }
|
||||
}
|
||||
|
||||
fun updatePendingTransactions(
|
||||
pendingTxs: () -> ImmutableList<TransactionItemUM>,
|
||||
legacyPendingTxs: () -> ImmutableList<TransactionState>,
|
||||
) {
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
_uiState.update { state ->
|
||||
if (state is TxHistoryItemsUM.NotSupported) {
|
||||
state.copy(pendingTransactions = pendingTxs())
|
||||
} else {
|
||||
state
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_legacyUiState.update { state ->
|
||||
if (state is TxHistoryUM.NotSupported) {
|
||||
state.copy(pendingTransactions = legacyPendingTxs())
|
||||
} else {
|
||||
state
|
||||
}
|
||||
fun updatePendingTransactions(pendingTxs: () -> ImmutableList<TransactionItemUM>) {
|
||||
_uiState.update { state ->
|
||||
if (state is TxHistoryItemsUM.NotSupported) {
|
||||
state.copy(pendingTransactions = pendingTxs())
|
||||
} else {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
package com.tangem.features.txhistory.utils
|
||||
|
||||
import com.tangem.core.ui.utils.toDateFormatWithTodayYesterday
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.annotations.RemoveWithToggle
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]. Renders pre-redesign tx-history UI.")
|
||||
@RemoveWithToggle("APP_REDESIGN_ENABLED")
|
||||
internal class TxHistoryLegacyUiManager(
|
||||
private val state: MutableStateFlow<TxHistoryListState>,
|
||||
private val txHistoryItemConverter: TxHistoryItemToTransactionStateConverter,
|
||||
private val txHistoryUiActions: TxHistoryUiActions,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val items: Flow<ImmutableList<TxHistoryUM.TxHistoryItemUM>> = state
|
||||
.filter { state ->
|
||||
state.status !is PaginationStatus.None &&
|
||||
state.status !is PaginationStatus.InitialLoading &&
|
||||
state.status !is PaginationStatus.InitialLoadingError
|
||||
}
|
||||
.mapLatest { state ->
|
||||
state.legacyUiBatches.asSequence()
|
||||
.flatMap { it.data }
|
||||
.toImmutableList()
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
fun createOrUpdateUiBatches(
|
||||
newCurrencyBatches: List<Batch<Int, PaginationWrapper<TxInfo>>>,
|
||||
shouldClearUiBatches: Boolean,
|
||||
): List<Batch<Int, List<TxHistoryUM.TxHistoryItemUM>>> {
|
||||
val currentUiBatches = state.value.legacyUiBatches
|
||||
val batches = if (shouldClearUiBatches) mutableListOf() else currentUiBatches.toMutableList()
|
||||
|
||||
for ((key, data) in newCurrencyBatches) {
|
||||
val existingBatchIndex = batches.indexOfFirst { it.key == key }
|
||||
if (existingBatchIndex == -1) {
|
||||
val items = generateUiItems(key, data)
|
||||
batches.add(Batch(key = key, data = items))
|
||||
} else if (currentUiBatches[existingBatchIndex].data.transactionItemsSizeNotEqual(data.items)) {
|
||||
val items = generateUiItems(key, data)
|
||||
batches[existingBatchIndex] = Batch(key = key, data = items)
|
||||
}
|
||||
}
|
||||
|
||||
return batches
|
||||
}
|
||||
|
||||
private fun generateUiItems(key: Int, data: PaginationWrapper<TxInfo>): List<TxHistoryUM.TxHistoryItemUM> {
|
||||
val items = mutableListOf<TxHistoryUM.TxHistoryItemUM>()
|
||||
|
||||
if (key == 0) {
|
||||
items.add(TxHistoryUM.TxHistoryItemUM.Title(onExploreClick = txHistoryUiActions::openExplorer))
|
||||
}
|
||||
|
||||
if (data.items.isNotEmpty()) {
|
||||
val firstItem = data.items.first()
|
||||
val firstDate = firstItem.timestampInMillis.toDateFormatWithTodayYesterday()
|
||||
|
||||
items.add(
|
||||
TxHistoryUM.TxHistoryItemUM.GroupTitle(
|
||||
title = firstDate,
|
||||
itemKey = "$key-$firstDate",
|
||||
),
|
||||
)
|
||||
items.add(TxHistoryUM.TxHistoryItemUM.Transaction(txHistoryItemConverter.convert(firstItem)))
|
||||
|
||||
data.items.zipWithNext { current, next ->
|
||||
val currentDate = current.timestampInMillis.toDateFormatWithTodayYesterday()
|
||||
val nextDate = next.timestampInMillis.toDateFormatWithTodayYesterday()
|
||||
|
||||
if (currentDate != nextDate) {
|
||||
items.add(
|
||||
TxHistoryUM.TxHistoryItemUM.GroupTitle(
|
||||
title = nextDate,
|
||||
itemKey = "$key-$nextDate",
|
||||
),
|
||||
)
|
||||
}
|
||||
items.add(TxHistoryUM.TxHistoryItemUM.Transaction(txHistoryItemConverter.convert(next)))
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
private fun List<TxHistoryUM.TxHistoryItemUM>.transactionItemsSizeNotEqual(txInfos: List<TxInfo>): Boolean {
|
||||
return this.filterIsInstance<TxHistoryUM.TxHistoryItemUM.Transaction>().size != txInfos.size
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.features.txhistory.utils
|
||||
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
|
@ -9,7 +8,6 @@ import com.tangem.domain.txhistory.model.TxHistoryListConfig
|
|||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionItemUMConverter
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
|
||||
import com.tangem.features.txhistory.model.TxHistoryLookupContext
|
||||
import com.tangem.features.txhistory.state.TxHistoryItemsSnapshot
|
||||
import com.tangem.pagination.BatchAction
|
||||
|
|
@ -34,10 +32,8 @@ internal class TxHistoryListManager(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val userWalletId: UserWalletId,
|
||||
private val currency: CryptoCurrency,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val txHistoryUiActions: TxHistoryUiActions,
|
||||
private val lookupDataFlow: Flow<TxHistoryLookupContext>,
|
||||
legacyTxHistoryItemConverter: TxHistoryItemToTransactionStateConverter,
|
||||
) {
|
||||
|
||||
private val jobHolder = JobHolder()
|
||||
|
|
@ -48,17 +44,8 @@ internal class TxHistoryListManager(
|
|||
)
|
||||
private val state: MutableStateFlow<TxHistoryListState> = MutableStateFlow(TxHistoryListState())
|
||||
private val uiManager = TxHistoryUiManager(state = state)
|
||||
private val legacyUiManager = TxHistoryLegacyUiManager(
|
||||
state = state,
|
||||
txHistoryItemConverter = legacyTxHistoryItemConverter,
|
||||
txHistoryUiActions = txHistoryUiActions,
|
||||
)
|
||||
|
||||
val uiItems: Flow<TxHistoryItemsSnapshot> = if (designFeatureToggles.isRedesignEnabled) {
|
||||
uiManager.items.map(TxHistoryItemsSnapshot::Items)
|
||||
} else {
|
||||
legacyUiManager.items.map(TxHistoryItemsSnapshot::LegacyItems)
|
||||
}
|
||||
val uiItems: Flow<TxHistoryItemsSnapshot> = uiManager.items.map(TxHistoryItemsSnapshot::Items)
|
||||
val paginationStatus: Flow<PaginationStatus<*>> = state.map { it.status }.distinctUntilChanged()
|
||||
|
||||
suspend fun init() = coroutineScope {
|
||||
|
|
@ -76,24 +63,16 @@ internal class TxHistoryListManager(
|
|||
.launchIn(scope = this)
|
||||
.saveIn(autoLoadMoreJobHolder)
|
||||
|
||||
if (designFeatureToggles.isRedesignEnabled) {
|
||||
var previousLookup: TxHistoryLookupContext? = null
|
||||
combine(batchFlow.state, lookupDataFlow) { batchState, lookup -> batchState to lookup }
|
||||
.onEach { (batchState, lookup) ->
|
||||
val isLookupChanged = previousLookup != null && previousLookup != lookup
|
||||
previousLookup = lookup
|
||||
updateState(batchState, lookup, isLookupChanged)
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(scope = this)
|
||||
.saveIn(jobHolder)
|
||||
} else {
|
||||
batchFlow.state
|
||||
.onEach { batchState -> updateState(batchState, lookupContext = null, isLookupChanged = false) }
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(scope = this)
|
||||
.saveIn(jobHolder)
|
||||
}
|
||||
var previousLookup: TxHistoryLookupContext? = null
|
||||
combine(batchFlow.state, lookupDataFlow) { batchState, lookup -> batchState to lookup }
|
||||
.onEach { (batchState, lookup) ->
|
||||
val isLookupChanged = previousLookup != null && previousLookup != lookup
|
||||
previousLookup = lookup
|
||||
updateState(batchState, lookup, isLookupChanged)
|
||||
}
|
||||
.flowOn(dispatchers.default)
|
||||
.launchIn(scope = this)
|
||||
.saveIn(jobHolder)
|
||||
}
|
||||
|
||||
suspend fun startLoading() {
|
||||
|
|
@ -129,32 +108,19 @@ internal class TxHistoryListManager(
|
|||
val isInitialToPaginating = state.status is PaginationStatus.InitialLoading &&
|
||||
batchListState.status is PaginationStatus.Paginating
|
||||
val shouldClearUiBatches = isInitialToPaginating || isLookupChanged
|
||||
val isRedesignEnabled = designFeatureToggles.isRedesignEnabled
|
||||
val converter = TxHistoryItemToTransactionItemUMConverter(
|
||||
currency = currency,
|
||||
txHistoryUiActions = txHistoryUiActions,
|
||||
lookupContext = lookupContext,
|
||||
)
|
||||
state.copy(
|
||||
status = batchListState.status,
|
||||
rawBatches = batchListState.data,
|
||||
uiBatches = if (isRedesignEnabled) {
|
||||
val converter = TxHistoryItemToTransactionItemUMConverter(
|
||||
currency = currency,
|
||||
txHistoryUiActions = txHistoryUiActions,
|
||||
lookupContext = lookupContext,
|
||||
)
|
||||
uiManager.createOrUpdateUiBatches(
|
||||
newCurrencyBatches = batchListState.data,
|
||||
shouldClearUiBatches = shouldClearUiBatches,
|
||||
converter = converter,
|
||||
)
|
||||
} else {
|
||||
state.uiBatches
|
||||
},
|
||||
legacyUiBatches = if (isRedesignEnabled) {
|
||||
state.legacyUiBatches
|
||||
} else {
|
||||
legacyUiManager.createOrUpdateUiBatches(
|
||||
newCurrencyBatches = batchListState.data,
|
||||
shouldClearUiBatches = shouldClearUiBatches,
|
||||
)
|
||||
},
|
||||
uiBatches = uiManager.createOrUpdateUiBatches(
|
||||
newCurrencyBatches = batchListState.data,
|
||||
shouldClearUiBatches = shouldClearUiBatches,
|
||||
converter = converter,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.features.txhistory.utils
|
|||
import com.tangem.domain.models.network.TxInfo
|
||||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import com.tangem.pagination.Batch
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.utils.annotations.RemoveWithToggle
|
||||
|
|
@ -14,5 +13,4 @@ internal data class TxHistoryListState(
|
|||
val status: PaginationStatus<*> = PaginationStatus.None,
|
||||
val rawBatches: List<Batch<Int, PaginationWrapper<TxInfo>>> = emptyList(),
|
||||
val uiBatches: List<Batch<Int, List<TxHistoryItemsUM.TxHistoryItemUM>>> = emptyList(),
|
||||
val legacyUiBatches: List<Batch<Int, List<TxHistoryUM.TxHistoryItemUM>>> = emptyList(),
|
||||
)
|
||||
|
|
@ -2,11 +2,7 @@ package com.tangem.features.txhistory.state
|
|||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionItemUM
|
||||
import com.tangem.core.ui.components.transactions.state.TransactionState
|
||||
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
|
||||
import com.tangem.features.txhistory.entity.TxHistoryUM
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
|
@ -14,12 +10,7 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TxHistoryStateControllerTest {
|
||||
|
||||
private val controller = TxHistoryStateController(
|
||||
designFeatureToggles = mockk { every { isRedesignEnabled } returns true },
|
||||
)
|
||||
private val legacyController = TxHistoryStateController(
|
||||
designFeatureToggles = mockk { every { isRedesignEnabled } returns false },
|
||||
)
|
||||
private val controller = TxHistoryStateController()
|
||||
|
||||
@Test
|
||||
fun `GIVEN empty items snapshot WHEN setContent THEN Empty state with explorer action`() {
|
||||
|
|
@ -79,41 +70,4 @@ internal class TxHistoryStateControllerTest {
|
|||
|
||||
assertThat(controller.uiState.value).isInstanceOf(TxHistoryItemsUM.Empty::class.java)
|
||||
}
|
||||
|
||||
// region Legacy (e.g. Solana: probe reports HasTransactions but the mapped page is empty)
|
||||
|
||||
@Test
|
||||
fun `GIVEN legacy snapshot with only a title WHEN setContent THEN legacy Empty state with explorer`() {
|
||||
val onExploreClick = {}
|
||||
|
||||
legacyController.setContent(
|
||||
snapshot = TxHistoryItemsSnapshot.LegacyItems(
|
||||
persistentListOf(TxHistoryUM.TxHistoryItemUM.Title(onExploreClick = {})),
|
||||
),
|
||||
loadMore = { true },
|
||||
onExploreClick = onExploreClick,
|
||||
)
|
||||
|
||||
val state = legacyController.legacyUiState.value
|
||||
assertThat(state).isInstanceOf(TxHistoryUM.Empty::class.java)
|
||||
assertThat((state as TxHistoryUM.Empty).onExploreClick).isEqualTo(onExploreClick)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN legacy snapshot with transactions WHEN setContent THEN legacy Content state`() {
|
||||
legacyController.setContent(
|
||||
snapshot = TxHistoryItemsSnapshot.LegacyItems(
|
||||
persistentListOf(
|
||||
TxHistoryUM.TxHistoryItemUM.Title(onExploreClick = {}),
|
||||
TxHistoryUM.TxHistoryItemUM.Transaction(TransactionState.Loading("hash")),
|
||||
),
|
||||
),
|
||||
loadMore = { true },
|
||||
onExploreClick = {},
|
||||
)
|
||||
|
||||
assertThat(legacyController.legacyUiState.value).isInstanceOf(TxHistoryUM.Content::class.java)
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -12,14 +12,12 @@ import com.tangem.domain.txhistory.models.Page
|
|||
import com.tangem.domain.txhistory.models.PaginationWrapper
|
||||
import com.tangem.domain.txhistory.repository.ExpressHistoryPage
|
||||
import com.tangem.domain.txhistory.repository.TxHistoryRepositoryV2
|
||||
import com.tangem.features.txhistory.converter.TxHistoryItemToTransactionStateConverter
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
import com.tangem.pagination.PaginationStatus
|
||||
import com.tangem.pagination.fetcher.BatchFetcher
|
||||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -152,10 +150,8 @@ internal class TxHistoryListManagerTest {
|
|||
dispatchers = repository.dispatchers,
|
||||
userWalletId = userWalletId,
|
||||
currency = currency,
|
||||
designFeatureToggles = mockk { every { isRedesignEnabled } returns false },
|
||||
txHistoryUiActions = mockk(relaxed = true),
|
||||
lookupDataFlow = emptyFlow(),
|
||||
legacyTxHistoryItemConverter = mockk<TxHistoryItemToTransactionStateConverter>(relaxed = true),
|
||||
)
|
||||
|
||||
private fun page(itemCount: Int, isLast: Boolean): Page2Spec = Page2Spec(itemCount = itemCount, isLast = isLast)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue