Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-16 21:37:49 +08:00
parent c444f76c76
commit 4ad8c5bfc1
17 changed files with 318 additions and 124 deletions

View file

@ -1,76 +1,67 @@
package com.tangem.core.ui.components.transactions.state
import androidx.paging.PagingData
import androidx.paging.TerminalSeparatorType
import androidx.paging.insertHeaderItem
import com.tangem.core.ui.components.wallet.WalletLockedContentState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
/**
* Wallet transaction history state
*/
/** Wallet transaction history state */
sealed interface TxHistoryState {
/**
* Wallet transaction history state with content
* Wallet transaction history state with content. Items contains a required [TxHistoryItemState.Title].
*
* @property items content items
* @property contentItems content items
*/
sealed class ContentState(open val items: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState
sealed class ContentState(private val contentItems: Flow<PagingData<TxHistoryItemState>>) : TxHistoryState {
/** Lambda be invoke when explore button was clicked */
abstract val onExploreClick: () -> Unit
/** Content items with [TxHistoryItemState.Title] */
val items: Flow<PagingData<TxHistoryItemState>>
get() {
return contentItems.map {
it.insertHeaderItem(
terminalSeparatorType = TerminalSeparatorType.SOURCE_COMPLETE,
item = TxHistoryItemState.Title(onExploreClick = onExploreClick),
)
}
}
}
/**
* Loading state
*
* @property onExploreClick lambda be invoke when explore button was clicked
* @property transactions loading transactions
*/
data class Loading(val onExploreClick: () -> Unit) : ContentState(
items = flowOf(
PagingData.from(
listOf(
TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
),
),
),
)
/**
* Wallet transaction history state with loading transactions
*
* @property itemsCount count of loading transactions
*/
data class ContentWithLoadingItems(val itemsCount: Int) : ContentState(
items = flowOf(
value = PagingData.from(
data = buildList(capacity = itemsCount) {
add(TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)))
},
),
),
)
data class Loading(
override val onExploreClick: () -> Unit,
val transactions: Flow<PagingData<TxHistoryItemState>> = getDefaultLoadingTransactions(),
) : ContentState(transactions)
/**
* Wallet transaction history state with content
*
* @property items content items
* @property onExploreClick lambda be invoke when explore button was clicked
* @property contentItems content items
*/
data class Content(override val items: Flow<PagingData<TxHistoryItemState>>) : ContentState(items)
data class Content(
override val onExploreClick: () -> Unit,
val contentItems: Flow<PagingData<TxHistoryItemState>>,
) : ContentState(contentItems)
/**
* Locked state
*
* @property onExploreClick lambda be invoke when explore button was clicked
*/
data class Locked(val onExploreClick: () -> Unit) :
ContentState(
items = flowOf(
PagingData.from(
listOf(
TxHistoryItemState.Title(onExploreClick = onExploreClick),
TxHistoryItemState.Transaction(state = TransactionState.Loading(txHash = LOADING_TX_HASH)),
),
),
),
),
data class Locked(override val onExploreClick: () -> Unit) :
ContentState(contentItems = getDefaultLoadingTransactions()),
WalletLockedContentState
/**
@ -121,5 +112,17 @@ sealed interface TxHistoryState {
private companion object {
const val LOADING_TX_HASH = "LOADING_TX_HASH"
private fun getDefaultLoadingTransactions(): Flow<PagingData<TxHistoryItemState>> {
return flowOf(
value = PagingData.from(
data = listOf(
element = TxHistoryItemState.Transaction(
state = TransactionState.Loading(txHash = LOADING_TX_HASH),
),
),
),
)
}
}
}

View file

@ -351,10 +351,10 @@ internal object WalletPreviewData {
),
),
txHistoryState = TxHistoryState.Content(
flowOf(
onExploreClick = {},
contentItems = flowOf(
PagingData.from(
listOf(
TxHistoryState.TxHistoryItemState.Title(onExploreClick = {}),
TxHistoryState.TxHistoryItemState.GroupTitle("Today"),
TxHistoryState.TxHistoryItemState.Transaction(
TransactionState.Sending(

View file

@ -75,12 +75,13 @@ internal sealed interface TokenItemState {
) : TokenItemState
/** Token options state */
@Immutable
sealed interface TokenOptionsState {
/**
* Visible token options state
*
* @property fiatAmount fiat amount of token
* @property fiatAmount fiat amount of token
* @property priceChange value of price changing
*/
data class Visible(val fiatAmount: String, val priceChange: PriceChangeConfig) : TokenOptionsState

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.feature.wallet.presentation.wallet.state.components.*
@ -11,14 +12,12 @@ import kotlinx.collections.immutable.persistentListOf
*
[REDACTED_AUTHOR]
*/
@Immutable
internal sealed class WalletSingleCurrencyState : WalletState.ContentState() {
/** Manage buttons */
abstract val buttons: ImmutableList<WalletManageButton>
/** Market price block state */
abstract val marketPriceBlockState: MarketPriceBlockState?
/** Transactions history state */
abstract val txHistoryState: TxHistoryState
@ -30,8 +29,8 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() {
override val notifications: ImmutableList<WalletNotification>,
override val bottomSheetConfig: WalletBottomSheetConfig?,
override val buttons: ImmutableList<WalletManageButton>,
override val marketPriceBlockState: MarketPriceBlockState,
override val txHistoryState: TxHistoryState,
val marketPriceBlockState: MarketPriceBlockState,
) : WalletSingleCurrencyState()
data class Locked(
@ -61,8 +60,6 @@ internal sealed class WalletSingleCurrencyState : WalletState.ContentState() {
),
)
override val marketPriceBlockState = null
override val txHistoryState: TxHistoryState = TxHistoryState.Locked(onExploreClick)
}
}

