Updated on 2026-08-14
This commit is contained in:
parent
590888db59
commit
682349e353
48 changed files with 860 additions and 111 deletions
|
|
@ -12,8 +12,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.graphics.ColorMatrix
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -29,6 +27,8 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
|
||||
import com.tangem.core.ui.utils.GrayscaleColorFilter
|
||||
import com.tangem.feature.swap.models.states.PercentDifference
|
||||
import com.tangem.feature.swap.models.states.ProviderState
|
||||
|
||||
|
|
@ -38,11 +38,6 @@ import com.tangem.feature.swap.models.states.ProviderState
|
|||
* https://www.figma.com/file/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?type=design&node-id=7856-41909&mode=design&t=vo7dyElitnzSPSW3-4
|
||||
*/
|
||||
|
||||
private const val GRAY_SCALE_SATURATION = 0f
|
||||
private const val GRAY_SCALE_ALPHA = 0.4f
|
||||
private val GrayscaleColorFilter: ColorFilter
|
||||
get() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(GRAY_SCALE_SATURATION) })
|
||||
|
||||
@Composable
|
||||
fun ProviderItemBlock(state: ProviderState, modifier: Modifier = Modifier) {
|
||||
if (state !is ProviderState.Empty) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.tokendetails.featuretoggles
|
||||
|
||||
interface TokenDetailsFeatureToggles {
|
||||
|
||||
fun isGenerateXPubEnabled(): Boolean
|
||||
}
|
||||
|
|
@ -75,6 +75,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding)
|
||||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.staking)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -6,5 +6,6 @@ import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggle
|
|||
internal class DefaultTokenDetailsFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : TokenDetailsFeatureToggles {
|
||||
|
||||
override fun isGenerateXPubEnabled() = featureTogglesManager.isFeatureEnabled(name = "GENERATE_XPUB_ENABLED")
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
val tokenInfoBlockStateWithLongNameInMainCurrency = TokenInfoBlockState(
|
||||
name = "Stellar (XLM) with long name test",
|
||||
iconState = TokenInfoBlockState.IconState.CoinIcon(
|
||||
iconState = IconState.CoinIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackResId = R.drawable.img_stellar_22,
|
||||
isGrayscale = false,
|
||||
|
|
@ -44,7 +44,7 @@ internal object TokenDetailsPreviewData {
|
|||
)
|
||||
val tokenInfoBlockStateWithLongName = TokenInfoBlockState(
|
||||
name = "Tether (USDT) with long name test",
|
||||
iconState = TokenInfoBlockState.IconState.TokenIcon(
|
||||
iconState = IconState.TokenIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackTint = Color.Cyan,
|
||||
fallbackBackground = Color.Blue,
|
||||
|
|
@ -59,7 +59,7 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
val tokenInfoBlockStateWithLongNameNoStandard = TokenInfoBlockState(
|
||||
name = "Tether (USDT) with long name test",
|
||||
iconState = TokenInfoBlockState.IconState.TokenIcon(
|
||||
iconState = IconState.TokenIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackTint = Color.Cyan,
|
||||
fallbackBackground = Color.Blue,
|
||||
|
|
@ -74,7 +74,7 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
val tokenInfoBlockState = TokenInfoBlockState(
|
||||
name = "Tether USDT",
|
||||
iconState = TokenInfoBlockState.IconState.CustomTokenIcon(
|
||||
iconState = IconState.CustomTokenIcon(
|
||||
tint = Color.Green,
|
||||
background = Color.Magenta,
|
||||
isGrayscale = true,
|
||||
|
|
@ -86,6 +86,13 @@ internal object TokenDetailsPreviewData {
|
|||
),
|
||||
)
|
||||
|
||||
val iconState = IconState.TokenIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackTint = Color.Cyan,
|
||||
fallbackBackground = Color.Blue,
|
||||
isGrayscale = false,
|
||||
)
|
||||
|
||||
private val actionButtons = persistentListOf(
|
||||
TokenDetailsActionButton.Buy(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Send(dimContent = false, onClick = {}),
|
||||
|
|
@ -103,6 +110,8 @@ internal object TokenDetailsPreviewData {
|
|||
|
||||
private val marketPriceLoading = MarketPriceBlockState.Loading(currencySymbol = "USDT")
|
||||
|
||||
private val stakingLoading = StakingBlockState.Loading(iconState = iconState)
|
||||
|
||||
private val pullToRefreshConfig = TokenDetailsPullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
onRefresh = {},
|
||||
|
|
@ -237,6 +246,7 @@ internal object TokenDetailsPreviewData {
|
|||
tokenInfoBlockState = tokenInfoBlockState,
|
||||
tokenBalanceBlockState = balanceLoading,
|
||||
marketPriceBlockState = marketPriceLoading,
|
||||
stakingBlockState = stakingLoading,
|
||||
notifications = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.Content(
|
||||
contentItems = MutableStateFlow(
|
||||
|
|
@ -250,6 +260,7 @@ internal object TokenDetailsPreviewData {
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
isMarketPriceAvailable = false,
|
||||
isStakingAvailable = false,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
|
||||
|
|
@ -267,6 +278,12 @@ internal object TokenDetailsPreviewData {
|
|||
type = PriceChangeType.UP,
|
||||
),
|
||||
),
|
||||
stakingBlockState = StakingBlockState.Content(
|
||||
interestRate = "7.38",
|
||||
periodInDays = 4,
|
||||
tokenSymbol = "XLM",
|
||||
iconState = iconState,
|
||||
),
|
||||
notifications = persistentListOf(),
|
||||
txHistoryState = TxHistoryState.NotSupported(
|
||||
onExploreClick = {},
|
||||
|
|
@ -279,6 +296,7 @@ internal object TokenDetailsPreviewData {
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = false,
|
||||
isMarketPriceAvailable = true,
|
||||
isStakingAvailable = true,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@Immutable
|
||||
internal 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,20 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
internal sealed interface StakingBlockState {
|
||||
|
||||
val iconState: IconState
|
||||
|
||||
data class Error(override val iconState: IconState) : StakingBlockState
|
||||
|
||||
data class Loading(override val iconState: IconState) : StakingBlockState
|
||||
|
||||
data class Content(
|
||||
override val iconState: IconState,
|
||||
val interestRate: String,
|
||||
val periodInDays: Int,
|
||||
val tokenSymbol: String,
|
||||
) : StakingBlockState
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ internal data class TokenDetailsState(
|
|||
val tokenInfoBlockState: TokenInfoBlockState,
|
||||
val tokenBalanceBlockState: TokenDetailsBalanceBlockState,
|
||||
val marketPriceBlockState: MarketPriceBlockState,
|
||||
val stakingBlockState: StakingBlockState,
|
||||
val notifications: ImmutableList<TokenDetailsNotification>,
|
||||
val pendingTxs: PersistentList<TransactionState>,
|
||||
val swapTxs: PersistentList<SwapTransactionsState>,
|
||||
|
|
@ -26,5 +27,6 @@ internal data class TokenDetailsState(
|
|||
val bottomSheetConfig: TangemBottomSheetConfig?,
|
||||
val isBalanceHidden: Boolean,
|
||||
val isMarketPriceAvailable: Boolean,
|
||||
val isStakingAvailable: Boolean,
|
||||
val event: StateEvent<TextReference>,
|
||||
)
|
||||
|
|
@ -2,7 +2,6 @@ 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,
|
||||
|
|
@ -11,7 +10,7 @@ internal data class TokenInfoBlockState(
|
|||
) {
|
||||
@Immutable
|
||||
sealed class Currency {
|
||||
object Native : Currency()
|
||||
data object Native : Currency()
|
||||
|
||||
/**
|
||||
* @param standardName - token standard. Samples: ERC20, BEP20, BEP2, TRC20 and etc.
|
||||
|
|
@ -24,29 +23,4 @@ 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,39 +4,39 @@ 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.feature.tokendetails.presentation.tokendetails.state.IconState
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class TokenDetailsIconStateConverter : Converter<CryptoCurrency, TokenInfoBlockState.IconState> {
|
||||
internal class TokenDetailsIconStateConverter : Converter<CryptoCurrency, IconState> {
|
||||
|
||||
override fun convert(value: CryptoCurrency): TokenInfoBlockState.IconState {
|
||||
override fun convert(value: CryptoCurrency): 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(
|
||||
private fun getIconStateForCoin(coin: CryptoCurrency.Coin): IconState.CoinIcon {
|
||||
return IconState.CoinIcon(
|
||||
url = coin.iconUrl,
|
||||
fallbackResId = coin.networkIconResId,
|
||||
isGrayscale = coin.network.isTestnet,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getIconStateForToken(token: CryptoCurrency.Token): TokenInfoBlockState.IconState {
|
||||
private fun getIconStateForToken(token: CryptoCurrency.Token): IconState {
|
||||
val isGrayscale = token.network.isTestnet
|
||||
val background = token.tryGetBackgroundForTokenIcon(isGrayscale)
|
||||
val tint = getTintForTokenIcon(background)
|
||||
|
||||
return if (token.isCustom && token.iconUrl == null) {
|
||||
TokenInfoBlockState.IconState.CustomTokenIcon(
|
||||
IconState.CustomTokenIcon(
|
||||
tint = tint,
|
||||
background = background,
|
||||
isGrayscale = isGrayscale,
|
||||
)
|
||||
} else {
|
||||
TokenInfoBlockState.IconState.TokenIcon(
|
||||
IconState.TokenIcon(
|
||||
url = token.iconUrl,
|
||||
isGrayscale = isGrayscale,
|
||||
fallbackTint = tint,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
|
||||
|
|
@ -16,6 +17,7 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.Toke
|
|||
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -25,11 +27,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
internal class TokenDetailsSkeletonStateConverter(
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
) : Converter<CryptoCurrency, TokenDetailsState> {
|
||||
|
||||
private val iconStateConverter by lazy { TokenDetailsIconStateConverter() }
|
||||
|
||||
override fun convert(value: CryptoCurrency): TokenDetailsState {
|
||||
val iconState = iconStateConverter.convert(value)
|
||||
return TokenDetailsState(
|
||||
topAppBarConfig = TokenDetailsTopAppBarConfig(
|
||||
onBackClick = clickIntents::onBackClick,
|
||||
|
|
@ -37,7 +41,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
),
|
||||
tokenInfoBlockState = TokenInfoBlockState(
|
||||
name = value.name,
|
||||
iconState = iconStateConverter.convert(value),
|
||||
iconState = iconState,
|
||||
currency = when (value) {
|
||||
is CryptoCurrency.Coin -> TokenInfoBlockState.Currency.Native
|
||||
is CryptoCurrency.Token -> TokenInfoBlockState.Currency.Token(
|
||||
|
|
@ -49,6 +53,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
),
|
||||
tokenBalanceBlockState = TokenDetailsBalanceBlockState.Loading(actionButtons = createButtons()),
|
||||
marketPriceBlockState = MarketPriceBlockState.Loading(value.symbol),
|
||||
stakingBlockState = StakingBlockState.Loading(iconState = iconState),
|
||||
notifications = persistentListOf(),
|
||||
pendingTxs = persistentListOf(),
|
||||
swapTxs = persistentListOf(),
|
||||
|
|
@ -62,6 +67,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
bottomSheetConfig = null,
|
||||
isBalanceHidden = true,
|
||||
isMarketPriceAvailable = value.id.rawCurrencyId != null,
|
||||
isStakingAvailable = stakingAvailabilityProvider.invoke() is StakingAvailability.Available,
|
||||
event = consumedEvent(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import com.tangem.core.ui.extensions.wrappedList
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.domain.tokens.error.CurrencyStatusError
|
||||
import com.tangem.domain.tokens.model.*
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
|
|
@ -38,10 +40,11 @@ import kotlinx.collections.immutable.toImmutableList
|
|||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
@Suppress("TooManyFunctions", "LargeClass", "LongParameterList")
|
||||
internal class TokenDetailsStateFactory(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
private val appCurrencyProvider: Provider<AppCurrency>,
|
||||
private val stakingAvailabilityProvider: Provider<StakingAvailability>,
|
||||
private val clickIntents: TokenDetailsClickIntents,
|
||||
private val featureToggles: TokenDetailsFeatureToggles,
|
||||
symbol: String,
|
||||
|
|
@ -52,6 +55,7 @@ internal class TokenDetailsStateFactory(
|
|||
TokenDetailsSkeletonStateConverter(
|
||||
clickIntents = clickIntents,
|
||||
featureToggles = featureToggles,
|
||||
stakingAvailabilityProvider = stakingAvailabilityProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -98,6 +102,12 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
private val stakingStateConverter by lazy {
|
||||
TokenStakingStateConverter(
|
||||
currentStateProvider = currentStateProvider,
|
||||
)
|
||||
}
|
||||
|
||||
fun getInitialState(screenArgument: CryptoCurrency): TokenDetailsState {
|
||||
return skeletonStateConverter.convert(value = screenArgument)
|
||||
}
|
||||
|
|
@ -201,6 +211,12 @@ internal class TokenDetailsStateFactory(
|
|||
)
|
||||
}
|
||||
|
||||
fun getStateWithStaking(stakingEither: Either<Throwable, StakingEntryInfo>): TokenDetailsState {
|
||||
return currentStateProvider().copy(
|
||||
stakingBlockState = stakingStateConverter.convert(stakingEither),
|
||||
)
|
||||
}
|
||||
|
||||
fun getRefreshingState(): TokenDetailsState {
|
||||
return refreshStateConverter.convert(true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.ui.utils.BigDecimalFormatter
|
||||
import com.tangem.domain.staking.model.StakingEntryInfo
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal class TokenStakingStateConverter(
|
||||
private val currentStateProvider: Provider<TokenDetailsState>,
|
||||
) : Converter<Either<Throwable, StakingEntryInfo>, StakingBlockState> {
|
||||
|
||||
override fun convert(value: Either<Throwable, StakingEntryInfo>): StakingBlockState {
|
||||
value.fold(
|
||||
ifLeft = {
|
||||
return StakingBlockState.Error(
|
||||
iconState = currentStateProvider().tokenInfoBlockState.iconState,
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
return StakingBlockState.Content(
|
||||
interestRate = BigDecimalFormatter.formatPercent(
|
||||
percent = it.interestRate,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
periodInDays = it.periodInDays,
|
||||
tokenSymbol = it.tokenSymbol,
|
||||
iconState = currentStateProvider().tokenInfoBlockState.iconState,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -42,8 +42,10 @@ import com.tangem.core.ui.extensions.resolveReference
|
|||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.*
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsBalanceBlock
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsDialogs
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
|
||||
|
|
@ -133,6 +135,14 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
|
|||
)
|
||||
}
|
||||
|
||||
if (state.isStakingAvailable) {
|
||||
item(
|
||||
key = StakingBlockState::class.java,
|
||||
contentType = StakingBlockState::class.java,
|
||||
content = { TokenStakingBlock(modifier = itemModifier, state = state.stakingBlockState) },
|
||||
)
|
||||
}
|
||||
|
||||
swapTransactionsItems(
|
||||
state.swapTxs,
|
||||
itemModifier,
|
||||
|
|
|
|||
|
|
@ -21,26 +21,21 @@ import coil.request.ImageRequest
|
|||
import com.tangem.core.ui.components.CircleShimmer
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.ImageBackgroundContrastChecker
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenInfoBlockState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
internal fun CurrencyIcon(
|
||||
icon: TokenInfoBlockState.IconState,
|
||||
alpha: Float,
|
||||
colorFilter: ColorFilter?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
internal fun CurrencyIcon(icon: IconState, alpha: Float, colorFilter: ColorFilter?, modifier: Modifier = Modifier) {
|
||||
when (icon) {
|
||||
is TokenInfoBlockState.IconState.CoinIcon -> CoinIcon(
|
||||
is IconState.CoinIcon -> CoinIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
fallbackResId = icon.fallbackResId,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
is TokenInfoBlockState.IconState.TokenIcon -> TokenIcon(
|
||||
is IconState.TokenIcon -> TokenIcon(
|
||||
modifier = modifier,
|
||||
url = icon.url,
|
||||
alpha = alpha,
|
||||
|
|
@ -54,7 +49,7 @@ internal fun CurrencyIcon(
|
|||
)
|
||||
},
|
||||
)
|
||||
is TokenInfoBlockState.IconState.CustomTokenIcon -> CustomTokenIcon(
|
||||
is IconState.CustomTokenIcon -> CustomTokenIcon(
|
||||
modifier = modifier,
|
||||
tint = icon.tint,
|
||||
background = icon.background,
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.tooling.preview.Preview
|
||||
|
|
@ -20,14 +18,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
|
||||
import com.tangem.core.ui.utils.GrayscaleColorFilter
|
||||
import com.tangem.core.ui.utils.NORMAL_ALPHA
|
||||
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(
|
||||
|
|
@ -132,10 +129,10 @@ 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) })
|
||||
private data class ExtractedTokenNetworkText(
|
||||
val normalText: String,
|
||||
val boldText: String,
|
||||
)
|
||||
|
||||
@Preview
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,206 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.GRAY_SCALE_ALPHA
|
||||
import com.tangem.core.ui.utils.GrayscaleColorFilter
|
||||
import com.tangem.core.ui.utils.NORMAL_ALPHA
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.IconState
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockState
|
||||
import com.tangem.features.tokendetails.impl.R
|
||||
|
||||
/**
|
||||
* Token staking block
|
||||
*
|
||||
* @param state component state
|
||||
* @param modifier modifier
|
||||
*/
|
||||
@Composable
|
||||
internal fun TokenStakingBlock(state: StakingBlockState, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.background.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = TangemTheme.dimens.size72)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Content(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: StakingBlockState, modifier: Modifier = Modifier) {
|
||||
AnimatedContent(
|
||||
modifier = modifier.heightIn(min = TangemTheme.dimens.size60),
|
||||
targetState = state,
|
||||
contentAlignment = Alignment.CenterStart,
|
||||
label = "Update the content",
|
||||
) { stakingBlockState ->
|
||||
when (stakingBlockState) {
|
||||
is StakingBlockState.Content -> {
|
||||
StakingContent(
|
||||
stakingBlockState = stakingBlockState,
|
||||
iconState = stakingBlockState.iconState,
|
||||
)
|
||||
}
|
||||
is StakingBlockState.Loading -> {
|
||||
StakingLoading(
|
||||
iconState = stakingBlockState.iconState,
|
||||
)
|
||||
}
|
||||
is StakingBlockState.Error -> Row {} // TODO staking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StakingContent(stakingBlockState: StakingBlockState.Content, iconState: IconState) {
|
||||
Column {
|
||||
Row {
|
||||
val (alpha, colorFilter) = remember(iconState.isGrayscale) {
|
||||
if (iconState.isGrayscale) {
|
||||
GRAY_SCALE_ALPHA to GrayscaleColorFilter
|
||||
} else {
|
||||
NORMAL_ALPHA to null
|
||||
}
|
||||
}
|
||||
CurrencyIcon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.align(Alignment.CenterVertically),
|
||||
icon = iconState,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
SpacerW8()
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.token_details_staking_block_title,
|
||||
stakingBlockState.interestRate,
|
||||
),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size4))
|
||||
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.token_details_staking_block_subtitle,
|
||||
stakingBlockState.tokenSymbol,
|
||||
stakingBlockState.periodInDays,
|
||||
),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
|
||||
}
|
||||
}
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Stake",
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StakingLoading(iconState: IconState) {
|
||||
Column {
|
||||
Row {
|
||||
val (alpha, colorFilter) = remember(iconState.isGrayscale) {
|
||||
if (iconState.isGrayscale) {
|
||||
GRAY_SCALE_ALPHA to GrayscaleColorFilter
|
||||
} else {
|
||||
NORMAL_ALPHA to null
|
||||
}
|
||||
}
|
||||
CurrencyIcon(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size20)
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.align(Alignment.CenterVertically),
|
||||
icon = iconState,
|
||||
alpha = alpha,
|
||||
colorFilter = colorFilter,
|
||||
)
|
||||
SpacerW8()
|
||||
Column {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size20),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size4))
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(TangemTheme.dimens.size20),
|
||||
)
|
||||
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
|
||||
}
|
||||
}
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = "Loading", // TODO staking
|
||||
onClick = { /* [REDACTED_TODO_COMMENT] */ },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// region Preview
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_TokenStakingBlock(
|
||||
@PreviewParameter(StakingBlockStateProvider::class)
|
||||
state: StakingBlockState,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
TokenStakingBlock(state = state)
|
||||
}
|
||||
}
|
||||
|
||||
private class StakingBlockStateProvider : CollectionPreviewParameterProvider<StakingBlockState>(
|
||||
collection = listOf(
|
||||
StakingBlockState.Content(
|
||||
iconState = iconState,
|
||||
interestRate = "10",
|
||||
periodInDays = 4,
|
||||
tokenSymbol = "SOL",
|
||||
),
|
||||
StakingBlockState.Loading(iconState = iconState),
|
||||
StakingBlockState.Error(iconState = iconState),
|
||||
),
|
||||
)
|
||||
|
||||
private val iconState = IconState.TokenIcon(
|
||||
url = "https://s3.eu-central-1.amazonaws.com/tangem.api/coins/large/stellar.png",
|
||||
fallbackTint = Color.Cyan,
|
||||
fallbackBackground = Color.Blue,
|
||||
isGrayscale = false,
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -27,6 +27,9 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.demo.IsDemoCardUseCase
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.staking.GetStakingAvailabilityUseCase
|
||||
import com.tangem.domain.staking.GetStakingEntryInfoUseCase
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction
|
||||
import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo
|
||||
|
|
@ -97,6 +100,8 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val shouldShowSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
|
||||
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
|
||||
private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase,
|
||||
private val getStakingEntryInfoUseCase: GetStakingEntryInfoUseCase,
|
||||
private val getStakingAvailabilityUseCase: GetStakingAvailabilityUseCase,
|
||||
private val swapRepository: SwapRepository,
|
||||
private val swapTransactionRepository: SwapTransactionRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
|
|
@ -107,8 +112,8 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val analyticsEventsHandler: AnalyticsEventHandler,
|
||||
private val hapticManager: HapticManager,
|
||||
private val clipboardManager: ClipboardManager,
|
||||
tokenDetailsFeatureToggles: TokenDetailsFeatureToggles,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
featureToggles: TokenDetailsFeatureToggles,
|
||||
deepLinksRegistry: DeepLinksRegistry,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
|
||||
|
|
@ -137,10 +142,13 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
private val stateFactory = TokenDetailsStateFactory(
|
||||
currentStateProvider = Provider { uiState },
|
||||
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
|
||||
stakingAvailabilityProvider = Provider {
|
||||
getStakingAvailabilityUseCase.invoke(cryptoCurrency.network.id.value)
|
||||
},
|
||||
clickIntents = this,
|
||||
symbol = cryptoCurrency.symbol,
|
||||
decimals = cryptoCurrency.decimals,
|
||||
featureToggles = featureToggles,
|
||||
featureToggles = tokenDetailsFeatureToggles,
|
||||
)
|
||||
|
||||
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
|
|
@ -207,6 +215,7 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
subscribeOnCurrencyStatusUpdates()
|
||||
subscribeOnExchangeTransactionsUpdates()
|
||||
updateTxHistory(refresh = false, showItemsLoading = true)
|
||||
updateStakingInfo()
|
||||
}
|
||||
|
||||
private fun handleBalanceHiding(owner: LifecycleOwner) {
|
||||
|
|
@ -358,6 +367,16 @@ internal class TokenDetailsViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateStakingInfo() {
|
||||
viewModelScope.launch(dispatchers.io) {
|
||||
val stakingAvailability = getStakingAvailabilityUseCase(cryptoCurrency.network.id.value)
|
||||
if (stakingAvailability is StakingAvailability.Available) {
|
||||
val stakingInfo = getStakingEntryInfoUseCase(stakingAvailability.integrationId)
|
||||
uiState = stateFactory.getStateWithStaking(stakingInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTopBarMenu() {
|
||||
viewModelScope.launch(dispatchers.main) {
|
||||
uiState = stateFactory.getStateWithUpdatedMenu(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue