Updated on 2026-08-14

This commit is contained in:
Tangem 2023-07-31 18:59:35 +08:00
parent 3d887a5574
commit 06b13f2227
15 changed files with 491 additions and 243 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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))

View file

@ -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,

View file

@ -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)

View file

@ -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(),
)
}
}
},
)
}
},
)
}
}

View file

@ -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 = {})
},
)
}
}

View file

@ -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,
)
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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()),
)
}
}

View file

@ -25,4 +25,10 @@ internal interface WalletClickIntents {
fun onRefreshSwipe()
fun onOrganizeTokensClick()
fun onBuyClick()
fun onReloadClick()
fun onExploreClick()
}

View file

@ -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]
}
}