Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-22 18:49:27 +05:00
parent 55641cab14
commit cd34a586bb
16 changed files with 496 additions and 22 deletions

View file

@ -0,0 +1,10 @@
package com.tangem.core.ui.components.transactions.intents
interface TxHistoryClickIntents {
fun onBuyClick()
fun onReloadClick()
fun onExploreClick()
}

View file

@ -31,9 +31,8 @@ data class Network(
*
* @property value The string representation of the network ID.
*/
// FIXME: Remove serialization [REDACTED_JIRA]
@JvmInline
value class ID(val value: String) : Serializable {
value class ID(val value: String) {
init {
require(value.isNotBlank()) { "Network ID must not be blank" }
@ -49,7 +48,8 @@ data class Network(
*
* @property name The human-readable name of the standard type.
*/
sealed class StandardType {
// FIXME: Remove serialization [REDACTED_JIRA]
sealed class StandardType : Serializable {
abstract val name: String
/** Represents the ERC20 token standard, common on the Ethereum network. */

View file

@ -14,19 +14,22 @@ android {
dependencies {
/** AndroidX */
implementation(deps.androidx.activity.compose)
implementation(deps.androidx.paging.runtime)
/** Compose */
implementation(deps.compose.material)
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.compose.coil)
implementation(deps.compose.foundation)
implementation(deps.compose.material)
implementation(deps.compose.material3)
implementation(deps.compose.navigation)
implementation(deps.compose.navigation.hilt)
implementation(deps.compose.paging)
implementation(deps.compose.ui)
implementation(deps.compose.ui.tooling)
implementation(deps.compose.accompanist.systemUiController)
implementation(deps.compose.coil)
implementation(deps.arrow.core)
implementation(deps.jodatime)
implementation(deps.kotlin.immutable.collections)
implementation(deps.tangem.blockchain)
implementation(deps.tangem.card.core)

View file

@ -14,4 +14,8 @@ internal class DefaultTokenDetailsRouter(
override fun popBackStack() {
navigationStateHolder.navigate(NavigationAction.PopBackTo())
}
override fun openUrl(url: String) {
navigationStateHolder.navigate(NavigationAction.OpenUrl(url = url))
}
}

View file

@ -4,5 +4,9 @@ import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
internal interface InnerTokenDetailsRouter : TokenDetailsRouter {
/** Pop back stack */
fun popBackStack()
/** Open website by [url] */
fun openUrl(url: String)
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
@ -10,6 +11,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfo
import com.tangem.features.tokendetails.impl.R
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.MutableStateFlow
internal object TokenDetailsPreviewData {
@ -82,5 +84,10 @@ internal object TokenDetailsPreviewData {
tokenInfoBlockState = tokenInfoBlockState,
tokenBalanceBlockState = balanceLoading,
marketPriceBlockState = marketPriceLoading,
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions {},
),
),
)
}

View file

@ -1,10 +1,12 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
data class TokenDetailsState(
val topAppBarConfig: TokenDetailsTopAppBarConfig,
val tokenInfoBlockState: TokenInfoBlockState,
val tokenBalanceBlockState: TokenDetailsBalanceBlockState,
val marketPriceBlockState: MarketPriceBlockState,
val txHistoryState: TxHistoryState,
)

View file

@ -1,6 +1,7 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockState
@ -11,6 +12,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.T
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.converter.Converter
import kotlinx.coroutines.flow.MutableStateFlow
internal class TokenDetailsSkeletonStateConverter(
private val clickIntents: TokenDetailsClickIntents,
@ -39,6 +41,11 @@ internal class TokenDetailsSkeletonStateConverter(
TokenDetailsPreviewData.disabledActionButtons,
),
marketPriceBlockState = MarketPriceBlockState.Loading(value.cryptoCurrency.name),
txHistoryState = TxHistoryState.Content(
contentItems = MutableStateFlow(
value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick),
),
),
)
}

View file

@ -1,18 +1,27 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import androidx.paging.PagingData
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import kotlinx.coroutines.flow.Flow
internal class TokenDetailsStateFactory(
private val currentStateProvider: Provider<TokenDetailsState>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: TokenDetailsClickIntents,
symbol: String,
decimals: Int,
) {
private val skeletonStateConverter by lazy {
@ -26,6 +35,19 @@ internal class TokenDetailsStateFactory(
)
}
private val loadingTransactionsStateConverter by lazy {
TokenDetailsLoadingTxHistoryConverter(currentStateProvider = currentStateProvider, clickIntents = clickIntents)
}
private val loadedTxHistoryConverter by lazy {
TokenDetailsLoadedTxHistoryConverter(
currentStateProvider = currentStateProvider,
clickIntents = clickIntents,
symbol = symbol,
decimals = decimals,
)
}
fun getInitialState(cryptoCurrency: CryptoCurrency): TokenDetailsState {
return skeletonStateConverter.convert(
TokenDetailsSkeletonStateConverter.SkeletonModel(cryptoCurrency = cryptoCurrency),
@ -37,4 +59,14 @@ internal class TokenDetailsStateFactory(
): TokenDetailsState {
return tokenDetailsLoadedBalanceConverter.convert(cryptoCurrencyEither)
}
fun getLoadingTxHistoryState(itemsCountEither: Either<TxHistoryStateError, Int>): TokenDetailsState {
return loadingTransactionsStateConverter.convert(value = itemsCountEither)
}
fun getLoadedTxHistoryState(
txHistoryEither: Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>,
): TokenDetailsState {
return loadedTxHistoryConverter.convert(txHistoryEither)
}
}

View file

@ -0,0 +1,49 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory
import androidx.paging.PagingData
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.core.ui.components.transactions.intents.TxHistoryClickIntents
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.utils.converter.Converter
import kotlinx.coroutines.flow.Flow
internal class TokenDetailsLoadedTxHistoryConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
private val clickIntents: TxHistoryClickIntents,
symbol: String,
decimals: Int,
) : Converter<Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>, TokenDetailsState> {
private val txHistoryItemFlowConverter by lazy {
TokenDetailsTxHistoryItemFlowConverter(
currentStateProvider = currentStateProvider,
symbol = symbol,
decimals = decimals,
clickIntents = clickIntents,
)
}
override fun convert(value: Either<TxHistoryListError, Flow<PagingData<TxHistoryItem>>>): TokenDetailsState {
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
}
private fun convertError(error: TxHistoryListError): TokenDetailsState {
return currentStateProvider().copy(
txHistoryState = when (error) {
is TxHistoryListError.DataError -> {
TxHistoryState.Error(onReloadClick = clickIntents::onReloadClick)
}
},
)
}
private fun convert(items: Flow<PagingData<TxHistoryItem>>): TokenDetailsState {
return currentStateProvider().copy(
txHistoryState = txHistoryItemFlowConverter.convert(value = items),
)
}
}

View file

@ -0,0 +1,59 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory
import androidx.paging.PagingData
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.core.ui.components.transactions.intents.TxHistoryClickIntents
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.utils.converter.Converter
import kotlinx.coroutines.flow.update
internal class TokenDetailsLoadingTxHistoryConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
private val clickIntents: TxHistoryClickIntents,
) : Converter<Either<TxHistoryStateError, Int>, TokenDetailsState> {
override fun convert(value: Either<TxHistoryStateError, Int>): TokenDetailsState {
return value.fold(ifLeft = ::convertError, ifRight = ::convert)
}
private fun convertError(error: TxHistoryStateError): TokenDetailsState {
return currentStateProvider().copy(
txHistoryState = when (error) {
is TxHistoryStateError.EmptyTxHistories -> {
TxHistoryState.Empty(onBuyClick = clickIntents::onBuyClick)
}
is TxHistoryStateError.DataError -> {
TxHistoryState.Error(onReloadClick = clickIntents::onReloadClick)
}
is TxHistoryStateError.TxHistoryNotImplemented -> {
TxHistoryState.NotSupported(onExploreClick = clickIntents::onExploreClick)
}
},
)
}
private fun convert(value: Int): TokenDetailsState {
val state = currentStateProvider()
val txHistoryContent = state.txHistoryState as TxHistoryState.Content
txHistoryContent.contentItems.update {
PagingData.from(
data = listOf(TxHistoryState.TxHistoryItemState.Title(onExploreClick = clickIntents::onExploreClick)) +
MutableList(
size = value,
init = {
TxHistoryState.TxHistoryItemState.Transaction(
state = TransactionState.Loading(it.toString()),
)
},
),
)
}
return state
}
}

View file

@ -0,0 +1,223 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory
import android.text.format.DateUtils
import androidx.paging.*
import com.tangem.common.Provider
import com.tangem.core.ui.components.transactions.intents.TxHistoryClickIntents
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
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.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.*
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format.DateTimeFormatterBuilder
import java.math.BigDecimal
import java.util.Locale
internal class TokenDetailsTxHistoryItemFlowConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
private val symbol: String,
private val decimals: Int,
private val clickIntents: TxHistoryClickIntents,
) : Converter<Flow<PagingData<TxHistoryItem>>, TxHistoryState> {
/** 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>>): TxHistoryState {
val txHistoryContent = currentStateProvider().txHistoryState as TxHistoryState.Content
// FIXME: TxHistoryRepository should send loading transactions
// [REDACTED_JIRA]
value
.onEach { txHistoryStatePagingData ->
txHistoryContent.contentItems.update {
txHistoryStatePagingData
.map<TxHistoryItem, TxHistoryItemState> { item ->
// [createTransactionState] returns timestamp without formatting
TxHistoryItemState.Transaction(state = createTransactionState(item))
}
.insertHeaderItem(
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
item = TxHistoryItemState.Title(clickIntents::onExploreClick),
)
.insertGroupTitle() // method uses the raw timestamp
.formatTransactionsTimestamp() // method formats the timestamp
}
}
.launchIn(CoroutineScope(Dispatchers.IO))
return txHistoryContent
}
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)
}
is TxHistoryItem.TransactionDirection.Outgoing -> {
createOutgoingTransferTransaction(item, direction)
}
}
}
}
}
private fun createIncomingTransferTransaction(
item: TxHistoryItem,
direction: TxHistoryItem.TransactionDirection.Incoming,
): TransactionState {
return when (item.status) {
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Receive(
txHash = item.txHash,
address = direction.from.toBriefAddressFormat(),
amount = item.amount.toCryptoCurrencyFormat(),
timestamp = item.getRawTimestamp(),
)
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Receiving(
txHash = item.txHash,
address = direction.from.toBriefAddressFormat(),
amount = item.amount.toCryptoCurrencyFormat(),
timestamp = item.getRawTimestamp(),
)
}
}
private fun createOutgoingTransferTransaction(
item: TxHistoryItem,
direction: TxHistoryItem.TransactionDirection.Outgoing,
): TransactionState {
return when (item.status) {
TxHistoryItem.TxStatus.Confirmed -> TransactionState.Send(
txHash = item.txHash,
address = direction.to.toBriefAddressFormat(),
amount = item.amount.toCryptoCurrencyFormat(),
timestamp = item.getRawTimestamp(),
)
TxHistoryItem.TxStatus.Unconfirmed -> TransactionState.Sending(
txHash = item.txHash,
address = direction.to.toBriefAddressFormat(),
amount = item.amount.toCryptoCurrencyFormat(),
timestamp = item.getRawTimestamp(),
)
}
}
private fun BigDecimal.toCryptoCurrencyFormat(): String {
return toFormattedCurrencyString(currency = symbol, decimals = 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
) {
val txContent = txHistoryItemState.state as TransactionState.Content
txHistoryItemState.copy(
state = txContent.copySealed(
timestamp = txContent.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.timestampInMillis.toString()
private fun TxHistoryItemState?.getTimestamp(): Long? {
return if (this is TxHistoryItemState.Transaction && this.state is TransactionState.Content) {
val txContent = this.state as TransactionState.Content
requireNotNull(txContent.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

@ -1,14 +1,17 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.paging.compose.collectAsLazyPagingItems
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.components.transactions.txHistoryItems
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
@ -22,16 +25,36 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) },
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
Column(
val txHistoryItems = if (state.txHistoryState is TxHistoryState.Content) {
state.txHistoryState.contentItems.collectAsLazyPagingItems()
} else {
null
}
val betweenItemsPadding = TangemTheme.dimens.spacing12
val horizontalPadding = TangemTheme.dimens.spacing16
val itemModifier = Modifier
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
LazyColumn(
modifier = Modifier
.padding(paddingValues = scaffoldPaddings)
.padding(horizontal = TangemTheme.dimens.spacing16)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
TokenInfoBlock(state = state.tokenInfoBlockState)
TokenDetailsBalanceBlock(state = state.tokenBalanceBlockState)
MarketPriceBlock(state = state.marketPriceBlockState)
item {
TokenInfoBlock(
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing4)
.padding(horizontal = horizontalPadding),
state = state.tokenInfoBlockState,
)
}
item { TokenDetailsBalanceBlock(modifier = itemModifier, state = state.tokenBalanceBlockState) }
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,
content = { MarketPriceBlock(modifier = itemModifier, state = state.marketPriceBlockState) },
)
txHistoryItems(state = state.txHistoryState, txHistoryItems = txHistoryItems)
}
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels
interface TokenDetailsClickIntents {
import com.tangem.core.ui.components.transactions.intents.TxHistoryClickIntents
interface TokenDetailsClickIntents : TxHistoryClickIntents {
fun onBackClick()

View file

@ -4,13 +4,17 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import androidx.paging.cachedIn
import arrow.core.getOrElse
import com.tangem.common.Provider
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.GetCurrencyUseCase
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
@ -21,15 +25,20 @@ import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.properties.Delegates
@Suppress("LongParameterList")
@HiltViewModel
internal class TokenDetailsViewModel @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val getCurrencyUseCase: GetCurrencyUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
@ -45,6 +54,8 @@ internal class TokenDetailsViewModel @Inject constructor(
currentStateProvider = Provider { uiState },
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
clickIntents = this,
symbol = cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
)
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
private set
@ -63,6 +74,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private fun updateContent(selectedWallet: UserWallet, refresh: Boolean) {
updateMarketPrice(selectedWallet = selectedWallet, refresh = refresh)
updateTxHistory()
}
private fun updateMarketPrice(selectedWallet: UserWallet, refresh: Boolean) {
@ -74,6 +86,28 @@ internal class TokenDetailsViewModel @Inject constructor(
.saveIn(marketPriceJobHolder)
}
private fun updateTxHistory() {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
networkId = cryptoCurrency.network.id,
derivationPath = cryptoCurrency.derivationPath,
)
uiState = stateFactory.getLoadingTxHistoryState(itemsCountEither = txHistoryItemsCountEither)
txHistoryItemsCountEither.onRight {
uiState = stateFactory.getLoadedTxHistoryState(
txHistoryEither = txHistoryItemsUseCase(
networkId = cryptoCurrency.network.id,
derivationPath = cryptoCurrency.derivationPath,
).map {
it.cachedIn(viewModelScope)
},
)
}
}
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
@ -93,4 +127,24 @@ internal class TokenDetailsViewModel @Inject constructor(
override fun onMoreClick() {
TODO("Not yet implemented")
}
override fun onBuyClick() {
// TODO: [REDACTED_JIRA]
}
override fun onReloadClick() {
updateTxHistory()
}
override fun onExploreClick() {
viewModelScope.launch {
val wallet = getWallet()
router.openUrl(
url = getExploreUrlUseCase(
userWalletId = wallet.walletId,
networkId = cryptoCurrency.network.id,
),
)
}
}
}

View file

@ -1,8 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.core.ui.components.transactions.intents.TxHistoryClickIntents
import com.tangem.domain.tokens.models.CryptoCurrency
internal interface WalletClickIntents {
internal interface WalletClickIntents : TxHistoryClickIntents {
fun onBackClick()
@ -28,12 +29,6 @@ internal interface WalletClickIntents {
fun onOrganizeTokensClick()
fun onBuyClick()
fun onReloadClick()
fun onExploreClick()
fun onUnlockWalletClick()
fun onUnlockWalletNotificationClick()