Updated on 2026-08-14

This commit is contained in:
Tangem 2023-08-11 15:09:20 +08:00
parent 9b70e5bc82
commit 300179445d
11 changed files with 417 additions and 150 deletions

View file

@ -28,7 +28,7 @@ internal object TokensDomainModule {
@Provides
@ViewModelScoped
fun provideGetPrimaryCurrencyUseCase(
fun provideGetCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
@ -37,6 +37,17 @@ internal object TokensDomainModule {
return GetCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideGetPrimaryCurrencyUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetPrimaryCurrencyUseCase {
return GetPrimaryCurrencyUseCase(currenciesRepository, quotesRepository, networksRepository, dispatchers)
}
@Provides
@ViewModelScoped
fun provideToggleTokenListGroupingUseCase(

View file

@ -1,5 +1,7 @@
package com.tangem.core.ui.components.marketprice
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
@ -20,15 +22,22 @@ import androidx.compose.ui.unit.Dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
/**
* @see <a href =
* "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1217-922&mode=design&t=t1PxyisyGJSwzQwg-4"
* Market price block
*
* @param state component state
* @param modifier modifier
*
* @see <a href = "https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1217-922&mode
* =design&t=t1PxyisyGJSwzQwg-4"
* >Figma component</a>
*/
@Composable
fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier) {
var rootWidth by remember { mutableStateOf(value = 0) }
Column(
modifier = modifier
.background(
@ -42,81 +51,139 @@ fun MarketPriceBlock(state: MarketPriceBlockState, modifier: Modifier = Modifier
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
horizontalAlignment = Alignment.Start,
) {
Text(
text = stringResource(id = R.string.wallet_marketplace_block_title, state.currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
Title(currencyName = state.currencyName)
when (state) {
is MarketPriceBlockState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(width = TangemTheme.dimens.size158, height = TangemTheme.dimens.size20),
)
}
is MarketPriceBlockState.Content -> {
Price(
config = state,
Content(state = state, rootWidth = rootWidth)
}
}
@Composable
private fun Title(currencyName: String) {
Text(
text = stringResource(id = R.string.wallet_marketplace_block_title, currencyName),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Content(state: MarketPriceBlockState, rootWidth: Int) {
AnimatedContent(targetState = state, label = "Update the content") { marketPriceBlockState ->
when (marketPriceBlockState) {
is MarketPriceBlockState.Content,
is MarketPriceBlockState.Error,
-> {
PriceContent(
state = marketPriceBlockState,
priceWidthDp = with(LocalDensity.current) { rootWidth.div(other = 2).toDp() },
)
}
is MarketPriceBlockState.Loading -> LoadingContent()
}
}
}
@Composable
private fun Price(config: MarketPriceBlockState.Content, priceWidthDp: Dp) {
private fun PriceContent(state: MarketPriceBlockState, priceWidthDp: Dp) {
Row(
modifier = Modifier,
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Text(
text = config.price,
modifier = Modifier.widthIn(max = priceWidthDp),
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
PriceBlock(state = state, priceWidthDp = priceWidthDp)
PriceChangeInPercent(config.priceChangeConfig)
QuoteTimeStatus()
}
}
Text(
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceBlock(state: MarketPriceBlockState, priceWidthDp: Dp) {
val priceModifier = Modifier.widthIn(max = priceWidthDp)
AnimatedContent(targetState = state, label = "Update the price block") { marketPriceBlockState ->
if (marketPriceBlockState is MarketPriceBlockState.Content) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Price(price = marketPriceBlockState.price, modifier = priceModifier)
PriceChangeInPercent(marketPriceBlockState.priceChangeConfig)
}
} else {
Price(price = BigDecimalFormatter.EMPTY_BALANCE_SIGN, modifier = priceModifier)
}
}
}
@Composable
private fun Price(price: String, modifier: Modifier = Modifier) {
Text(
text = price,
modifier = modifier,
color = TangemTheme.colors.text.primary1,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
style = TangemTheme.typography.body2,
)
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun PriceChangeInPercent(config: PriceChangeConfig) {
AnimatedContent(targetState = config.type, label = "Update price change") { type ->
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Image(
painter = painterResource(
id = when (type) {
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
},
),
contentDescription = null,
)
Text(
text = config.valueInPercent,
color = when (type) {
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
},
style = TangemTheme.typography.body2,
)
}
}
}
@Composable
private fun LoadingContent() {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing8),
) {
Image(
painter = painterResource(
id = when (config.type) {
PriceChangeConfig.Type.UP -> R.drawable.img_arrow_up_8
PriceChangeConfig.Type.DOWN -> R.drawable.img_arrow_down_8
},
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size158,
height = TangemTheme.dimens.size20,
),
contentDescription = null,
)
Text(
text = config.valueInPercent,
color = when (config.type) {
PriceChangeConfig.Type.UP -> TangemTheme.colors.text.accent
PriceChangeConfig.Type.DOWN -> TangemTheme.colors.text.warning
},
style = TangemTheme.typography.body2,
)
QuoteTimeStatus()
}
}
@Composable
private fun QuoteTimeStatus() {
Text(
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
)
}
@Preview
@Composable
private fun Preview_MarketPriceBlock_Light(
@ -143,7 +210,7 @@ private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterPr
collection = listOf(
MarketPriceBlockState.Content(
currencyName = "BTC",
price = "98900",
price = "98900 $",
priceChangeConfig = PriceChangeConfig(
valueInPercent = "5.16%",
type = PriceChangeConfig.Type.DOWN,
@ -151,12 +218,13 @@ private class WalletMarketPriceBlockStateProvider : CollectionPreviewParameterPr
),
MarketPriceBlockState.Content(
currencyName = "BTC",
price = "98900",
price = "98900 $",
priceChangeConfig = PriceChangeConfig(
valueInPercent = "10.89%",
type = PriceChangeConfig.Type.UP,
),
),
MarketPriceBlockState.Loading(currencyName = "BTC"),
MarketPriceBlockState.Error(currencyName = "BTC"),
),
)

View file

@ -7,6 +7,8 @@ sealed interface MarketPriceBlockState {
val currencyName: String
data class Error(override val currencyName: String) : MarketPriceBlockState
data class Loading(override val currencyName: String) : MarketPriceBlockState
data class Content(

View file

@ -1,6 +1,7 @@
package com.tangem.data.tokens.repository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.tokens.utils.CardCurrenciesFactory
import com.tangem.data.tokens.utils.NetworkConverter
import com.tangem.data.tokens.utils.NetworkStatusFactory
import com.tangem.data.tokens.utils.ResponseCurrenciesFactory
@ -32,8 +33,10 @@ internal class DefaultNetworksRepository(
private val dispatchers: CoroutineDispatcherProvider,
) : NetworksRepository {
private val demoConfig by lazy { DemoConfig() }
private val networkConverter by lazy { NetworkConverter() }
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(DemoConfig()) }
private val cardCurrenciesFactory by lazy { CardCurrenciesFactory(demoConfig) }
private val responseCurrenciesFactory by lazy { ResponseCurrenciesFactory(demoConfig) }
private val networkStatusFactory by lazy { NetworkStatusFactory() }
private val networksStatuses: MutableStateFlow<List<NetworkStatus>> = MutableStateFlow(emptyList())
@ -107,11 +110,18 @@ internal class DefaultNetworksRepository(
val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) {
"Unable to find user wallet with provided ID: $userWalletId"
}
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
return responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse.card)
return if (userWallet.isMultiCurrency) {
val response = requireNotNull(userTokensStore.getSyncOrNull(userWalletId)) {
"Unable to find tokens response for user wallet with provided ID: $userWalletId"
}
responseCurrenciesFactory.createCurrencies(response, userWallet.scanResponse.card)
} else {
val currency = cardCurrenciesFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse)
listOf(currency)
}
}
private fun getNetworksStatusesCacheKey(userWalletId: UserWalletId): String = "network_status_$userWalletId"

View file

@ -61,7 +61,7 @@ internal class CardCurrenciesFactory(private val demoConfig: DemoConfig) {
}
private fun createCoin(blockchain: Blockchain, card: CardDTO): CryptoCurrency.Coin? {
if (blockchain != Blockchain.Unknown) {
if (blockchain == Blockchain.Unknown) {
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
return null
}

View file

@ -21,4 +21,12 @@ internal sealed interface WalletLockedState {
/** Lambda be invoked when bottom sheet is dismissed */
val onBottomSheetDismiss: () -> Unit
/** Get selected wallet index */
fun getSelectedWalletIndex(): Int {
return when (this) {
is WalletMultiCurrencyState.Locked -> walletsListConfig.selectedWalletIndex
is WalletSingleCurrencyState.Locked -> walletsListConfig.selectedWalletIndex
}
}
}

View file

@ -0,0 +1,120 @@
package com.tangem.feature.wallet.presentation.wallet.state.factory
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.marketprice.PriceChangeConfig
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
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.WalletsListConfig
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
internal class WalletSingleCurrencyLoadedBalanceConverter(
private val currentStateProvider: Provider<WalletState>,
private val fiatCurrencyCode: String,
private val fiatCurrencySymbol: String,
) : Converter<Either<CurrencyError, CryptoCurrencyStatus>, WalletSingleCurrencyState.Content> {
override fun convert(value: Either<CurrencyError, CryptoCurrencyStatus>): WalletSingleCurrencyState.Content {
return value.fold(ifLeft = { convertError() }, ifRight = ::convert)
}
private fun convertError(): WalletSingleCurrencyState.Content {
return requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content)
}
private fun convert(status: CryptoCurrencyStatus): WalletSingleCurrencyState.Content {
val state = requireNotNull(currentStateProvider() as? WalletSingleCurrencyState.Content)
val currencyName = state.marketPriceBlockState.currencyName
return state.copy(
walletsListConfig = getUpdatedSelectedWallet(status.value, state),
marketPriceBlockState = getMarketPriceState(status = status.value, currencyName = currencyName),
)
}
private fun getMarketPriceState(status: CryptoCurrencyStatus.Status, currencyName: String): MarketPriceBlockState {
return when (status) {
is CryptoCurrencyStatus.Loaded -> MarketPriceBlockState.Content(
currencyName = currencyName,
price = BigDecimalFormatter.formatFiatAmount(
fiatAmount = status.fiatRate,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
),
priceChangeConfig = PriceChangeConfig(
valueInPercent = BigDecimalFormatter.formatPercent(
percent = status.priceChange,
useAbsoluteValue = true,
),
type = if (status.priceChange > BigDecimal.ZERO) {
PriceChangeConfig.Type.UP
} else {
PriceChangeConfig.Type.DOWN
},
),
)
is CryptoCurrencyStatus.Loading -> MarketPriceBlockState.Loading(currencyName)
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.Unreachable,
-> MarketPriceBlockState.Error(currencyName)
}
}
private fun getUpdatedSelectedWallet(
status: CryptoCurrencyStatus.Status,
state: WalletSingleCurrencyState,
): WalletsListConfig {
val selectedWallet = state.walletsListConfig.wallets[state.walletsListConfig.selectedWalletIndex]
val updatedWallet = when (status) {
is CryptoCurrencyStatus.Loaded -> {
WalletCardState.Content(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = selectedWallet.additionalInfo,
imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick,
balance = BigDecimalFormatter.formatFiatAmount(
fiatAmount = status.fiatAmount,
fiatCurrencyCode = fiatCurrencyCode,
fiatCurrencySymbol = fiatCurrencySymbol,
),
)
}
is CryptoCurrencyStatus.Loading -> {
WalletCardState.Loading(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = selectedWallet.additionalInfo,
imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick,
)
}
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.Custom,
is CryptoCurrencyStatus.Unreachable,
-> {
WalletCardState.Error(
id = selectedWallet.id,
title = selectedWallet.title,
additionalInfo = selectedWallet.additionalInfo,
imageResId = selectedWallet.imageResId,
onClick = selectedWallet.onClick,
)
}
}
return state.walletsListConfig.copy(
wallets = state.walletsListConfig.wallets.toPersistentList()
.set(index = state.walletsListConfig.selectedWalletIndex, element = updatedWallet),
)
}
}

View file

@ -4,7 +4,9 @@ import androidx.paging.PagingData
import arrow.core.Either
import com.tangem.common.Provider
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.tokens.error.CurrencyError
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.txhistory.models.TxHistoryListError
@ -64,6 +66,14 @@ internal class WalletStateFactory(
)
}
private val singleCurrencyLoadedBalanceConverter by lazy {
WalletSingleCurrencyLoadedBalanceConverter(
currentStateProvider = currentStateProvider,
fiatCurrencyCode = "USD", // TODO: [REDACTED_JIRA]
fiatCurrencySymbol = "$", // TODO: [REDACTED_JIRA]
)
}
fun getInitialState(): WalletState = WalletState.Initial(onBackClick = clickIntents::onBackClick)
fun getSkeletonState(wallets: List<UserWallet>, selectedWalletIndex: Int): WalletState {
@ -189,4 +199,10 @@ internal class WalletStateFactory(
WalletManageButton.CopyAddress(onClick = {}),
)
}
fun getSingleCurrencyLoadedBalanceState(
cryptoCurrencyEither: Either<CurrencyError, CryptoCurrencyStatus>,
): WalletState {
return singleCurrencyLoadedBalanceConverter.convert(cryptoCurrencyEither)
}
}

View file

@ -1,6 +1,9 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
@ -20,6 +23,7 @@ import com.tangem.core.ui.components.FontSizeRange
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.components.WalletCardState
@ -64,85 +68,92 @@ internal fun WalletCard(state: WalletCardState, modifier: Modifier = Modifier) {
}
val imageWidth = TangemTheme.dimens.size120
state.imageResId?.let {
WalletImage(
id = it,
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
)
}
WalletImage(
id = state.imageResId,
modifier = Modifier.constrainAs(imageItem) {
centerVerticallyTo(parent)
top.linkTo(parent.top)
end.linkTo(parent.end)
height = Dimension.fillToConstraints
width = Dimension.value(imageWidth)
},
)
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Title(state: WalletCardState) {
when (state) {
is WalletCardState.HiddenContent -> {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
AnimatedContent(targetState = state, label = "Update the title") {
when (it) {
is WalletCardState.HiddenContent -> {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = it.title,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
is WalletCardState.Content,
is WalletCardState.Error,
is WalletCardState.Loading,
-> {
Text(
text = state.title,
text = it.title,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
Icon(
modifier = Modifier.size(size = TangemTheme.dimens.size20),
painter = painterResource(id = R.drawable.ic_eye_off_24),
contentDescription = null,
tint = TangemTheme.colors.icon.informative,
)
}
}
else -> {
Text(
text = state.title,
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
maxLines = 1,
)
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun Balance(state: WalletCardState) {
when (state) {
is WalletCardState.Content -> {
ResizableText(
text = state.balance,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
)
}
is WalletCardState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,
),
)
}
is WalletCardState.HiddenContent -> {
Text(
text = DOTS,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
is WalletCardState.Error -> {
Text(
text = "",
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
AnimatedContent(targetState = state, label = "Update the balance") {
when (it) {
is WalletCardState.Content -> {
ResizableText(
text = it.balance,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
fontSizeRange = FontSizeRange(min = 16.sp, max = TangemTheme.typography.h2.fontSize),
modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size32),
)
}
is WalletCardState.Loading -> {
RectangleShimmer(
modifier = Modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size24,
),
)
}
is WalletCardState.HiddenContent -> {
Text(
text = DOTS,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
is WalletCardState.Error -> {
Text(
text = BigDecimalFormatter.EMPTY_BALANCE_SIGN,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.h2,
)
}
}
}
}
@ -157,22 +168,23 @@ private fun AdditionalInfo(description: String) {
}
@Composable
private fun WalletImage(@DrawableRes id: Int, modifier: Modifier = Modifier) {
Image(
painter = painterResource(id),
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.FillWidth,
)
private fun WalletImage(@DrawableRes id: Int?, modifier: Modifier = Modifier) {
AnimatedVisibility(visible = id != null, modifier = modifier) {
Image(
painter = painterResource(id = requireNotNull(id)),
contentDescription = null,
contentScale = ContentScale.FillWidth,
)
}
}
// region Preview
@Preview
@Preview(widthDp = 360, heightDp = 360)
@Composable
private fun Preview_WalletCard_LightTheme(@PreviewParameter(WalletCardStateProvider::class) state: WalletCardState) {
TangemTheme(isDark = false) {
WalletCard(state)
WalletCard(state = state, modifier = Modifier.fillMaxWidth())
}
}

View file

@ -11,8 +11,8 @@ internal class JobHolder {
private var job: Job? = null
/** Update current job */
fun update(job: Job) {
/** Update current [job] */
fun update(job: Job?) {
this.job?.cancel()
this.job = job
}

View file

@ -5,6 +5,8 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.*
import androidx.paging.cachedIn
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.common.Provider
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
@ -14,6 +16,7 @@ import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.settings.IsUserAlreadyRateAppUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.models.Network
@ -21,6 +24,7 @@ import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.domain.userwallets.UserWalletBuilder
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.usecase.*
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.state.WalletLockedState
@ -55,6 +59,7 @@ internal class WalletViewModel @Inject constructor(
private val setAccessCodeRequestPolicyUseCase: SetAccessCodeRequestPolicyUseCase,
private val getAccessCodeSavingStatusUseCase: GetAccessCodeSavingStatusUseCase,
private val getTokenListUseCase: GetTokenListUseCase,
private val getPrimaryCurrencyUseCase: GetPrimaryCurrencyUseCase,
private val getCardWasScannedUseCase: GetCardWasScannedUseCase,
private val isUserAlreadyRateAppUseCase: IsUserAlreadyRateAppUseCase,
private val isDemoCardUseCase: IsDemoCardUseCase,
@ -97,6 +102,7 @@ internal class WalletViewModel @Inject constructor(
private var wallets: List<UserWallet> by Delegates.notNull()
private val tokensJobHolder = JobHolder()
private val marketPriceJobHolder = JobHolder()
private val notificationsJobHolder = JobHolder()
override fun onCreate(owner: LifecycleOwner) {
@ -115,10 +121,7 @@ internal class WalletViewModel @Inject constructor(
val currentState = uiState
val selectedWalletIndex = if (currentState is WalletLockedState) {
when (currentState) {
is WalletMultiCurrencyState.Locked -> currentState.walletsListConfig.selectedWalletIndex
is WalletSingleCurrencyState.Locked -> currentState.walletsListConfig.selectedWalletIndex
}
currentState.getSelectedWalletIndex()
} else {
val selectedWallet = getSelectedWalletUseCase().fold(
ifLeft = { error("Selected wallet is null") },
@ -135,12 +138,12 @@ internal class WalletViewModel @Inject constructor(
val cardTypeResolver = getCardTypeResolver(index)
when {
getWallet(index).isLocked -> uiState = stateFactory.getLockedState()
cardTypeResolver.isMultiwalletAllowed() -> updateByTokensList(index, isRefreshing)
!cardTypeResolver.isMultiwalletAllowed() -> updateByTxHistory(index)
cardTypeResolver.isMultiwalletAllowed() -> updateMultiCurrencyContent(index, isRefreshing)
!cardTypeResolver.isMultiwalletAllowed() -> updateSingleCurrencyContent(index)
}
}
private fun updateByTokensList(index: Int, isRefreshing: Boolean = false) {
private fun updateMultiCurrencyContent(index: Int, isRefreshing: Boolean = false) {
val state = requireNotNull(uiState as? WalletMultiCurrencyState) {
"Impossible to update tokens list if state isn't WalletMultiCurrencyState"
}
@ -163,11 +166,19 @@ internal class WalletViewModel @Inject constructor(
.saveIn(tokensJobHolder)
}
private fun updateByTxHistory(index: Int) {
private fun updateSingleCurrencyContent(index: Int) {
val wallet = getWallet(index)
updateTxHistory(
blockchain = getCardTypeResolver(index).getBlockchain(),
derivationStyle = wallet.scanResponse.card.derivationStyle,
)
updateMarketPrice(userWalletId = wallet.walletId)
updateNotifications(index)
}
private fun updateTxHistory(blockchain: Blockchain, derivationStyle: DerivationStyle?) {
viewModelScope.launch(dispatchers.io) {
val wallet = getWallet(index)
val blockchain = getCardTypeResolver(index).getBlockchain()
val derivationPath = blockchain.derivationPath(style = wallet.scanResponse.card.derivationStyle)?.rawPath
val derivationPath = blockchain.derivationPath(style = derivationStyle)?.rawPath
val txHistoryItemsCountEither = txHistoryItemsCountUseCase(
networkId = Network.ID(blockchain.id),
@ -177,20 +188,25 @@ internal class WalletViewModel @Inject constructor(
uiState = stateFactory.getLoadingTxHistoryState(itemsCountEither = txHistoryItemsCountEither)
txHistoryItemsCountEither.onRight {
updateTxHistory(
networkId = Network.ID(blockchain.id),
derivationPath = derivationPath,
uiState = stateFactory.getLoadedTxHistoryState(
txHistoryEither = txHistoryItemsUseCase(
networkId = Network.ID(blockchain.id),
derivationPath = derivationPath,
).map {
it.cachedIn(viewModelScope)
},
)
}
}
updateNotifications(index)
}
private fun updateTxHistory(networkId: Network.ID, derivationPath: String?) {
uiState = stateFactory.getLoadedTxHistoryState(
txHistoryEither = txHistoryItemsUseCase(networkId, derivationPath).map { it.cachedIn(viewModelScope) },
)
private fun updateMarketPrice(userWalletId: UserWalletId) {
getPrimaryCurrencyUseCase(userWalletId = userWalletId)
.distinctUntilChanged()
.onEach { uiState = stateFactory.getSingleCurrencyLoadedBalanceState(cryptoCurrencyEither = it) }
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
.saveIn(marketPriceJobHolder)
}
private fun updateNotifications(index: Int, tokenList: TokenList? = null) {
@ -300,6 +316,10 @@ internal class WalletViewModel @Inject constructor(
if (state.walletsListConfig.selectedWalletIndex == index) return
tokensJobHolder.update(job = null)
marketPriceJobHolder.update(job = null)
notificationsJobHolder.update(job = null)
uiState = stateFactory.getSkeletonState(wallets = wallets, selectedWalletIndex = index)
updateContentItems(index = index)
@ -328,7 +348,7 @@ internal class WalletViewModel @Inject constructor(
override fun onReloadClick() {
uiState = stateFactory.getStateAfterContentRefreshing()
updateByTxHistory(
updateSingleCurrencyContent(
index = requireNotNull(uiState as? WalletState.ContentState).walletsListConfig.selectedWalletIndex,
)
}