Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-17 12:49:55 +04:00
parent 7f4454c8d2
commit 98d0d9c75e
39 changed files with 266 additions and 3536 deletions

View file

@ -15,13 +15,11 @@ import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.model.details.NavigationAction
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel
import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreenLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.ChooseAddressBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.CloreMigrationBottomSheetComponent
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.bottomsheet.DynamicAddressesBottomSheetComponent
@ -154,31 +152,18 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
val txHistoryDetails by txHistoryDetailsSlot.subscribeAsState()
NavigationBar3ButtonsScrim()
if (LocalRedesignEnabled.current) {
val tokenDetailsUM by model.redesignUiState.collectAsStateWithLifecycle()
val tokenDetailsUM by model.redesignUiState.collectAsStateWithLifecycle()
TokenDetailsScreen(
tokenDetailsUM = tokenDetailsUM,
tokenMarketBlockComponent = tokenMarketBlockComponent,
yieldSupplyComponent = yieldSupplyComponent,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
ratingComponent = ratingSlotState.child?.instance,
marketingBannerComponent = marketingBannerComponent,
modifier = modifier,
)
} else {
val state by model.uiState.collectAsStateWithLifecycle()
TokenDetailsScreenLegacy(
state = state,
tokenMarketBlockComponent = tokenMarketBlockComponent,
txHistoryComponent = txHistoryComponent,
yieldSupplyComponent = yieldSupplyComponent,
expressTransactionsComponent = expressTransactionsComponent,
ratingComponent = ratingSlotState.child?.instance,
marketingBannerComponent = marketingBannerComponent,
)
}
TokenDetailsScreen(
tokenDetailsUM = tokenDetailsUM,
tokenMarketBlockComponent = tokenMarketBlockComponent,
yieldSupplyComponent = yieldSupplyComponent,
txHistoryComponent = txHistoryComponent,
expressTransactionsComponent = expressTransactionsComponent,
ratingComponent = ratingSlotState.child?.instance,
marketingBannerComponent = marketingBannerComponent,
modifier = modifier,
)
bottomSheet.child?.instance?.BottomSheet()
txHistoryDetails.child?.instance?.BottomSheet()

View file

