Updated on 2026-08-14
This commit is contained in:
parent
3d887a5574
commit
06b13f2227
15 changed files with 491 additions and 243 deletions
|
|
@ -18,7 +18,25 @@ sealed interface TransactionState {
|
|||
open val address: String,
|
||||
open val amount: String,
|
||||
open val timestamp: String,
|
||||
) : TransactionState
|
||||
) : TransactionState {
|
||||
|
||||
fun copySealed(
|
||||
address: String = this.address,
|
||||
amount: String = this.amount,
|
||||
timestamp: String = this.timestamp,
|
||||
): Content {
|
||||
return when (this) {
|
||||
is Approved -> copy(address, amount, timestamp)
|
||||
is Receive -> copy(address, amount, timestamp)
|
||||
is Send -> copy(address, amount, timestamp)
|
||||
is Swapped -> copy(address, amount, timestamp)
|
||||
is Approving -> copy(address, amount, timestamp)
|
||||
is Receiving -> copy(address, amount, timestamp)
|
||||
is Sending -> copy(address, amount, timestamp)
|
||||
is Swapping -> copy(address, amount, timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Content state for processed transaction
|
||||
|
|
|
|||
|
|
@ -6,10 +6,16 @@ plugins {
|
|||
|
||||
dependencies {
|
||||
|
||||
/** DI */
|
||||
// region DI
|
||||
implementation(deps.hilt.core)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
/** Coroutines */
|
||||
// region Coroutines
|
||||
implementation(deps.kotlin.coroutines)
|
||||
// endregion
|
||||
|
||||
// region Time dependencies
|
||||
implementation(deps.jodatime)
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.utils
|
||||
|
||||
/**
|
||||
* Convert address to brief format. Example, 33BddS...ga2B.
|
||||
* If [this.length] is less than a sum of [startCharsCount] and [endCharsCount], return [this].
|
||||
*/
|
||||
fun String.toBriefAddressFormat(startCharsCount: Int = 6, endCharsCount: Int = 4): String {
|
||||
return if (startCharsCount + endCharsCount < length) {
|
||||
substring(startIndex = 0, endIndex = startCharsCount) + "..." +
|
||||
substring(startIndex = length - endCharsCount, endIndex = length)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.utils.extensions
|
||||
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.LocalDate
|
||||
|
||||
fun DateTime.isToday(): Boolean = LocalDate.now().equals(LocalDate(this))
|
||||
|
||||
fun DateTime.isYesterday(): Boolean = LocalDate.now().minusDays(1).equals(LocalDate(this))
|
||||
|
|
@ -7,7 +7,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem1 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689930746,
|
||||
timestamp = System.currentTimeMillis(),
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
@ -16,7 +16,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem2 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689844346,
|
||||
timestamp = 1689844346000,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address2"),
|
||||
status = TxHistoryItem.TxStatus.Unconfirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
@ -25,7 +25,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem3 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689757946,
|
||||
timestamp = 1689757946000,
|
||||
direction = TxHistoryItem.TransactionDirection.Outgoing("address3"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
@ -34,7 +34,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem4 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689671546,
|
||||
timestamp = 1689671546000,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address4"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
@ -43,7 +43,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem5 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689585146,
|
||||
timestamp = 1689585146000,
|
||||
direction = TxHistoryItem.TransactionDirection.Outgoing("address5"),
|
||||
status = TxHistoryItem.TxStatus.Unconfirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
@ -52,7 +52,7 @@ internal object MockTxHistoryItems {
|
|||
|
||||
private val txHistoryItem6 = TxHistoryItem(
|
||||
txHash = "noster",
|
||||
timestamp = 1689585146,
|
||||
timestamp = 1689585146000,
|
||||
direction = TxHistoryItem.TransactionDirection.Incoming("address6"),
|
||||
status = TxHistoryItem.TxStatus.Confirmed,
|
||||
type = TxHistoryItem.TransactionType.Transfer,
|
||||
|
|
|
|||
|
|
@ -28,10 +28,10 @@ dependencies {
|
|||
|
||||
/** Other libraries */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.map
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.txhistory.error.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/**
|
||||
* Converter from loaded tx history to [WalletTxHistoryState]
|
||||
*
|
||||
* @property currentStateProvider current state provider
|
||||
* @property currentCardTypeResolverProvider current card type resolver provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletLoadedTxHistoryConverter(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
||||
) : Converter<Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>, WalletStateHolder> {
|
||||
|
||||
override fun convert(value: Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>): WalletStateHolder {
|
||||
return value.fold(ifLeft = { convertError() }, ifRight = ::convert)
|
||||
}
|
||||
|
||||
private fun convertError(): WalletStateHolder {
|
||||
val state = currentStateProvider()
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = state.onBackClick,
|
||||
topBarConfig = state.topBarConfig,
|
||||
walletsListConfig = state.walletsListConfig,
|
||||
pullToRefreshConfig = state.pullToRefreshConfig,
|
||||
notifications = state.notifications,
|
||||
bottomSheet = state.bottomSheet,
|
||||
buttons = persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
.map(WalletManageButton::config)
|
||||
.toImmutableList(),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(
|
||||
currencyName = currentCardTypeResolverProvider().getBlockchain().currency,
|
||||
),
|
||||
// TODO: [REDACTED_JIRA]
|
||||
txHistoryState = WalletTxHistoryState.Error(onReloadClick = {}),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convert(items: Flow<PagingData<TxHistoryItem>>): WalletStateHolder {
|
||||
val state = currentStateProvider()
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = state.onBackClick,
|
||||
topBarConfig = state.topBarConfig,
|
||||
walletsListConfig = state.walletsListConfig,
|
||||
pullToRefreshConfig = state.pullToRefreshConfig,
|
||||
notifications = state.notifications,
|
||||
bottomSheet = state.bottomSheet,
|
||||
buttons = persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
.map(WalletManageButton::config)
|
||||
.toImmutableList(),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(
|
||||
currencyName = currentCardTypeResolverProvider().getBlockchain().currency,
|
||||
),
|
||||
txHistoryState = createContentTxHistory(items),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createContentTxHistory(items: Flow<PagingData<TxHistoryItem>>): WalletTxHistoryState {
|
||||
return WalletTxHistoryState.Content(
|
||||
items = items.map { pagingData ->
|
||||
pagingData.map { item ->
|
||||
TxHistoryItemState.Transaction(
|
||||
state = when (val direction = item.direction) {
|
||||
is TxHistoryItem.TransactionDirection.Incoming -> {
|
||||
when (item.status) {
|
||||
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Receive(
|
||||
address = direction.from,
|
||||
amount = item.amount.toPlainString(),
|
||||
timestamp = item.timestamp.toString(),
|
||||
)
|
||||
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Receiving(
|
||||
address = direction.from,
|
||||
amount = item.amount.toPlainString(),
|
||||
timestamp = item.timestamp.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
is TxHistoryItem.TransactionDirection.Outgoing -> {
|
||||
when (item.status) {
|
||||
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Send(
|
||||
address = direction.to,
|
||||
amount = item.amount.toPlainString(),
|
||||
timestamp = item.timestamp.toString(),
|
||||
)
|
||||
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Sending(
|
||||
address = direction.to,
|
||||
amount = item.amount.toPlainString(),
|
||||
timestamp = item.timestamp.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.txhistory.error.TxHistoryStateError
|
||||
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* Converter from loading tx history to [WalletTxHistoryState]
|
||||
*
|
||||
* @property currentStateProvider current state provider
|
||||
* @property currentCardTypeResolverProvider current card type resolver provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletLoadingTxHistoryConverter(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
||||
) : Converter<Either<TxHistoryStateError, Int>, WalletStateHolder> {
|
||||
|
||||
override fun convert(value: Either<TxHistoryStateError, Int>): WalletStateHolder {
|
||||
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
|
||||
}
|
||||
|
||||
private fun convert(value: Int): WalletStateHolder {
|
||||
val state = currentStateProvider()
|
||||
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = state.onBackClick,
|
||||
topBarConfig = state.topBarConfig,
|
||||
walletsListConfig = state.walletsListConfig,
|
||||
pullToRefreshConfig = state.pullToRefreshConfig,
|
||||
notifications = state.notifications,
|
||||
bottomSheet = state.bottomSheet,
|
||||
buttons = persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
.map(WalletManageButton::config)
|
||||
.toImmutableList(),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(
|
||||
currencyName = currentCardTypeResolverProvider().getBlockchain().currency,
|
||||
),
|
||||
txHistoryState = createLoadingTxHistory(itemCount = value),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createLoadingTxHistory(itemCount: Int): WalletTxHistoryState {
|
||||
return WalletTxHistoryState.Content(
|
||||
items = flowOf(
|
||||
value = PagingData.from(
|
||||
data = buildList(
|
||||
capacity = itemCount,
|
||||
builderAction = {
|
||||
add(
|
||||
element = WalletTxHistoryState.TxHistoryItemState.Transaction(
|
||||
state = TransactionState.Loading,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertError(error: TxHistoryStateError): WalletStateHolder {
|
||||
val state = currentStateProvider()
|
||||
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = state.onBackClick,
|
||||
topBarConfig = state.topBarConfig,
|
||||
walletsListConfig = state.walletsListConfig,
|
||||
pullToRefreshConfig = state.pullToRefreshConfig,
|
||||
notifications = state.notifications,
|
||||
bottomSheet = state.bottomSheet,
|
||||
buttons = WalletPreviewData.singleWalletScreenState.buttons,
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(
|
||||
currencyName = currentCardTypeResolverProvider().getBlockchain().currency,
|
||||
),
|
||||
// TODO: [REDACTED_JIRA]
|
||||
txHistoryState = when (error) {
|
||||
is TxHistoryStateError.EmptyTxHistories -> WalletTxHistoryState.Empty(onBuyClick = {})
|
||||
is TxHistoryStateError.DataError -> WalletTxHistoryState.Error(onReloadClick = {})
|
||||
is TxHistoryStateError.TxHistoryNotImplemented -> WalletTxHistoryState.NotSupported(onExploreClick = {})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletBottomSheetConf
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.WalletNotification
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletLoadedTokensListConverter.LoadedTokensListModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadedTxHistoryConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory.WalletLoadingTxHistoryConverter
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -45,6 +47,7 @@ internal class WalletStateFactory(
|
|||
WalletLoadingTxHistoryConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
currentCardTypeResolverProvider = currentCardTypeResolverProvider,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ internal class WalletStateFactory(
|
|||
WalletLoadedTxHistoryConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
currentCardTypeResolverProvider = currentCardTypeResolverProvider,
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.txhistory.error.TxHistoryListError
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* Converter from loaded tx history to [WalletTxHistoryState]
|
||||
*
|
||||
* @property currentStateProvider current state provider
|
||||
* @property currentCardTypeResolverProvider current card type resolver provider
|
||||
* @property clickIntents screen click intents
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletLoadedTxHistoryConverter(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
) : Converter<Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>, WalletStateHolder> {
|
||||
|
||||
private val walletTxHistoryItemFlowConverter by lazy {
|
||||
WalletTxHistoryItemFlowConverter(
|
||||
blockchain = currentCardTypeResolverProvider().getBlockchain(),
|
||||
clickIntents = clickIntents,
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(value: Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>): WalletStateHolder {
|
||||
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
|
||||
}
|
||||
|
||||
private fun convertError(error: TxHistoryListError): WalletStateHolder {
|
||||
return currentStateProvider().copySingleCurrencyContent(
|
||||
txHistoryState = when (error) {
|
||||
is TxHistoryListError.DataError -> {
|
||||
WalletTxHistoryState.Error(onReloadClick = clickIntents::onReloadClick)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun convert(items: Flow<PagingData<TxHistoryItem>>): WalletStateHolder {
|
||||
return currentStateProvider().copySingleCurrencyContent(
|
||||
txHistoryState = walletTxHistoryItemFlowConverter.convert(value = items),
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletStateHolder.copySingleCurrencyContent(
|
||||
txHistoryState: WalletTxHistoryState,
|
||||
): WalletStateHolder.SingleCurrencyContent {
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = onBackClick,
|
||||
topBarConfig = topBarConfig,
|
||||
walletsListConfig = walletsListConfig,
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
notifications = notifications,
|
||||
bottomSheet = bottomSheet,
|
||||
buttons = getButtons(),
|
||||
marketPriceBlockState = getLoadingMarketPriceBlockState(),
|
||||
txHistoryState = txHistoryState,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
private fun getButtons(): ImmutableList<ActionButtonConfig> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
.map(WalletManageButton::config)
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
private fun getLoadingMarketPriceBlockState(): MarketPriceBlockState {
|
||||
return MarketPriceBlockState.Loading(currencyName = currentCardTypeResolverProvider().getBlockchain().currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
|
||||
|
||||
import androidx.paging.PagingData
|
||||
import arrow.core.Either
|
||||
import com.tangem.common.Provider
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.txhistory.error.TxHistoryStateError
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletManageButton
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
/**
|
||||
* Converter from loading tx history to [WalletTxHistoryState]
|
||||
*
|
||||
* @property currentStateProvider current state provider
|
||||
* @property currentCardTypeResolverProvider current card type resolver provider
|
||||
* @property clickIntents screen click intents
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletLoadingTxHistoryConverter(
|
||||
private val currentStateProvider: Provider<WalletStateHolder>,
|
||||
private val currentCardTypeResolverProvider: Provider<CardTypesResolver>,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
) : Converter<Either<TxHistoryStateError, Int>, WalletStateHolder> {
|
||||
|
||||
override fun convert(value: Either<TxHistoryStateError, Int>): WalletStateHolder {
|
||||
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
|
||||
}
|
||||
|
||||
private fun convert(value: Int): WalletStateHolder {
|
||||
return currentStateProvider().copySingleCurrencyContent(
|
||||
txHistoryState = WalletTxHistoryState.Content(
|
||||
items = flowOf(
|
||||
value = PagingData.from(
|
||||
data = buildList(capacity = value) {
|
||||
add(WalletTxHistoryState.TxHistoryItemState.Transaction(state = TransactionState.Loading))
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertError(error: TxHistoryStateError): WalletStateHolder {
|
||||
return currentStateProvider().copySingleCurrencyContent(
|
||||
txHistoryState = when (error) {
|
||||
is TxHistoryStateError.EmptyTxHistories -> {
|
||||
WalletTxHistoryState.Empty(onBuyClick = clickIntents::onBuyClick)
|
||||
}
|
||||
is TxHistoryStateError.DataError -> {
|
||||
WalletTxHistoryState.Error(onReloadClick = clickIntents::onReloadClick)
|
||||
}
|
||||
is TxHistoryStateError.TxHistoryNotImplemented -> {
|
||||
WalletTxHistoryState.NotSupported(onExploreClick = clickIntents::onExploreClick)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun WalletStateHolder.copySingleCurrencyContent(
|
||||
txHistoryState: WalletTxHistoryState,
|
||||
): WalletStateHolder.SingleCurrencyContent {
|
||||
return WalletStateHolder.SingleCurrencyContent(
|
||||
onBackClick = onBackClick,
|
||||
topBarConfig = topBarConfig,
|
||||
walletsListConfig = walletsListConfig,
|
||||
pullToRefreshConfig = pullToRefreshConfig,
|
||||
notifications = notifications,
|
||||
bottomSheet = bottomSheet,
|
||||
buttons = getButtons(),
|
||||
marketPriceBlockState = getLoadingMarketPriceBlockState(),
|
||||
txHistoryState = txHistoryState,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
private fun getButtons(): ImmutableList<ActionButtonConfig> {
|
||||
return persistentListOf(
|
||||
WalletManageButton.Buy(onClick = {}),
|
||||
WalletManageButton.Send(onClick = {}),
|
||||
WalletManageButton.Receive(onClick = {}),
|
||||
WalletManageButton.Exchange(onClick = {}),
|
||||
WalletManageButton.CopyAddress(onClick = {}),
|
||||
)
|
||||
.map(WalletManageButton::config)
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
private fun getLoadingMarketPriceBlockState(): MarketPriceBlockState {
|
||||
return MarketPriceBlockState.Loading(currencyName = currentCardTypeResolverProvider().getBlockchain().currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.paging.*
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.ui.components.transactions.TransactionState
|
||||
import com.tangem.domain.txhistory.model.TxHistoryItem
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.content.WalletTxHistoryState.TxHistoryItemState
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.isToday
|
||||
import com.tangem.utils.extensions.isYesterday
|
||||
import com.tangem.utils.toBriefAddressFormat
|
||||
import com.tangem.utils.toFormattedCurrencyString
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import org.joda.time.format.DateTimeFormatterBuilder
|
||||
import java.math.BigDecimal
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Convert from [Flow] of [TxHistoryItem] to [WalletTxHistoryState]
|
||||
*
|
||||
* @property blockchain blockchain of transactions history
|
||||
* @property clickIntents screen click intents
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class WalletTxHistoryItemFlowConverter(
|
||||
private val blockchain: Blockchain,
|
||||
private val clickIntents: WalletClickIntents,
|
||||
) : Converter<Flow<PagingData<TxHistoryItem>>, WalletTxHistoryState> {
|
||||
|
||||
/** Example, 2 Aug, 2023 */
|
||||
private val dateFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendDayOfMonth(1)
|
||||
.appendLiteral(' ')
|
||||
.appendMonthOfYearShortText()
|
||||
.appendLiteral(", ")
|
||||
.appendYear(4, 4)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
/** Example, 13:35 */
|
||||
private val timeFormatter by lazy {
|
||||
DateTimeFormatterBuilder()
|
||||
.appendHourOfDay(1)
|
||||
.appendLiteral(':')
|
||||
.appendMinuteOfHour(2)
|
||||
.toFormatter()
|
||||
.withLocale(Locale.getDefault())
|
||||
}
|
||||
|
||||
override fun convert(value: Flow<PagingData<TxHistoryItem>>): WalletTxHistoryState {
|
||||
return WalletTxHistoryState.Content(
|
||||
items = value
|
||||
.map { pagingData ->
|
||||
pagingData
|
||||
.map<TxHistoryItem, TxHistoryItemState> { item ->
|
||||
// [createTransactionState] returns timestamp without formatting
|
||||
TxHistoryItemState.Transaction(state = createTransactionState(item))
|
||||
}
|
||||
.insertHeaderItem(
|
||||
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
|
||||
item = TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick),
|
||||
)
|
||||
.insertGroupTitle() // method uses the raw timestamp
|
||||
.formatTransactionsTimestamp() // method formats the timestamp
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTransactionState(item: TxHistoryItem): TransactionState {
|
||||
return when (item.type) {
|
||||
TxHistoryItem.TransactionType.Transfer -> {
|
||||
when (val direction = item.direction) {
|
||||
is TxHistoryItem.TransactionDirection.Incoming -> {
|
||||
createIncomingTransferTransaction(item, direction, blockchain)
|
||||
}
|
||||
is TxHistoryItem.TransactionDirection.Outgoing -> {
|
||||
createOutgoingTransferTransaction(item, direction, blockchain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createIncomingTransferTransaction(
|
||||
item: TxHistoryItem,
|
||||
direction: TxHistoryItem.TransactionDirection.Incoming,
|
||||
blockchain: Blockchain,
|
||||
): TransactionState {
|
||||
return when (item.status) {
|
||||
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Receive(
|
||||
address = direction.from.toBriefAddressFormat(),
|
||||
amount = item.amount.toCryptoCurrencyFormat(blockchain = blockchain),
|
||||
timestamp = item.getRawTimestamp(),
|
||||
)
|
||||
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Receiving(
|
||||
address = direction.from.toBriefAddressFormat(),
|
||||
amount = item.amount.toCryptoCurrencyFormat(blockchain = blockchain),
|
||||
timestamp = item.getRawTimestamp(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createOutgoingTransferTransaction(
|
||||
item: TxHistoryItem,
|
||||
direction: TxHistoryItem.TransactionDirection.Outgoing,
|
||||
blockchain: Blockchain,
|
||||
): TransactionState {
|
||||
return when (item.status) {
|
||||
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Send(
|
||||
address = direction.to.toBriefAddressFormat(),
|
||||
amount = item.amount.toCryptoCurrencyFormat(blockchain = blockchain),
|
||||
timestamp = item.getRawTimestamp(),
|
||||
)
|
||||
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Sending(
|
||||
address = direction.to.toBriefAddressFormat(),
|
||||
amount = item.amount.toCryptoCurrencyFormat(blockchain = blockchain),
|
||||
timestamp = item.getRawTimestamp(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal.toCryptoCurrencyFormat(blockchain: Blockchain): String {
|
||||
return toFormattedCurrencyString(currency = blockchain.currency, decimals = blockchain.decimals())
|
||||
}
|
||||
|
||||
private fun PagingData<TxHistoryItemState>.insertGroupTitle(): PagingData<TxHistoryItemState> {
|
||||
return insertSeparators(terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE) { before, after ->
|
||||
// Use raw timestamp to get date
|
||||
|
||||
// If [afterDate] is the first transaction in the flow, add the group title
|
||||
val afterDate = after.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
|
||||
if (before is TxHistoryItemState.Title) {
|
||||
return@insertSeparators TxHistoryItemState.GroupTitle(afterDate)
|
||||
}
|
||||
|
||||
/*
|
||||
* If [beforeDate] is not equals to [afterDate], then [afterDate] is first transaction in
|
||||
* the new group
|
||||
*/
|
||||
val beforeDate = before.getTimestamp()?.toDateFormat() ?: return@insertSeparators null
|
||||
return@insertSeparators if (beforeDate != afterDate) {
|
||||
TxHistoryItemState.GroupTitle(afterDate)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the [PagingData] to format the [TxHistoryItemState] timestamp
|
||||
*/
|
||||
private fun PagingData<TxHistoryItemState>.formatTransactionsTimestamp(): PagingData<TxHistoryItemState> {
|
||||
return map { txHistoryItemState ->
|
||||
if (txHistoryItemState is TxHistoryItemState.Transaction &&
|
||||
txHistoryItemState.state is TransactionState.Content
|
||||
) {
|
||||
txHistoryItemState.copy(
|
||||
state = txHistoryItemState.state.copySealed(
|
||||
timestamp = txHistoryItemState.state.timestamp.toTimeFormat(),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
txHistoryItemState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timestamp without formatting.
|
||||
* It's life hack that help us to add transaction's group title to flow.
|
||||
*
|
||||
* @see [convert]
|
||||
*/
|
||||
private fun TxHistoryItem.getRawTimestamp() = this.timestamp.toString()
|
||||
|
||||
private fun TxHistoryItemState?.getTimestamp(): Long? {
|
||||
return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) {
|
||||
requireNotNull(this.state.timestamp.toLongOrNull()) { "Timestamp must be Long type" }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If [this] timestamp is today or yesterday, returns relative date,
|
||||
* otherwise returns formatting date by [dateFormatter]
|
||||
*/
|
||||
private fun Long.toDateFormat(): String {
|
||||
val localDate = DateTime(this, DateTimeZone.getDefault())
|
||||
return if (localDate.isToday() || localDate.isYesterday()) {
|
||||
DateUtils.getRelativeTimeSpanString(
|
||||
this,
|
||||
DateTime.now().millis,
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE,
|
||||
).toString()
|
||||
} else {
|
||||
dateFormatter.print(localDate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toTimeFormat(): String {
|
||||
return timeFormatter.print(
|
||||
DateTime(this.toLong(), DateTimeZone.getDefault()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,4 +25,10 @@ internal interface WalletClickIntents {
|
|||
fun onRefreshSwipe()
|
||||
|
||||
fun onOrganizeTokensClick()
|
||||
|
||||
fun onBuyClick()
|
||||
|
||||
fun onReloadClick()
|
||||
|
||||
fun onExploreClick()
|
||||
}
|
||||
|
|
@ -249,4 +249,16 @@ internal class WalletViewModel @Inject constructor(
|
|||
|
||||
router.openOrganizeTokensScreen(walletId)
|
||||
}
|
||||
|
||||
override fun onBuyClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onReloadClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
|
||||
override fun onExploreClick() {
|
||||
// TODO: [REDACTED_JIRA]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue