From 06b13f2227ae1db3986faffc3810f32cdd8a231a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 31 Jul 2023 18:59:35 +0800 Subject: [PATCH] Updated on 2026-08-14 --- .../transactions/TransactionState.kt | 20 +- core/utils/build.gradle.kts | 10 +- .../utils/CryptoAddressFormatExtensions.kt | 14 ++ ...s.kt => CryptoCurrencyFormatExtensions.kt} | 0 .../com/tangem/utils/extensions/DateTime.kt | 8 + .../data/txhistory/mock/MockTxHistoryItems.kt | 12 +- features/wallet/impl/build.gradle.kts | 2 +- .../factory/WalletLoadedTxHistoryConverter.kt | 130 ----------- .../WalletLoadingTxHistoryConverter.kt | 103 --------- .../state/factory/WalletStateFactory.kt | 4 + .../WalletLoadedTxHistoryConverter.kt | 95 ++++++++ .../WalletLoadingTxHistoryConverter.kt | 102 +++++++++ .../WalletTxHistoryItemFlowConverter.kt | 216 ++++++++++++++++++ .../wallet/viewmodels/WalletClickIntents.kt | 6 + .../wallet/viewmodels/WalletViewModel.kt | 12 + 15 files changed, 491 insertions(+), 243 deletions(-) create mode 100644 core/utils/src/main/java/com/tangem/utils/CryptoAddressFormatExtensions.kt rename core/utils/src/main/java/com/tangem/utils/{FormatExtensions.kt => CryptoCurrencyFormatExtensions.kt} (100%) create mode 100644 core/utils/src/main/java/com/tangem/utils/extensions/DateTime.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt delete mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt create mode 100644 features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt index f0502a4a21..b5b6adaaba 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionState.kt @@ -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 diff --git a/core/utils/build.gradle.kts b/core/utils/build.gradle.kts index 1e8960ce77..bf7c86b763 100644 --- a/core/utils/build.gradle.kts +++ b/core/utils/build.gradle.kts @@ -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 } \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/CryptoAddressFormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/CryptoAddressFormatExtensions.kt new file mode 100644 index 0000000000..f2b5b09fce --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/CryptoAddressFormatExtensions.kt @@ -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 + } +} \ No newline at end of file diff --git a/core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt b/core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt similarity index 100% rename from core/utils/src/main/java/com/tangem/utils/FormatExtensions.kt rename to core/utils/src/main/java/com/tangem/utils/CryptoCurrencyFormatExtensions.kt diff --git a/core/utils/src/main/java/com/tangem/utils/extensions/DateTime.kt b/core/utils/src/main/java/com/tangem/utils/extensions/DateTime.kt new file mode 100644 index 0000000000..3fad6d57d7 --- /dev/null +++ b/core/utils/src/main/java/com/tangem/utils/extensions/DateTime.kt @@ -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)) \ No newline at end of file diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/mock/MockTxHistoryItems.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/mock/MockTxHistoryItems.kt index 4f7cbec344..583fbaa5ee 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/mock/MockTxHistoryItems.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/mock/MockTxHistoryItems.kt @@ -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, diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 25b400217f..606d847a63 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -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) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt deleted file mode 100644 index 1d8fa010d8..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadedTxHistoryConverter.kt +++ /dev/null @@ -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, - private val currentCardTypeResolverProvider: Provider, -) : Converter>>, WalletStateHolder> { - - override fun convert(value: Either>>): 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>): 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>): 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(), - ) - } - } - }, - ) - } - }, - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt deleted file mode 100644 index b24b26dd5a..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletLoadingTxHistoryConverter.kt +++ /dev/null @@ -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, - private val currentCardTypeResolverProvider: Provider, -) : Converter, WalletStateHolder> { - - override fun convert(value: Either): 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 = {}) - }, - ) - } -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt index 24be88c35b..453c182e49 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt @@ -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, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt new file mode 100644 index 0000000000..5ed89bf2e9 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadedTxHistoryConverter.kt @@ -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, + private val currentCardTypeResolverProvider: Provider, + private val clickIntents: WalletClickIntents, +) : Converter>>, WalletStateHolder> { + + private val walletTxHistoryItemFlowConverter by lazy { + WalletTxHistoryItemFlowConverter( + blockchain = currentCardTypeResolverProvider().getBlockchain(), + clickIntents = clickIntents, + ) + } + + override fun convert(value: Either>>): 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>): 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 { + 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) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt new file mode 100644 index 0000000000..da2936afcc --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletLoadingTxHistoryConverter.kt @@ -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, + private val currentCardTypeResolverProvider: Provider, + private val clickIntents: WalletClickIntents, +) : Converter, WalletStateHolder> { + + override fun convert(value: Either): 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 { + 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) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt new file mode 100644 index 0000000000..2fe01c3e0e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/txhistory/WalletTxHistoryItemFlowConverter.kt @@ -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>, 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>): WalletTxHistoryState { + return WalletTxHistoryState.Content( + items = value + .map { pagingData -> + pagingData + .map { 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.insertGroupTitle(): PagingData { + 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.formatTransactionsTimestamp(): PagingData { + 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()), + ) + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt index c92a9707a8..503b697419 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletClickIntents.kt @@ -25,4 +25,10 @@ internal interface WalletClickIntents { fun onRefreshSwipe() fun onOrganizeTokensClick() + + fun onBuyClick() + + fun onReloadClick() + + fun onExploreClick() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 7dbc2e6af9..af2d2daddd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -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] + } } \ No newline at end of file