@ -33,7 +33,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.ds.image.DeviceIconUM
import com.tangem.core.ui.extensions.TextReference
@ -184,7 +183,6 @@ internal class TokenDetailsModel @Inject constructor(
private val getWalletIconUseCase: GetWalletIconUseCase,
private val walletIconUMConverter: WalletIconUMConverter,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val redesignStateController: TokenDetailsStateController,
private val swapFeedbackUseCase: SwapFeedbackUseCase,
private val quickTopUpBlockFactory: QuickTopUpBlockFactory,
@ -346,10 +344,8 @@ internal class TokenDetailsModel @Inject constructor(
uiState.value = stateFactory.getStateWithUpdatedHidden(
isBalanceHidden = settings.isBalanceHidden,
)
if (designFeatureToggles.isRedesignEnabled) {
redesignStateController.update { state ->
state.copy(isBalanceHidden = settings.isBalanceHidden)
}
redesignStateController.update { state ->
state.copy(isBalanceHidden = settings.isBalanceHidden)
}
}
.launchIn(modelScope)
@ -366,38 +362,36 @@ internal class TokenDetailsModel @Inject constructor(
latestTokenActions = state.states
sendButtonsEvents(state.states)
uiState.value = stateFactory.getManageButtonsState(actions = state.states)
if (designFeatureToggles.isRedesignEnabled) {
val networkSource = currencyStatus.value.sources.networkSource
redesignStateController.update(
UpdateActionButtonsTransformer(
actions = state.states,
clickIntents = this@TokenDetailsModel,
),
)
redesignStateController.update(
UpdateAddFundsTransformer(
actions = state.states,
networkSource = networkSource,
clickIntents = this@TokenDetailsModel,
onActionDispatched = bottomSheetNavigation::dismiss,
),
)
redesignStateController.update(
UpdateTransferTransformer(
actions = state.states,
networkSource = networkSource,
clickIntents = this@TokenDetailsModel,
analyticsEventHandler = analyticsEventsHandler,
onActionDispatched = bottomSheetNavigation::dismiss,
),
)
redesignStateController.update(
UpdateZeroBalanceActionsTransformer(
actions = state.states,
clickIntents = this@TokenDetailsModel,
),
)
}
val networkSource = currencyStatus.value.sources.networkSource
redesignStateController.update(
UpdateActionButtonsTransformer(
actions = state.states,
clickIntents = this@TokenDetailsModel,
),
)
redesignStateController.update(
UpdateAddFundsTransformer(
actions = state.states,
networkSource = networkSource,
clickIntents = this@TokenDetailsModel,
onActionDispatched = bottomSheetNavigation::dismiss,
),
)
redesignStateController.update(
UpdateTransferTransformer(
actions = state.states,
networkSource = networkSource,
clickIntents = this@TokenDetailsModel,
analyticsEventHandler = analyticsEventsHandler,
onActionDispatched = bottomSheetNavigation::dismiss,
),
)
redesignStateController.update(
UpdateZeroBalanceActionsTransformer(
actions = state.states,
clickIntents = this@TokenDetailsModel,
),
)
}
.flowOn(dispatchers.main)
.launchIn(modelScope)
@ -1502,7 +1496,6 @@ internal class TokenDetailsModel @Inject constructor(
}
private fun initRedesign() {
if (!designFeatureToggles.isRedesignEnabled) return
initRedesignState()
observeRedesignBalance()
updateRedesignTopBarMenu()

View file

@ -1,261 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
import com.tangem.features.rating.RatingComponent
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.components.marketprice.MarketPriceBlock
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemeRedesign
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
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.TokenDetailsBalanceBlockLegacy
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlockLegacy
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.marketing.api.MarketingBannerComponent
import com.tangem.features.tokendetails.ExpressTransactionsComponent
import com.tangem.features.txhistory.component.TxHistoryComponent
import com.tangem.features.txhistory.entity.TxHistoryItemsUM
import com.tangem.features.txhistory.entity.TxHistoryUM
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
// TODO: Split to blocks [REDACTED_JIRA]
@Suppress("LongMethod", "CyclomaticComplexMethod", "LongParameterList")
@Composable
internal fun TokenDetailsScreenLegacy(
state: TokenDetailsState,
tokenMarketBlockComponent: TokenMarketBlockComponent?,
txHistoryComponent: TxHistoryComponent,
yieldSupplyComponent: YieldSupplyComponent,
expressTransactionsComponent: ExpressTransactionsComponent,
ratingComponent: RatingComponent?,
marketingBannerComponent: MarketingBannerComponent,
) {
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
Scaffold(
topBar = { TokenDetailsTopAppBar(config = state.topAppBarConfig) },
contentWindowInsets = ScaffoldDefaults.contentWindowInsets.exclude(WindowInsets.navigationBars),
containerColor = TangemTheme.colors.background.secondary,
) { scaffoldPaddings ->
val listState = rememberLazyListState()
val txHistoryComponentState by txHistoryComponent.legacyTxHistoryState.collectAsStateWithLifecycle()
val expressState by expressTransactionsComponent.state.collectAsStateWithLifecycle()
val betweenItemsPadding = TangemTheme.dimens.spacing12
val horizontalPadding = TangemTheme.dimens.spacing16
val itemModifier = Modifier
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
TangemPullToRefreshContainer(
config = state.pullToRefreshConfig,
modifier = Modifier.padding(scaffoldPaddings),
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.testTag(TokenDetailsScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = PaddingValues(
bottom = TangemTheme.dimens.spacing16 + bottomBarHeight,
),
) {
item {
TokenInfoBlock(
modifier = Modifier.padding(horizontal = horizontalPadding),
state = state.tokenInfoBlockState,
)
}
item {
TokenDetailsBalanceBlockLegacy(
modifier = itemModifier,
isBalanceHidden = state.isBalanceHidden,
state = state.tokenBalanceBlockState,
)
}
items(
items = state.notifications,
key = { it::class.java },
contentType = { it.config::class.java },
itemContent = { notification ->
Notification(
modifier = itemModifier.animateItem(),
config = notification.config,
iconTint = when (notification) {
is TokenDetailsNotification.Informational -> TangemTheme.colors.icon.accent
is TokenDetailsNotification.UsedOutdatedData -> TangemTheme.colors.text.attention
else -> null
},
)
},
)
when {
tokenMarketBlockComponent != null -> {
item(
key = TokenMarketBlockComponent::class.java,
contentType = TokenMarketBlockComponent::class.java,
content = { tokenMarketBlockComponent.Content(modifier = itemModifier) },
)
}
state.isMarketPriceAvailable -> {
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,
content = {
MarketPriceBlock(
modifier = itemModifier,
state = state.marketPriceBlockState,
)
},
)
}
}
if (state.stakingBlocksState != null) {
item(
key = StakingBlockUM::class.java,
contentType = StakingBlockUM::class.java,
content = {
TokenStakingBlockLegacy(
state = state.stakingBlocksState,
isBalanceHidden = state.isBalanceHidden,
modifier = itemModifier,
)
},
)
}
item {
yieldSupplyComponent.Content(modifier = itemModifier)
}
item(key = "marketing_banner_block") {
TangemThemeRedesign {
marketingBannerComponent.Content(modifier = itemModifier)
}
}
with(expressTransactionsComponent) {
expressTransactionsContentLegacy(
state = expressState.transactionsToDisplay,
modifier = itemModifier,
)
}
state.quickTopUpBlock?.let { quickTopUpBlock ->
item(key = "quick_top_up_block") {
TangemThemeRedesign {
QuickTopUpBlock(
state = quickTopUpBlock,
modifier = itemModifier,
)
}
}
}
with(txHistoryComponent) {
txHistoryContentLegacy(listState = listState, state = txHistoryComponentState)
}
}
}
expressState.bottomSheetSlot?.content(
ratingComponent?.let { comp -> { comp.Content(modifier = Modifier.fillMaxWidth()) } },
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun TokenDetailsScreenPreview(
@PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState,
) {
TangemThemePreview {
TokenDetailsScreenLegacy(
state = state,
tokenMarketBlockComponent = null,
txHistoryComponent = object : TxHistoryComponent {
override val legacyTxHistoryState: StateFlow<TxHistoryUM> = MutableStateFlow(
value = TxHistoryUM.Empty(isBalanceHidden = false, onExploreClick = {}),
)
override val txHistoryState: StateFlow<TxHistoryItemsUM> = MutableStateFlow(
value = TxHistoryItemsUM.Empty(isBalanceHidden = false, onExploreClick = {}),
)
override fun LazyListScope.txHistoryContentLegacy(listState: LazyListState, state: TxHistoryUM) = Unit
override fun LazyListScope.txHistoryContent(listState: LazyListState, state: TxHistoryItemsUM) = Unit
},
yieldSupplyComponent = object : YieldSupplyComponent {
@Composable
override fun Content(modifier: Modifier) {
}
},
expressTransactionsComponent = PreviewExpressTransactionsComponent,
ratingComponent = null,
marketingBannerComponent = object : MarketingBannerComponent {
@Composable
override fun Content(modifier: Modifier) = Unit
},
)
}
}
private val PreviewExpressTransactionsComponent = object : ExpressTransactionsComponent {
override val state: StateFlow<ExpressTransactionsBlockState> = MutableStateFlow(
ExpressTransactionsBlockState(
transactions = persistentListOf(),
transactionsToDisplay = persistentListOf(),
bottomSheetSlot = null,
),
)
override fun LazyListScope.expressTransactionsContentLegacy(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
override fun LazyListScope.expressTransactionsContent(
state: PersistentList<ExpressTransactionStateUM>,
modifier: Modifier,
) = Unit
}
private class TokenDetailsScreenParameterProvider : CollectionPreviewParameterProvider<TokenDetailsState>(
collection = listOf(
TokenDetailsPreviewData.tokenDetailsState_1,
TokenDetailsPreviewData.tokenDetailsState_2,
TokenDetailsPreviewData.tokenDetailsState_3,
),
)
// endregion Preview

View file

@ -1,281 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components
import android.content.res.Configuration
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Surface
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.buttons.HorizontalActionChips
import com.tangem.core.ui.components.buttons.segmentedbutton.SegmentedButtons
import com.tangem.core.ui.components.text.TextAnimatedCounter
import com.tangem.core.ui.components.text.applyBladeBrush
import com.tangem.core.ui.extensions.orMaskWithStars
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
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.TokenDetailsBalanceBlockState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsYieldSupplyState
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.toImmutableList
@Suppress("DestructuringDeclarationWithTooManyEntries")
@Composable
internal fun TokenDetailsBalanceBlockLegacy(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
Surface(
modifier = modifier.fillMaxWidth(),
shape = TangemTheme.shapes.roundedCornersXMedium,
color = TangemTheme.colors.background.primary,
) {
val spacing4 = TangemTheme.dimens.spacing4
val spacing10 = TangemTheme.dimens.spacing10
val spacing12 = TangemTheme.dimens.spacing12
ConstraintLayout(
modifier = Modifier.fillMaxWidth(),
) {
val (balanceTitle, toggleButtons, fiatBalance, cryptoBalance, actionChips) = createRefs()
Text(
text = stringResourceSafe(id = R.string.common_balance_title),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.constrainAs(balanceTitle) {
top.linkTo(anchor = parent.top, margin = spacing12)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
BalanceButtons(
state = state,
modifier = Modifier.constrainAs(toggleButtons) {
top.linkTo(anchor = parent.top)
end.linkTo(anchor = parent.end, margin = spacing10)
},
)
FiatBalance(
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.constrainAs(fiatBalance) {
top.linkTo(anchor = balanceTitle.bottom, margin = spacing4)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
CryptoBalance(
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.constrainAs(cryptoBalance) {
top.linkTo(anchor = fiatBalance.bottom, margin = spacing4)
start.linkTo(anchor = parent.start, margin = spacing12)
},
)
HorizontalActionChips(
buttons = state.actionButtons.map(TokenDetailsActionButton::config).toImmutableList(),
modifier = Modifier.constrainAs(actionChips) {
top.linkTo(anchor = cryptoBalance.bottom, margin = spacing12)
start.linkTo(anchor = parent.start)
end.linkTo(anchor = parent.end)
bottom.linkTo(anchor = parent.bottom, margin = spacing12)
},
containerColor = TangemTheme.colors.background.primary,
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
)
}
}
}
@Composable
private fun FiatBalance(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer(
modifier = modifier.size(
width = TangemTheme.dimens.size102,
height = TangemTheme.dimens.size32,
),
)
is TokenDetailsBalanceBlockState.Content -> if (state.displayYieldSupplyFiatBalance != null &&
!isBalanceHidden
) {
TextAnimatedCounter(
modifier = modifier,
text = state.displayYieldSupplyFiatBalance,
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
} else {
Text(
modifier = modifier,
text = (state.displayYieldSupplyFiatBalance ?: state.displayFiatBalance).orMaskWithStars(
isBalanceHidden,
),
style = TangemTheme.typography.h2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.primary1,
),
)
}
is TokenDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
private fun CryptoBalance(
state: TokenDetailsBalanceBlockState,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
when (state) {
is TokenDetailsBalanceBlockState.Loading -> RectangleShimmer(
modifier = modifier.size(
width = TangemTheme.dimens.size70,
height = TangemTheme.dimens.size16,
),
)
is TokenDetailsBalanceBlockState.Content -> {
Crossfade(
modifier = modifier,
targetState = state.yieldSupplyState,
) { yieldSupplyState ->
when (yieldSupplyState) {
is TokenDetailsYieldSupplyState.Active -> {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_exchange_horizontal_24),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
TextAnimatedCounter(
text = (state.displayYieldSupplyCryptoBalance ?: state.displayCryptoBalance)
.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.tertiary,
),
)
Icon(
modifier = Modifier
.size(TangemTheme.dimens.size16)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = ripple(bounded = false),
onClick = { yieldSupplyState.yieldInfoClick() },
),
painter = painterResource(id = R.drawable.ic_information_24),
tint = TangemTheme.colors.icon.inactive,
contentDescription = null,
)
}
}
is TokenDetailsYieldSupplyState.Empty -> Text(
text = state.displayCryptoBalance.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2.applyBladeBrush(
isEnabled = state.isBalanceFlickering,
textColor = TangemTheme.colors.text.tertiary,
),
)
}
}
}
is TokenDetailsBalanceBlockState.Error -> Text(
modifier = modifier,
text = DASH_SIGN.orMaskWithStars(isBalanceHidden),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
@Composable
private fun BalanceButtons(state: TokenDetailsBalanceBlockState, modifier: Modifier = Modifier) {
if (state !is TokenDetailsBalanceBlockState.Content || !state.isBalanceSelectorEnabled) return
SegmentedButtons(
config = state.balanceSegmentedButtonConfig,
onClick = state.onBalanceSelect,
showIndication = false,
modifier = modifier
.padding(top = TangemTheme.dimens.spacing11)
.width(IntrinsicSize.Min),
) { config ->
val style = if (state.selectedBalanceType == config.type) {
TangemTheme.typography.caption1
} else {
TangemTheme.typography.caption2
}
Text(
text = config.title.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = style,
maxLines = 1,
modifier = Modifier
.padding(
horizontal = TangemTheme.dimens.spacing6,
vertical = TangemTheme.dimens.spacing4,
)
.align(Alignment.Center),
)
}
}
@Preview(widthDp = 328, heightDp = 152)
@Preview(widthDp = 328, heightDp = 152, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_TokenDetailsBalanceBlock(
@PreviewParameter(TokenDetailsBalanceBlockStateProvider::class) state: TokenDetailsBalanceBlockState,
) {
TangemThemePreview {
TokenDetailsBalanceBlockLegacy(state = state, isBalanceHidden = false)
}
}
private class TokenDetailsBalanceBlockStateProvider : CollectionPreviewParameterProvider<TokenDetailsBalanceBlockState>(
collection = listOf(
TokenDetailsPreviewData.balanceLoading,
TokenDetailsPreviewData.balanceContent,
TokenDetailsPreviewData.balanceContent.copy(isBalanceFlickering = true),
TokenDetailsPreviewData.balanceContent.copy(
yieldSupplyState = TokenDetailsYieldSupplyState.Active({}),
),
TokenDetailsPreviewData.balanceError,
),
)

View file

@ -1,198 +0,0 @@
package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking
import android.content.res.Configuration
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag
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.RectangleShimmer
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SpacerW8
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.TokenDetailsScreenTestTags
import com.tangem.core.ui.utils.getGreyScaleColorFilter
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingAvailableBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingBalanceBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingLoadingBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.TokenDetailsPreviewData.stakingTemporaryUnavailableBlock
import com.tangem.feature.tokendetails.presentation.tokendetails.state.StakingBlockUM
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.CurrencyIcon
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.StringsSigns
/**
* Token staking block
*
* @param state component state
* @param isBalanceHidden whether to hide balance
* @param modifier modifier
*/
@Composable
internal fun TokenStakingBlockLegacy(state: StakingBlockUM, isBalanceHidden: Boolean, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(TangemTheme.colors.background.primary)
.clickable(
enabled = state is StakingBlockUM.Staked,
interactionSource = remember { MutableInteractionSource() },
indication = ripple(),
onClick = { (state as? StakingBlockUM.Staked)?.onStakeClicked?.invoke() },
)
.fillMaxWidth()
.padding(all = TangemTheme.dimens.spacing12),
) {
AnimatedContent(
targetState = state,
contentAlignment = Alignment.CenterStart,
label = "Staking block animation",
) { stakingBlock ->
when (stakingBlock) {
is StakingBlockUM.TemporaryUnavailable -> StakingTemporaryUnavailableBlock()
is StakingBlockUM.Loading -> StakingLoading()
is StakingBlockUM.Staked -> StakingBalanceBlock(
state = stakingBlock,
isBalanceHidden = isBalanceHidden,
)
is StakingBlockUM.StakeAvailable -> StakingAvailableContent(
state = stakingBlock,
)
}
}
}
}
@Composable
private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth()
.testTag(TokenDetailsScreenTestTags.STAKING_AVAILABLE_BLOCK),
) {
Row {
val (alpha, colorFilter) = remember(state.iconState.isGrayscale) {
getGreyScaleColorFilter(state.iconState.isGrayscale)
}
CurrencyIcon(
modifier = Modifier
.size(TangemTheme.dimens.size20)
.clip(TangemTheme.shapes.roundedCorners8)
.align(Alignment.CenterVertically)
.testTag(TokenDetailsScreenTestTags.STAKING_CURRENCY_ICON),
icon = state.iconState,
alpha = alpha,
colorFilter = colorFilter,
)
SpacerW8()
Column {
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4)) {
Text(
text = state.titleText.resolveReference(),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
modifier = Modifier.testTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TITLE),
)
if (state.isBetaMode) {
Text(
text = StringsSigns.DOT,
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
Text(
text = stringResourceSafe(R.string.beta_mode_warning_title),
color = TangemTheme.colors.text.primary1,
style = TangemTheme.typography.subtitle2,
)
}
}
Spacer(modifier = Modifier.size(TangemTheme.dimens.size4))
Text(
text = state.subtitleText.resolveReference(),
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.body2,
modifier = Modifier.testTag(TokenDetailsScreenTestTags.STAKING_SERVICE_TEXT),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
}
}
SecondaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResourceSafe(id = R.string.common_stake),
enabled = state.isEnabled,
onClick = state.onStakeClicked,
)
}
}
@Composable
private fun StakingLoading(modifier: Modifier = Modifier) {
Column(
modifier = modifier.fillMaxWidth(),
) {
Row {
Column {
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size20),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size36),
)
Spacer(modifier = Modifier.size(TangemTheme.dimens.size8))
}
}
RectangleShimmer(
modifier = Modifier
.fillMaxWidth()
.height(TangemTheme.dimens.size48),
)
}
}
// 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: StakingBlockUM,
) {
TangemThemePreview {
TokenStakingBlockLegacy(
state = state,
isBalanceHidden = false,
)
}
}
private class StakingBlockStateProvider : CollectionPreviewParameterProvider<StakingBlockUM>(
collection = listOf(
stakingLoadingBlock,
stakingAvailableBlock,
stakingTemporaryUnavailableBlock,
stakingBalanceBlock,
),
)
// endregion Preview