View file

@ -34,14 +34,26 @@ internal sealed class WalletState {
/**
* Util function that allow to make a copy
*
* @param walletsListConfig wallets list config
* @param walletsListConfig wallets list config
* @param pullToRefreshConfig pull to refresh config
*/
fun copySealed(walletsListConfig: WalletsListConfig = this.walletsListConfig): ContentState {
fun copySealed(
walletsListConfig: WalletsListConfig = this.walletsListConfig,
pullToRefreshConfig: WalletPullToRefreshConfig = this.pullToRefreshConfig,
): ContentState {
return when (this) {
is WalletMultiCurrencyState.Content -> copy(walletsListConfig = walletsListConfig)
is WalletMultiCurrencyState.Locked -> copy(walletsListConfig = walletsListConfig)
is WalletSingleCurrencyState.Content -> copy(walletsListConfig = walletsListConfig)
is WalletSingleCurrencyState.Locked -> copy(walletsListConfig = walletsListConfig)
is WalletMultiCurrencyState.Content -> {
copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig)
}
is WalletMultiCurrencyState.Locked -> {
copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig)
}
is WalletSingleCurrencyState.Content -> {
copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig)
}
is WalletSingleCurrencyState.Locked -> {
copy(walletsListConfig = walletsListConfig, pullToRefreshConfig = pullToRefreshConfig)
}
}
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.components
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.wallet.impl.R
@ -11,6 +12,7 @@ import com.tangem.feature.wallet.impl.R
*
[REDACTED_AUTHOR]
*/
@Immutable
sealed class WalletManageButton(val config: ActionButtonConfig) {
/** Lambda be invoked when manage button is clicked */

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.components
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.notifications.NotificationState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.WrappedList
@ -14,6 +15,7 @@ import com.tangem.feature.wallet.impl.R
[REDACTED_AUTHOR]
*/
// TODO: Finalize notification strings [REDACTED_JIRA]
@Immutable
sealed class WalletNotification(open val state: NotificationState) {
/** Clickable notification */

View file

@ -28,14 +28,17 @@ internal sealed class WalletTokensListState {
open val onOrganizeTokensClick: (() -> Unit)?,
) : WalletTokensListState()
/** Loading content state */
object Loading : ContentState(
items = persistentListOf(
/**
* Loading content state
*
* @property items content items
*/
data class Loading(
override val items: ImmutableList<TokensListItemState.Token> = persistentListOf(
TokensListItemState.Token(state = TokenItemState.Loading(id = FIRST_LOADING_TOKEN_ID)),
TokensListItemState.Token(state = TokenItemState.Loading(id = SECOND_LOADING_TOKEN_ID)),
),
onOrganizeTokensClick = null,
)
) : ContentState(items = items, onOrganizeTokensClick = null)
/**
* Content state

View file

@ -0,0 +1,128 @@
package com.tangem.feature.wallet.presentation.wallet.state.factory
import androidx.paging.PagingData
import androidx.paging.map
import com.tangem.common.Provider
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.core.ui.components.transactions.state.TxHistoryState.TxHistoryItemState
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletPullToRefreshConfig
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState.TokensListItemState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
internal class WalletRefreshStateConverter(
private val currentStateProvider: Provider<WalletState>,
private val clickIntents: WalletClickIntents,
) : Converter<Unit, WalletState> {
override fun convert(value: Unit): WalletState {
return when (val state = currentStateProvider()) {
is WalletMultiCurrencyState.Content -> state.getRefreshState()
is WalletSingleCurrencyState.Content -> state.getRefreshState()
else -> state
}
}
private fun WalletMultiCurrencyState.Content.getRefreshState(): WalletMultiCurrencyState.Content {
return copy(
walletsListConfig = getWalletsListConfig(),
pullToRefreshConfig = getPullToRefreshConfig(),
tokensListState = getTokenListState(),
)
}
private fun WalletSingleCurrencyState.Content.getRefreshState(): WalletSingleCurrencyState.Content {
return copy(
// TODO: [REDACTED_JIRA]
walletsListConfig = getWalletsListConfig(additionalInfo = ""),
pullToRefreshConfig = getPullToRefreshConfig(),
txHistoryState = getTxHistoryState(),
marketPriceBlockState = MarketPriceBlockState.Loading(currencyName = marketPriceBlockState.currencyName),
)
}
private fun WalletState.ContentState.getWalletsListConfig(additionalInfo: String? = null): WalletsListConfig {
val selectedWallet = walletsListConfig.wallets[walletsListConfig.selectedWalletIndex]
return walletsListConfig.copy(
wallets = walletsListConfig.wallets
.toPersistentList()
.set(
index = walletsListConfig.selectedWalletIndex,
element = WalletCardState.Loading(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = additionalInfo ?: selectedWallet.additionalInfo,
imageResId = selectedWallet.imageResId,
),
),
)
}
private fun WalletState.ContentState.getPullToRefreshConfig(): WalletPullToRefreshConfig {
return pullToRefreshConfig.copy(isRefreshing = true)
}
private fun WalletMultiCurrencyState.Content.getTokenListState(): WalletTokensListState {
return when (tokensListState) {
is WalletTokensListState.Content -> {
WalletTokensListState.Loading(
items = tokensListState.items
.filterIsInstance<TokensListItemState.Token>()
.map {
TokensListItemState.Token(state = TokenItemState.Loading(id = it.state.id))
}
.toImmutableList(),
)
}
is WalletTokensListState.Empty -> WalletTokensListState.Loading()
is WalletTokensListState.Loading,
is WalletTokensListState.Locked,
-> tokensListState
}
}
private fun WalletSingleCurrencyState.Content.getTxHistoryState(): TxHistoryState {
return when (txHistoryState) {
is TxHistoryState.Content -> {
TxHistoryState.Loading(
onExploreClick = clickIntents::onExploreClick,
transactions = txHistoryState.contentItems
.filterIsInstance<PagingData<TxHistoryItemState.Transaction>>()
.mapPagingData { transaction ->
transaction.copy(
state = TransactionState.Loading(txHash = transaction.state.txHash),
)
},
)
}
is TxHistoryState.Empty,
is TxHistoryState.Error,
is TxHistoryState.NotSupported,
-> TxHistoryState.Loading(onExploreClick = clickIntents::onExploreClick)
is TxHistoryState.Locked,
is TxHistoryState.Loading,
-> txHistoryState
}
}
private fun Flow<PagingData<TxHistoryItemState.Transaction>>.mapPagingData(
transform: (TxHistoryItemState.Transaction) -> TxHistoryItemState,
): Flow<PagingData<TxHistoryItemState>> {
return map { it.map(transform) }
}
}

View file

@ -14,6 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyS
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.state.factory.WalletSingleCurrencyLoadedBalanceConverter.SingleCurrencyLoadedBalanceModel
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
@ -22,21 +23,32 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
private val currentStateProvider: Provider<WalletState>,
private val cardTypeResolverProvider: Provider<CardTypesResolver>,
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<Either<CurrencyError, CryptoCurrencyStatus>, WalletSingleCurrencyState.Content> {
) : Converter<SingleCurrencyLoadedBalanceModel, WalletSingleCurrencyState.Content> {
override fun convert(value: Either<CurrencyError, CryptoCurrencyStatus>): WalletSingleCurrencyState.Content {
return value.fold(ifLeft = { convertError() }, ifRight = ::convert)
override fun convert(value: SingleCurrencyLoadedBalanceModel): WalletSingleCurrencyState.Content {
return value.cryptoCurrencyEither.fold(
ifLeft = { convertError() },
ifRight = { convertContent(it, value.isRefreshing) },
)
}
private fun convertError(): WalletSingleCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content)
}
private fun convert(status: CryptoCurrencyStatus): WalletSingleCurrencyState.Content {
private fun convertContent(
status: CryptoCurrencyStatus,
isRefreshing: Boolean,
): WalletSingleCurrencyState.Content {
val state = requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content)
val currencyName = state.marketPriceBlockState.currencyName
return state.copy(
walletsListConfig = getUpdatedSelectedWallet(status = status.value, state = state),
pullToRefreshConfig = if (isRefreshing) {
state.pullToRefreshConfig.copy(isRefreshing = status.value is CryptoCurrencyStatus.Loading)
} else {
state.pullToRefreshConfig
},
marketPriceBlockState = getMarketPriceState(status = status.value, currencyName = currencyName),
)
}
@ -153,4 +165,9 @@ internal class WalletSingleCurrencyLoadedBalanceConverter(
fiatCurrencySymbol = appCurrency.symbol,
)
}
data class SingleCurrencyLoadedBalanceModel(
val cryptoCurrencyEither: Either<CurrencyError, CryptoCurrencyStatus>,
val isRefreshing: Boolean,
)
}

View file

@ -47,7 +47,7 @@ internal class WalletSkeletonStateConverter(
topBarConfig = createTopBarConfig(),
walletsListConfig = createWalletsListConfig(value),
pullToRefreshConfig = createPullToRefreshConfig(),
tokensListState = WalletTokensListState.Loading,
tokensListState = WalletTokensListState.Loading(),
notifications = persistentListOf(),
bottomSheetConfig = null,
)

View file

@ -77,6 +77,10 @@ internal class WalletStateFactory(
)
}
private val refreshStateConverter by lazy {
WalletRefreshStateConverter(currentStateProvider = currentStateProvider, clickIntents = clickIntents)
}
fun getInitialState(): WalletState = WalletState.Initial(onBackClick = clickIntents::onBackClick)
fun getSkeletonState(wallets: List<UserWallet>, selectedWalletIndex: Int): WalletState {
@ -105,9 +109,7 @@ internal class WalletStateFactory(
}
}
fun getStateAfterContentRefreshing(): WalletState {
return currentStateProvider()
}
fun getStateAfterContentRefreshing(): WalletState = refreshStateConverter.convert(Unit)
fun getStateWithOpenBottomSheet(content: WalletBottomSheetConfig.BottomSheetContentConfig): WalletState {
return when (val state = currentStateProvider() as WalletState.ContentState) {
@ -205,7 +207,13 @@ internal class WalletStateFactory(
fun getSingleCurrencyLoadedBalanceState(
cryptoCurrencyEither: Either<CurrencyError, CryptoCurrencyStatus>,
isRefreshing: Boolean,
): WalletState {
return singleCurrencyLoadedBalanceConverter.convert(cryptoCurrencyEither)
return singleCurrencyLoadedBalanceConverter.convert(
value = WalletSingleCurrencyLoadedBalanceConverter.SingleCurrencyLoadedBalanceModel(
cryptoCurrencyEither = cryptoCurrencyEither,
isRefreshing = isRefreshing,
),
)
}
}

View file

@ -1,13 +1,16 @@
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.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.feature.wallet.presentation.wallet.state.WalletSingleCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
import com.tangem.utils.converter.Converter
import kotlinx.coroutines.flow.flow
/**
* Converter from loading tx history state to [WalletSingleCurrencyState.Content]
@ -44,7 +47,17 @@ internal class WalletLoadingTxHistoryConverter(
private fun convert(value: Int): WalletSingleCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content).copy(
txHistoryState = TxHistoryState.ContentWithLoadingItems(itemsCount = value),
txHistoryState = TxHistoryState.Loading(
onExploreClick = clickIntents::onExploreClick,
transactions = flow {
PagingData.from(
data = MutableList(
size = value,
init = { TransactionState.Loading(it.toString()) },
),
)
},
),
)
}
}

View file

@ -1,7 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state.factory.txhistory
import android.text.format.DateUtils
import androidx.paging.*
import androidx.paging.PagingData
import androidx.paging.TerminalSeparatorType
import androidx.paging.insertSeparators
import androidx.paging.map
import com.tangem.blockchain.common.Blockchain
import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
@ -58,17 +61,14 @@ internal class WalletTxHistoryItemFlowConverter(
override fun convert(value: Flow<PagingData<TxHistoryItem>>): TxHistoryState {
return TxHistoryState.Content(
items = value
onExploreClick = clickIntents::onExploreClick,
contentItems = 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
},

View file

@ -4,10 +4,8 @@ import com.tangem.common.Provider
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletsListConfig
import com.tangem.feature.wallet.presentation.wallet.utils.TokenListToWalletStateConverter.TokensListModel
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletClickIntents
@ -35,7 +33,7 @@ internal class TokenListToWalletStateConverter(
return state.copy(
walletsListConfig = state.updateSelectedWallet(fiatBalance = value.tokenList.totalFiatBalance),
pullToRefreshConfig = if (value.isRefreshing) {
state.pullToRefreshConfig.copy(isRefreshing = state.getRefreshingStatus())
state.pullToRefreshConfig.copy(isRefreshing = getRefreshingStatus(tokenList = value.tokenList))
} else {
state.pullToRefreshConfig
},
@ -60,17 +58,8 @@ internal class TokenListToWalletStateConverter(
)
}
private fun WalletState.getRefreshingStatus(): Boolean {
return if (this is WalletMultiCurrencyState.Content &&
this.tokensListState is WalletTokensListState.ContentState
) {
tokensListState.items.any { tokensListItemState ->
tokensListItemState is WalletTokensListState.TokensListItemState.Token &&
tokensListItemState.state is TokenItemState.Loading
}
} else {
false
}
private fun getRefreshingStatus(tokenList: TokenList): Boolean {
return tokenList.totalFiatBalance is TokenList.FiatBalance.Loading
}
data class TokensListModel(val tokenList: TokenList, val isRefreshing: Boolean)

View file

@ -1,15 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.viewmodels
import com.tangem.common.Provider
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.NetworkGroup
import com.tangem.domain.tokens.model.TokenList
import com.tangem.feature.wallet.presentation.common.state.TokenItemState
import com.tangem.feature.wallet.presentation.wallet.state.WalletMultiCurrencyState
import com.tangem.feature.wallet.presentation.wallet.state.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletTokensListState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -26,7 +21,6 @@ import kotlinx.coroutines.flow.flow
[REDACTED_AUTHOR]
*/
internal class WalletNotificationsListFactory(
private val currentStateProvider: Provider<WalletState>,
private val wasCardScannedCallback: suspend (String) -> Boolean,
private val isUserAlreadyRateAppCallback: suspend () -> Boolean,
private val isDemoCardCallback: (String) -> Boolean,
@ -56,7 +50,7 @@ internal class WalletNotificationsListFactory(
add(element = WalletNotification.DemoCard)
}
if (hasUnreachableNetworks()) {
if (hasUnreachableNetworks(tokenList)) {
add(element = WalletNotification.UnreachableNetworks)
}
@ -112,15 +106,22 @@ internal class WalletNotificationsListFactory(
}
}
private fun hasUnreachableNetworks(): Boolean {
val isUnreachableState = { item: WalletTokensListState.TokensListItemState ->
(item as? WalletTokensListState.TokensListItemState.Token)?.state is TokenItemState.Unreachable
}
return currentStateProvider().let { state ->
state is WalletMultiCurrencyState.Content &&
state.tokensListState is WalletTokensListState.ContentState &&
state.tokensListState.items.any(isUnreachableState)
private fun hasUnreachableNetworks(tokenList: TokenList?): Boolean {
return when (tokenList) {
is TokenList.GroupedByNetwork -> {
tokenList.groups
.flatMap(NetworkGroup::currencies)
.map(CryptoCurrencyStatus::value)
.any { it is CryptoCurrencyStatus.Unreachable }
}
is TokenList.Ungrouped -> {
tokenList.currencies
.map(CryptoCurrencyStatus::value)
.any { it is CryptoCurrencyStatus.Unreachable }
}
is TokenList.NotInitialized,
null,
-> false
}
}

View file

@ -43,6 +43,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -83,7 +84,6 @@ internal class WalletViewModel @Inject constructor(
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
private val notificationsListFactory = WalletNotificationsListFactory(
currentStateProvider = Provider { uiState },
wasCardScannedCallback = getCardWasScannedUseCase::invoke,
isUserAlreadyRateAppCallback = isUserAlreadyRateAppUseCase::invoke,
isDemoCardCallback = isDemoCardUseCase::invoke,
@ -148,7 +148,7 @@ internal class WalletViewModel @Inject constructor(
when {
getWallet(index).isLocked -> uiState = stateFactory.getLockedState()
cardTypeResolver.isMultiwalletAllowed() -> updateMultiCurrencyContent(index, isRefreshing)
!cardTypeResolver.isMultiwalletAllowed() -> updateSingleCurrencyContent(index)
!cardTypeResolver.isMultiwalletAllowed() -> updateSingleCurrencyContent(index, isRefreshing)
}
}
@ -157,7 +157,7 @@ internal class WalletViewModel @Inject constructor(
"Impossible to update tokens list if state isn't WalletMultiCurrencyState"
}
getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id)
getTokenListUseCase(userWalletId = state.walletsListConfig.wallets[index].id, refresh = isRefreshing)
.distinctUntilChanged()
.onEach { tokenListEither ->
uiState = stateFactory.getStateByTokensList(
@ -175,13 +175,13 @@ internal class WalletViewModel @Inject constructor(
.saveIn(tokensJobHolder)
}
private fun updateSingleCurrencyContent(index: Int) {
private fun updateSingleCurrencyContent(index: Int, isRefreshing: Boolean) {
val wallet = getWallet(index)
updateTxHistory(
blockchain = getCardTypeResolver(index).getBlockchain(),
derivationStyle = wallet.scanResponse.derivationStyleProvider.getDerivationStyle(),
)
updateMarketPrice(userWalletId = wallet.walletId)
updateMarketPrice(userWalletId = wallet.walletId, isRefreshing = isRefreshing)
updateNotifications(index)
}
@ -209,10 +209,16 @@ internal class WalletViewModel @Inject constructor(
}
}
private fun updateMarketPrice(userWalletId: UserWalletId) {
// It also update wallet balance
private fun updateMarketPrice(userWalletId: UserWalletId, isRefreshing: Boolean) {
getPrimaryCurrencyUseCase(userWalletId = userWalletId)
.distinctUntilChanged()
.onEach { uiState = stateFactory.getSingleCurrencyLoadedBalanceState(cryptoCurrencyEither = it) }
.onEach {
uiState = stateFactory.getSingleCurrencyLoadedBalanceState(
cryptoCurrencyEither = it,
isRefreshing = isRefreshing,
)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
.saveIn(marketPriceJobHolder)
@ -336,7 +342,10 @@ internal class WalletViewModel @Inject constructor(
val cacheState = WalletStateCache.getState(userWalletId = state.walletsListConfig.wallets[index].id)
if (cacheState != null) {
uiState = if (cacheState is WalletState.ContentState) {
cacheState.copySealed(walletsListConfig = state.walletsListConfig.copy(selectedWalletIndex = index))
cacheState.copySealed(
walletsListConfig = state.walletsListConfig.copy(selectedWalletIndex = index),
pullToRefreshConfig = state.pullToRefreshConfig.copy(isRefreshing = false),
)
} else {
cacheState
}
@ -366,19 +375,27 @@ internal class WalletViewModel @Inject constructor(
tokensListState is WalletTokensListState.Loading || hasLoadingTokens
}
is WalletSingleCurrencyState -> {
txHistoryState is TxHistoryState.Loading || marketPriceBlockState is MarketPriceBlockState.Loading
this is WalletSingleCurrencyState.Content && marketPriceBlockState is MarketPriceBlockState.Loading ||
txHistoryState is TxHistoryState.Loading
}
is WalletState.Initial -> false
}
}
override fun onRefreshSwipe() {
uiState = stateFactory.getStateAfterContentRefreshing()
if (uiState is WalletState.Initial || uiState is WalletLockedState) return
updateContentItems(
index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex,
isRefreshing = true,
)
viewModelScope.launch(dispatchers.io) {
uiState = stateFactory.getStateAfterContentRefreshing()
// TODO: [REDACTED_JIRA]
delay(timeMillis = 500)
updateContentItems(
index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex,
isRefreshing = true,
)
}
}
override fun onOrganizeTokensClick() {
@ -397,6 +414,7 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getStateAfterContentRefreshing()
updateSingleCurrencyContent(
index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex,
isRefreshing = true,
)
}