diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
index 274f5bbe46..d5ab303555 100644
--- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
+++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt
@@ -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(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
index d7f217308c..4192706627 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlock.kt
@@ -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 Figma component
*/
@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"),
),
)
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt
index ae59401aa9..30b652ea68 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/marketprice/MarketPriceBlockState.kt
@@ -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(
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
index d70869c7b8..e9e235c8d1 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt
@@ -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> = 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"
diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCurrenciesFactory.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCurrenciesFactory.kt
index 8d2ac0b3be..9a5858f0dc 100644
--- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCurrenciesFactory.kt
+++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/utils/CardCurrenciesFactory.kt
@@ -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
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt
index 352b0687a2..dd3bbbeee7 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletLockedState.kt
@@ -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
+ }
+ }
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt
new file mode 100644
index 0000000000..6087b1f2fa
--- /dev/null
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletSingleCurrencyLoadedBalanceConverter.kt
@@ -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,
+ private val fiatCurrencyCode: String,
+ private val fiatCurrencySymbol: String,
+) : Converter, WalletSingleCurrencyState.Content> {
+
+ override fun convert(value: Either): 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),
+ )
+ }
+}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt
index 0f4d3bd755..c1ab20757d 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/factory/WalletStateFactory.kt
@@ -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, selectedWalletIndex: Int): WalletState {
@@ -189,4 +199,10 @@ internal class WalletStateFactory(
WalletManageButton.CopyAddress(onClick = {}),
)
}
+
+ fun getSingleCurrencyLoadedBalanceState(
+ cryptoCurrencyEither: Either,
+ ): WalletState {
+ return singleCurrencyLoadedBalanceConverter.convert(cryptoCurrencyEither)
+ }
}
\ No newline at end of file
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt
index fd0498866a..ccff84d714 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletCard.kt
@@ -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())
}
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt
index 94c0e30ade..6414445119 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/JobHolder.kt
@@ -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
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
index bc50b0d051..eb8e6247e1 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt
@@ -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 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,
)
}