Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-11 12:16:43 +03:00
commit d58e4ee2a5
9 changed files with 121 additions and 32 deletions

View file

@ -10,6 +10,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig
import com.tangem.features.tokendetails.impl.R
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
@ -71,6 +72,11 @@ internal object TokenDetailsPreviewData {
private val marketPriceLoading = MarketPriceBlockState.Loading(currencyName = "USDT")
private val pullToRefreshConfig = TokenDetailsPullToRefreshConfig(
isRefreshing = false,
onRefresh = {},
)
val tokenDetailsState = TokenDetailsState(
topAppBarConfig = tokenDetailsTopAppBarConfig,
tokenInfoBlockState = tokenInfoBlockState,
@ -83,5 +89,6 @@ internal object TokenDetailsPreviewData {
),
dialogConfig = null,
pendingTxs = persistentListOf(),
pullToRefreshConfig = pullToRefreshConfig,
)
}

View file

@ -4,6 +4,7 @@ import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig
import kotlinx.collections.immutable.PersistentList
internal data class TokenDetailsState(
@ -14,4 +15,5 @@ internal data class TokenDetailsState(
val pendingTxs: PersistentList<TransactionState>,
val txHistoryState: TxHistoryState,
val dialogConfig: TokenDetailsDialogConfig?,
val pullToRefreshConfig: TokenDetailsPullToRefreshConfig,
)

View file

@ -0,0 +1,3 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.components
data class TokenDetailsPullToRefreshConfig(val isRefreshing: Boolean, val onRefresh: () -> Unit)

View file

@ -0,0 +1,19 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
import com.tangem.common.Provider
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
import com.tangem.utils.converter.Converter
internal class TokenDetailsRefreshStateConverter(
private val currentStateProvider: Provider<TokenDetailsState>,
) : Converter<Boolean, TokenDetailsState> {
override fun convert(value: Boolean): TokenDetailsState {
val state = currentStateProvider()
return state.createPullToRefresh(value)
}
private fun TokenDetailsState.createPullToRefresh(isRefreshing: Boolean): TokenDetailsState {
return copy(pullToRefreshConfig = pullToRefreshConfig.copy(isRefreshing = isRefreshing))
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.models.CryptoCurrency
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSkeletonStateConverter.SkeletonModel
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.tokendetails.impl.R
@ -49,6 +50,7 @@ internal class TokenDetailsSkeletonStateConverter(
),
),
dialogConfig = null,
pullToRefreshConfig = createPullToRefresh(),
)
}
@ -72,5 +74,10 @@ internal class TokenDetailsSkeletonStateConverter(
)
}
private fun createPullToRefresh(): TokenDetailsPullToRefreshConfig = TokenDetailsPullToRefreshConfig(
isRefreshing = false,
onRefresh = clickIntents::onRefreshSwipe,
)
data class SkeletonModel(val cryptoCurrency: CryptoCurrency)
}

View file

@ -59,6 +59,12 @@ internal class TokenDetailsStateFactory(
)
}
private val refreshStateConverter by lazy {
TokenDetailsRefreshStateConverter(
currentStateProvider = currentStateProvider,
)
}
fun getInitialState(cryptoCurrency: CryptoCurrency): TokenDetailsState {
return skeletonStateConverter.convert(
TokenDetailsSkeletonStateConverter.SkeletonModel(cryptoCurrency = cryptoCurrency),
@ -117,4 +123,12 @@ internal class TokenDetailsStateFactory(
),
)
}
fun getRefreshingState(): TokenDetailsState {
return refreshStateConverter.convert(true)
}
fun getRefreshedState(): TokenDetailsState {
return refreshStateConverter.convert(false)
}
}

View file

@ -1,11 +1,12 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import androidx.compose.foundation.background
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.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -29,12 +30,18 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
import kotlinx.collections.immutable.PersistentList
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun TokenDetailsScreen(state: TokenDetailsState) {
Scaffold(
topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) },
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
val pullRefreshState = rememberPullRefreshState(
refreshing = state.pullToRefreshConfig.isRefreshing,
onRefresh = state.pullToRefreshConfig.onRefresh,
)
val txHistoryItems = if (state.txHistoryState is TxHistoryState.Content) {
state.txHistoryState.contentItems.collectAsLazyPagingItems()
} else {
@ -45,34 +52,46 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
val itemModifier = Modifier
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
LazyColumn(
Box(
modifier = Modifier
.padding(paddingValues = scaffoldPaddings)
.fillMaxSize(),
.padding(scaffoldPaddings)
.pullRefresh(pullRefreshState),
) {
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) },
)
if (state.txHistoryState is TxHistoryState.NotSupported && state.pendingTxs.isNotEmpty()) {
LazyColumn(
modifier = Modifier
.fillMaxSize(),
) {
item {
PendingTxsBlock(
pendingTxs = state.pendingTxs,
modifier = itemModifier,
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) },
)
if (state.txHistoryState is TxHistoryState.NotSupported && state.pendingTxs.isNotEmpty()) {
item {
PendingTxsBlock(
pendingTxs = state.pendingTxs,
modifier = itemModifier,
)
}
}
txHistoryItems(state = state.txHistoryState, txHistoryItems = txHistoryItems)
}
txHistoryItems(state = state.txHistoryState, txHistoryItems = txHistoryItems)
PullRefreshIndicator(
modifier = Modifier.align(Alignment.TopCenter),
refreshing = state.pullToRefreshConfig.isRefreshing,
state = pullRefreshState,
)
}
TokenDetailsDialogs(state = state)

View file

@ -19,4 +19,6 @@ interface TokenDetailsClickIntents : TxHistoryClickIntents {
fun onHideClick()
fun onHideConfirmed()
fun onRefreshSwipe()
}

View file

@ -10,10 +10,7 @@ import com.tangem.common.Provider
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetNetworkCoinStatusUseCase
import com.tangem.domain.tokens.RemoveCurrencyUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.models.CryptoCurrency
@ -44,6 +41,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
@ -60,6 +58,7 @@ internal class TokenDetailsViewModel @Inject constructor(
var router by Delegates.notNull<InnerTokenDetailsRouter>()
private val marketPriceJobHolder = JobHolder()
private val refreshStateJobHolder = JobHolder()
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var wallet by Delegates.notNull<UserWallet>()
@ -116,14 +115,16 @@ internal class TokenDetailsViewModel @Inject constructor(
.saveIn(marketPriceJobHolder)
}
private fun updateTxHistory() {
private fun updateTxHistory(refresh: Boolean = false) {
viewModelScope.launch(dispatchers.io) {
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
networkId = cryptoCurrency.network.id,
derivationPath = cryptoCurrency.derivationPath,
)
uiState = stateFactory.getLoadingTxHistoryState(itemsCountEither = txHistoryItemsCountEither)
if (!refresh) {
uiState = stateFactory.getLoadingTxHistoryState(itemsCountEither = txHistoryItemsCountEither)
}
txHistoryItemsCountEither.onRight {
uiState = stateFactory.getLoadedTxHistoryState(
@ -258,4 +259,19 @@ internal class TokenDetailsViewModel @Inject constructor(
)
}
}
override fun onRefreshSwipe() {
uiState = stateFactory.getRefreshingState()
viewModelScope.launch(dispatchers.io) {
fetchCurrencyStatusUseCase.invoke(
userWalletId = wallet.walletId,
id = cryptoCurrency.id,
refresh = true,
)
updateTxHistory(refresh = true)
uiState = stateFactory.getRefreshedState()
}.saveIn(refreshStateJobHolder)
}
}