Updated on 2026-08-14
This commit is contained in:
parent
e1f80e7e41
commit
324f365771
14 changed files with 310 additions and 144 deletions
|
|
@ -7,10 +7,7 @@ import com.tangem.common.flatMap
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsArguments
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.analytics.events.Token.ButtonRemoveToken
|
||||
|
|
@ -20,7 +17,6 @@ import com.tangem.tap.common.extensions.dispatchErrorNotification
|
|||
import com.tangem.tap.common.extensions.dispatchWithMain
|
||||
import com.tangem.tap.common.redux.global.GlobalAction
|
||||
import com.tangem.tap.domain.TapError
|
||||
import com.tangem.tap.features.wallet.models.Currency
|
||||
import com.tangem.tap.features.wallet.converters.CryptoCurrencyConverter
|
||||
import com.tangem.tap.features.wallet.redux.WalletAction
|
||||
import com.tangem.tap.features.wallet.redux.WalletState
|
||||
|
|
@ -44,7 +40,7 @@ class MultiWalletMiddleware {
|
|||
is WalletAction.MultiWallet.SelectWallet -> {
|
||||
if (action.currency != null) {
|
||||
val bundle = bundleOf(
|
||||
TokenDetailsRouter.TOKEN_DETAILS_ARGS to createTokenDetailsArgument(action.currency),
|
||||
TokenDetailsRouter.CRYPTO_CURRENCY_KEY to cryptoCurrencyConverter.convert(action.currency),
|
||||
)
|
||||
store.dispatch(NavigationAction.NavigateTo(screen = AppScreen.WalletDetails, bundle = bundle))
|
||||
}
|
||||
|
|
@ -133,23 +129,4 @@ class MultiWalletMiddleware {
|
|||
store.state.globalState.tapWalletManager.loadData(updatedUserWallet, refresh = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createTokenDetailsArgument(currency: Currency): TokenDetailsArguments {
|
||||
val cryptoCurrency = cryptoCurrencyConverter.convert(currency)
|
||||
return TokenDetailsArguments(
|
||||
currencyId = cryptoCurrency.id,
|
||||
currencyName = cryptoCurrency.name,
|
||||
currencySymbol = cryptoCurrency.symbol,
|
||||
iconUrl = cryptoCurrency.iconUrl,
|
||||
coinType = when (cryptoCurrency) {
|
||||
is CryptoCurrency.Coin -> TokenDetailsArguments.CoinType.Native
|
||||
is CryptoCurrency.Token -> TokenDetailsArguments.CoinType.Token(
|
||||
isCustom = cryptoCurrency.isCustom,
|
||||
standardName = cryptoCurrency.network.standardType.name,
|
||||
networkName = cryptoCurrency.network.name,
|
||||
networkIcon = cryptoCurrency.networkIconResId,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,8 @@ import kotlinx.parcelize.Parcelize
|
|||
* @property iconUrl Optional URL of the cryptocurrency icon. `null` if not found.
|
||||
* @property isCustom Indicates whether the currency is a custom user-added currency or not.
|
||||
*/
|
||||
sealed class CryptoCurrency {
|
||||
@Parcelize
|
||||
sealed class CryptoCurrency : Parcelable {
|
||||
|
||||
abstract val id: ID
|
||||
abstract val network: Network
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.tangem.domain.tokens.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* Represents a blockchain network, identified by a unique ID, a human-readable name, and its standard type.
|
||||
*
|
||||
|
|
@ -13,13 +16,14 @@ package com.tangem.domain.tokens.model
|
|||
* @property isTestnet Indicates whether the network is a test network or a main network.
|
||||
* @property standardType The type of blockchain standard the network adheres to.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Network(
|
||||
val id: ID,
|
||||
val name: String,
|
||||
val derivationPath: DerivationPath,
|
||||
val isTestnet: Boolean,
|
||||
val standardType: StandardType,
|
||||
) {
|
||||
) : Parcelable {
|
||||
|
||||
init {
|
||||
require(name.isNotBlank()) { "Network name must not be blank" }
|
||||
|
|
@ -31,7 +35,8 @@ data class Network(
|
|||
* @property value The string representation of the network ID.
|
||||
*/
|
||||
@JvmInline
|
||||
value class ID(val value: String) {
|
||||
@Parcelize
|
||||
value class ID(val value: String) : Parcelable {
|
||||
|
||||
init {
|
||||
require(value.isNotBlank()) { "Network ID must not be blank" }
|
||||
|
|
@ -44,7 +49,8 @@ data class Network(
|
|||
* This class represents such paths in a generic manner, allowing for predefined card-based paths,
|
||||
* custom paths, or even no derivation path at all.
|
||||
*/
|
||||
sealed class DerivationPath {
|
||||
@Parcelize
|
||||
sealed class DerivationPath : Parcelable {
|
||||
|
||||
/** The actual derivation path value, if any. */
|
||||
abstract val value: String?
|
||||
|
|
@ -67,7 +73,7 @@ data class Network(
|
|||
* Represents a lack of derivation path.
|
||||
*/
|
||||
object None : DerivationPath() {
|
||||
override val value: String? = null
|
||||
override val value: String? get() = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,27 +86,28 @@ data class Network(
|
|||
*
|
||||
* @property name The human-readable name of the standard type.
|
||||
*/
|
||||
sealed class StandardType {
|
||||
@Parcelize
|
||||
sealed class StandardType : Parcelable {
|
||||
abstract val name: String
|
||||
|
||||
/** Represents the ERC20 token standard, common on the Ethereum network. */
|
||||
object ERC20 : StandardType() {
|
||||
override val name: String = "ERC20"
|
||||
override val name: String get() = "ERC20"
|
||||
}
|
||||
|
||||
/** Represents the TRC20 token standard, common on the TRON network. */
|
||||
object TRC20 : StandardType() {
|
||||
override val name: String = "TRC20"
|
||||
override val name: String get() = "TRC20"
|
||||
}
|
||||
|
||||
/** Represents the BEP20 token standard, common on the Binance Smart Chain network. */
|
||||
object BEP20 : StandardType() {
|
||||
override val name: String = "BEP20"
|
||||
override val name: String get() = "BEP20"
|
||||
}
|
||||
|
||||
/** Represents the BEP2 token standard, common on the Binance Chain network. */
|
||||
object BEP2 : StandardType() {
|
||||
override val name: String = "BEP2"
|
||||
override val name: String get() = "BEP2"
|
||||
}
|
||||
|
||||
/** Represents a network that does not adhere to a predefined standard type. */
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
package com.tangem.features.tokendetails.navigation
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class TokenDetailsArguments(
|
||||
val currencyId: CryptoCurrency.ID,
|
||||
val currencyName: String,
|
||||
val currencySymbol: String,
|
||||
val iconUrl: String?,
|
||||
val coinType: CoinType,
|
||||
) : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
sealed class CoinType : Parcelable {
|
||||
object Native : CoinType()
|
||||
|
||||
/**
|
||||
* @param isCustom - Indicates whether the currency is a custom user-added currency or not.
|
||||
* @param standardName - token standard. Samples: ERC20, BEP20, BEP2, TRC20 and etc.
|
||||
* @param networkName - token's blockchain name. Ethereum, Tron and etc.
|
||||
*/
|
||||
data class Token(
|
||||
val isCustom: Boolean,
|
||||
val standardName: String,
|
||||
val networkName: String,
|
||||
@DrawableRes val networkIcon: Int,
|
||||
) : CoinType()
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@ interface TokenDetailsRouter {
|
|||
fun getEntryFragment(): Fragment
|
||||
|
||||
companion object {
|
||||
const val TOKEN_DETAILS_ARGS = "token_details_args"
|
||||
const val CRYPTO_CURRENCY_KEY = "token_details_crypto_currency"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
|
@ -32,12 +33,21 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
val tokenInfoBlockStateWithLongNameInMainCurrency = TokenInfoBlockState(
|
||||
name = "Stellar (XLM) with long name test",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
iconState = TokenInfoBlockState.IconState.CoinIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackResId = R.drawable.img_stellar_22,
|
||||
isGrayscale = false,
|
||||
),
|
||||
currency = TokenInfoBlockState.Currency.Native,
|
||||
)
|
||||
val tokenInfoBlockStateWithLongName = TokenInfoBlockState(
|
||||
name = "Tether (USDT) with long name test",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
iconState = TokenInfoBlockState.IconState.TokenIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackTint = Color.Cyan,
|
||||
fallbackBackground = Color.Blue,
|
||||
isGrayscale = false,
|
||||
),
|
||||
currency = TokenInfoBlockState.Currency.Token(
|
||||
standardName = "ERC20",
|
||||
networkIcon = R.drawable.img_eth_22,
|
||||
|
|
@ -47,7 +57,11 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
val tokenInfoBlockState = TokenInfoBlockState(
|
||||
name = "Tether USDT",
|
||||
iconUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/tether.png",
|
||||
iconState = TokenInfoBlockState.IconState.CustomTokenIcon(
|
||||
tint = Color.Green,
|
||||
background = Color.Magenta,
|
||||
isGrayscale = true,
|
||||
),
|
||||
currency = TokenInfoBlockState.Currency.Token(
|
||||
standardName = "ERC20",
|
||||
networkIcon = R.drawable.img_eth_22,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
internal data class TokenInfoBlockState(
|
||||
val name: String,
|
||||
val iconUrl: String?,
|
||||
val iconState: IconState,
|
||||
val currency: Currency,
|
||||
) {
|
||||
@Immutable
|
||||
sealed class Currency {
|
||||
object Native : Currency()
|
||||
|
||||
|
|
@ -21,4 +24,29 @@ internal data class TokenInfoBlockState(
|
|||
@DrawableRes val networkIcon: Int,
|
||||
) : Currency()
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed class IconState {
|
||||
|
||||
abstract val isGrayscale: Boolean
|
||||
|
||||
data class CoinIcon(
|
||||
val url: String?,
|
||||
@DrawableRes val fallbackResId: Int,
|
||||
override val isGrayscale: Boolean,
|
||||
) : IconState()
|
||||
|
||||
data class TokenIcon(
|
||||
val url: String?,
|
||||
val fallbackTint: Color,
|
||||
val fallbackBackground: Color,
|
||||
override val isGrayscale: Boolean,
|
||||
) : IconState()
|
||||
|
||||
data class CustomTokenIcon(
|
||||
val tint: Color,
|
||||
val background: Color,
|
||||
override val isGrayscale: Boolean,
|
||||
) : IconState()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import com.tangem.core.ui.extensions.getTintForTokenIcon
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.tryGetBackgroundForTokenIcon
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class TokenDetailsIconStateConverter : Converter<CryptoCurrency, TokenInfoBlockState.IconState> {
|
||||
|
||||
override fun convert(value: CryptoCurrency): TokenInfoBlockState.IconState {
|
||||
return when (value) {
|
||||
is CryptoCurrency.Coin -> getIconStateForCoin(value)
|
||||
is CryptoCurrency.Token -> getIconStateForToken(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconStateForCoin(coin: CryptoCurrency.Coin): TokenInfoBlockState.IconState.CoinIcon {
|
||||
return TokenInfoBlockState.IconState.CoinIcon(
|
||||
url = coin.iconUrl,
|
||||
fallbackResId = coin.networkIconResId,
|
||||
isGrayscale = coin.network.isTestnet,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIconStateForToken(token: CryptoCurrency.Token): TokenInfoBlockState.IconState {
|
||||
val isGrayscale = token.network.isTestnet
|
||||
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
|
||||
val tint = getTintForTokenIcon(background)
|
||||
|
||||
return if (token.isCustom) {
|
||||
TokenInfoBlockState.IconState.CustomTokenIcon(
|
||||
tint = tint,
|
||||
background = background,
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
} else {
|
||||
TokenInfoBlockState.IconState.TokenIcon(
|
||||
url = token.iconUrl,
|
||||
isGrayscale = isGrayscale,
|
||||
fallbackTint = tint,
|
||||
fallbackBackground = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
|||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsPullToRefreshConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsArguments
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -17,29 +18,30 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
|
||||
internal class TokenDetailsSkeletonStateConverter(
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
) : Converter<TokenDetailsArguments, TokenDetailsState> {
|
||||
) : Converter<CryptoCurrency, TokenDetailsState> {
|
||||
|
||||
override fun convert(value: TokenDetailsArguments): TokenDetailsState {
|
||||
val coinType = value.coinType
|
||||
private val iconStateConverter by lazy { TokenDetailsIconStateConverter() }
|
||||
|
||||
override fun convert(value: CryptoCurrency): TokenDetailsState {
|
||||
return TokenDetailsState(
|
||||
topAppBarConfig = TokenDetailsTopAppBarConfig(
|
||||
onBackClick = clickIntents::onBackClick,
|
||||
tokenDetailsAppBarMenuConfig = createMenu(),
|
||||
),
|
||||
tokenInfoBlockState = TokenInfoBlockState(
|
||||
name = value.currencyName,
|
||||
iconUrl = value.iconUrl,
|
||||
currency = when (coinType) {
|
||||
TokenDetailsArguments.CoinType.Native -> TokenInfoBlockState.Currency.Native
|
||||
is TokenDetailsArguments.CoinType.Token -> TokenInfoBlockState.Currency.Token(
|
||||
standardName = coinType.networkName,
|
||||
networkName = coinType.networkName,
|
||||
networkIcon = coinType.networkIcon,
|
||||
name = value.name,
|
||||
iconState = iconStateConverter.convert(value),
|
||||
currency = when (value) {
|
||||
is CryptoCurrency.Coin -> TokenInfoBlockState.Currency.Native
|
||||
is CryptoCurrency.Token -> TokenInfoBlockState.Currency.Token(
|
||||
standardName = value.network.standardType.name,
|
||||
networkName = value.network.name,
|
||||
networkIcon = value.networkIconResId,
|
||||
)
|
||||
},
|
||||
),
|
||||
tokenBalanceBlockState = TokenDetailsBalanceBlockState.Loading(actionButtons = createButtons()),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(value.currencySymbol),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol),
|
||||
notifications = persistentListOf(),
|
||||
pendingTxs = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
|
|
@ -51,7 +53,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
pullToRefreshConfig = createPullToRefresh(),
|
||||
bottomSheetConfig = null,
|
||||
isBalanceHidden = true,
|
||||
isCustomToken = coinType is TokenDetailsArguments.CoinType.Token && coinType.isCustom,
|
||||
isCustomToken = value.isCustom,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,15 +22,14 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.component
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadedTxHistoryConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsArguments
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
internal class TokenDetailsStateFactory(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
currencySymbolProvider: Provider<String>,
|
||||
currencyDecimalsProvider: Provider<Int>,
|
||||
symbol: String,
|
||||
decimals: Int,
|
||||
) {
|
||||
|
||||
private val skeletonStateConverter by lazy {
|
||||
|
|
@ -45,8 +44,8 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsLoadedBalanceConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
appCurrencyProvider = appCurrencyProvider,
|
||||
symbol = currencySymbolProvider(),
|
||||
decimals = currencyDecimalsProvider(),
|
||||
symbol = symbol,
|
||||
decimals = decimals,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +64,8 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsLoadedTxHistoryConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
clickIntents = clickIntents,
|
||||
symbol = currencySymbolProvider(),
|
||||
decimals = currencyDecimalsProvider(),
|
||||
symbol = symbol,
|
||||
decimals = decimals,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +75,7 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getInitialState(screenArgument: TokenDetailsArguments): TokenDetailsState {
|
||||
fun getInitialState(screenArgument: CryptoCurrency): TokenDetailsState {
|
||||
return skeletonStateConverter.convert(value = screenArgument)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,146 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
||||
@Composable
|
||||
internal fun CurrencyIcon(
|
||||
icon: TokenInfoBlockState.IconState,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (icon) {
|
||||
is TokenInfoBlockState.IconState.CoinIcon -> CoinIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
fallbackResId = icon.fallbackResId,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
is TokenInfoBlockState.IconState.TokenIcon -> TokenIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
errorIcon = {
|
||||
CustomTokenIcon(
|
||||
modifier = modifier,
|
||||
tint = icon.fallbackTint,
|
||||
background = icon.fallbackBackground,
|
||||
alpha = alpha,
|
||||
)
|
||||
},
|
||||
)
|
||||
is TokenInfoBlockState.IconState.CustomTokenIcon -> CustomTokenIcon(
|
||||
modifier = modifier,
|
||||
tint = icon.tint,
|
||||
background = icon.background,
|
||||
alpha = alpha,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CoinIcon(
|
||||
url: String?,
|
||||
@DrawableRes fallbackResId: Int,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val iconData: Any = if (url.isNullOrBlank()) fallbackResId else url
|
||||
|
||||
DefaultCurrencyIcon(
|
||||
modifier = modifier,
|
||||
iconData = iconData,
|
||||
errorIcon = {
|
||||
Image(
|
||||
painter = painterResource(id = fallbackResId),
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenIcon(
|
||||
url: String?,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (url == null) {
|
||||
errorIcon()
|
||||
} else {
|
||||
DefaultCurrencyIcon(
|
||||
modifier = modifier,
|
||||
iconData = url,
|
||||
errorIcon = errorIcon,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CustomTokenIcon(tint: Color, background: Color, alpha: Float, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = background.copy(alpha = alpha),
|
||||
shape = CircleShape,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
painter = painterResource(id = R.drawable.ic_custom_token_44),
|
||||
tint = tint.copy(alpha = alpha),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private inline fun DefaultCurrencyIcon(
|
||||
iconData: Any,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
crossinline errorIcon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = modifier,
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(iconData)
|
||||
.crossfade(enable = true)
|
||||
.build(),
|
||||
loading = { CircleShimmer() },
|
||||
error = { errorIcon() },
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
|
|
@ -10,19 +9,23 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
||||
private const val GRAY_SCALE_SATURATION = 0f
|
||||
private const val GRAY_SCALE_ALPHA = 0.4f
|
||||
private const val NORMAL_ALPHA = 1f
|
||||
|
||||
@Composable
|
||||
internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Modifier) {
|
||||
Row(modifier = modifier.fillMaxWidth()) {
|
||||
|
|
@ -37,16 +40,18 @@ internal fun TokenInfoBlock(state: TokenInfoBlockState, modifier: Modifier = Mod
|
|||
NetworkInfoText(state.currency)
|
||||
}
|
||||
|
||||
val tokenIconPainter = when (LocalInspectionMode.current) {
|
||||
// show drawable res in preview
|
||||
true -> painterResource(id = R.drawable.img_stellar_22)
|
||||
false -> rememberAsyncImagePainter(model = state.iconUrl)
|
||||
val (alpha, colorFilter) = remember(state.iconState.isGrayscale) {
|
||||
if (state.iconState.isGrayscale) {
|
||||
GRAY_SCALE_ALPHA to GrayscaleColorFilter
|
||||
} else {
|
||||
NORMAL_ALPHA to null
|
||||
}
|
||||
}
|
||||
|
||||
Image(
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size48),
|
||||
painter = tokenIconPainter,
|
||||
contentDescription = null,
|
||||
icon = state.iconState,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +115,9 @@ private fun extractNetwork(tokenCurrency: TokenInfoBlockState.Currency.Token): E
|
|||
|
||||
private data class ExtractedTokenNetworkText(val normalText: String, val boldText: String)
|
||||
|
||||
private val GrayscaleColorFilter: ColorFilter
|
||||
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun Preview_TokenInfoBlock_LightTheme(
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRout
|
|||
import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenScreenEvent
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsArguments
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
|
|
@ -59,15 +58,14 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val isBalanceHiddenUseCase: IsBalanceHiddenUseCase,
|
||||
private val listenToFlipsUseCase: ListenToFlipsUseCase,
|
||||
private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase,
|
||||
private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
|
||||
|
||||
private val screenArgument: TokenDetailsArguments = savedStateHandle[TokenDetailsRouter.TOKEN_DETAILS_ARGS]
|
||||
?: error("This screen can't open without TokenDetailsArgument")
|
||||
private val cryptoCurrency: CryptoCurrency = savedStateHandle[TokenDetailsRouter.CRYPTO_CURRENCY_KEY]
|
||||
?: error("This screen can't open without CryptoCurrency")
|
||||
|
||||
var router by Delegates.notNull<InnerTokenDetailsRouter>()
|
||||
|
||||
|
|
@ -75,7 +73,6 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val refreshStateJobHolder = JobHolder()
|
||||
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
|
||||
private var wallet by Delegates.notNull<UserWallet>()
|
||||
private var cryptoCurrency by Delegates.notNull<CryptoCurrency>()
|
||||
|
||||
private val selectedAppCurrencyFlow: StateFlow<AppCurrency> = createSelectedAppCurrencyFlow()
|
||||
|
||||
|
|
@ -83,34 +80,25 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
currentStateProvider = Provider { uiState },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
clickIntents = this,
|
||||
currencySymbolProvider = Provider { cryptoCurrency.symbol },
|
||||
currencyDecimalsProvider = Provider { cryptoCurrency.decimals },
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
)
|
||||
|
||||
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(screenArgument))
|
||||
var uiState: TokenDetailsState by mutableStateOf(stateFactory.getInitialState(cryptoCurrency))
|
||||
private set
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
initRequiredFields()
|
||||
getWallet()
|
||||
updateContent(selectedWallet = wallet)
|
||||
handleBalanceHiding(owner)
|
||||
}
|
||||
|
||||
private fun initRequiredFields() {
|
||||
private fun getWallet() {
|
||||
getSelectedWalletUseCase()
|
||||
.fold(
|
||||
ifLeft = { error("Can not get selected wallet $it") },
|
||||
ifRight = { wallet = it },
|
||||
)
|
||||
viewModelScope.launch {
|
||||
getCryptoCurrencyUseCase(userWalletId = wallet.walletId, id = screenArgument.currencyId)
|
||||
.fold(
|
||||
ifLeft = { error("Can not get cryptoCurrency with given ID: screenArgument.currencyId. $it") },
|
||||
ifRight = {
|
||||
cryptoCurrency = it
|
||||
updateContent(selectedWallet = wallet)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContent(selectedWallet: UserWallet) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import androidx.navigation.navArgument
|
|||
import com.tangem.core.navigation.AppScreen
|
||||
import com.tangem.core.navigation.NavigationAction
|
||||
import com.tangem.core.navigation.ReduxNavController
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.WalletFragment
|
||||
|
|
@ -27,7 +26,6 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScree
|
|||
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
|
||||
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsArguments
|
||||
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
|
@ -113,23 +111,7 @@ internal class DefaultWalletRouter(private val reduxNavController: ReduxNavContr
|
|||
reduxNavController.navigate(
|
||||
action = NavigationAction.NavigateTo(
|
||||
screen = AppScreen.WalletDetails,
|
||||
bundle = bundleOf(
|
||||
TokenDetailsRouter.TOKEN_DETAILS_ARGS to TokenDetailsArguments(
|
||||
currencyId = currency.id,
|
||||
currencyName = currency.name,
|
||||
currencySymbol = currency.symbol,
|
||||
iconUrl = currency.iconUrl,
|
||||
coinType = when (currency) {
|
||||
is CryptoCurrency.Coin -> TokenDetailsArguments.CoinType.Native
|
||||
is CryptoCurrency.Token -> TokenDetailsArguments.CoinType.Token(
|
||||
isCustom = currency.isCustom,
|
||||
standardName = currency.network.standardType.name,
|
||||
networkName = currency.network.name,
|
||||
networkIcon = currency.networkIconResId,
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
bundle = bundleOf(TokenDetailsRouter.CRYPTO_CURRENCY_KEY to currency),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue