Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-08 19:51:04 +04:00
parent f57a7ecac9
commit f6b0bf8dd5
24 changed files with 80 additions and 124 deletions

View file

@ -1,25 +0,0 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:AddStakingNotificationsTransformer.kt$AddStakingNotificationsTransformer$val showNotification = sendingAmount + feeAmount &gt; balance</ID>
<ID>BooleanPropertyNaming:AmountCurrencyChangeStateTransformer.kt$AmountCurrencyChangeStateTransformer$private val value: Boolean</ID>
<ID>BooleanPropertyNaming:StakingUiState.kt$StakingStates.InitialInfoState.Data$val showBanner: Boolean</ID>
<ID>BooleanPropertyNaming:StakingUiState.kt$StakingUiState$val showColdWalletInteractionIcon: Boolean</ID>
<ID>CastNullableToNonNullableType:SetApprovalBottomSheetInProgressTransformer.kt$SetApprovalBottomSheetInProgressTransformer$as</ID>
<ID>CastNullableToNonNullableType:SetApprovalBottomSheetTypeChangeTransformer.kt$SetApprovalBottomSheetTypeChangeTransformer$as</ID>
<ID>MultilineLambdaItParameter:AddStakingNotificationsTransformer.kt$AddStakingNotificationsTransformer${ it is StakingNotification.Error || it is NotificationUM.Error || it is NotificationUM.Warning.NetworkFeeUnreachable || it is StakingNotification.Warning.TransactionInProgress || it is StakingNotification.Warning.InitializeTonAccount }</ID>
<ID>MultilineLambdaItParameter:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler${ val isNetwork = it.network.backendId.equals(networkId, ignoreCase = true) val isCurrency = it.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true isNetwork &amp;&amp; isCurrency }</ID>
<ID>MultilineLambdaItParameter:StakingFeeBlock.kt${ if (it == FeeState.Error) { Text( text = DASH_SIGN, color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.body1, ) } }</ID>
<ID>MultilineLambdaItParameter:StakingFeeBlock.kt${ if (it == FeeState.Loading) { RectangleShimmer( radius = TangemTheme.dimens.radius3, modifier = Modifier.size( height = TangemTheme.dimens.size24, width = TangemTheme.dimens.size90, ), ) } }</ID>
<ID>MultilineLambdaItParameter:StakingInfoNotificationsFactory.kt$StakingInfoNotificationsFactory${ it.type == BalanceType.PREPARING || it.type == BalanceType.STAKED || it.type == BalanceType.LOCKED }</ID>
<ID>MultilineLambdaItParameter:StakingStateController.kt$StakingStateController${ it.copy( showColdWalletInteractionIcon = userWallet is UserWallet.Cold, ) }</ID>
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$networkId</ID>
<ID>NullableToStringCall:DefaultStakingDeepLinkHandler.kt$DefaultStakingDeepLinkHandler$$tokenId</ID>
<ID>PropertyUsedBeforeDeclaration:StakingFeeBlock.kt$FeeBlockPreviewProvider$contentState</ID>
<ID>PropertyUsedBeforeDeclaration:StakingStateController.kt$StakingStateController$uiState</ID>
<ID>UnnecessaryEventHandlerParameter:StakingInitialInfoContent.kt$onClick: (BalanceState) -&gt; Unit</ID>
<ID>UnnecessaryLet:StakingTosText.kt$let { onTextClick(PRIVACY_POLICY_URL) }</ID>
<ID>UnnecessaryLet:StakingTosText.kt$let { onTextClick(TERMS_OF_USE_URL) }</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -53,9 +53,9 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
params = MultiWalletCryptoCurrenciesProducer.Params(selectedUserWalletId),
)
.orEmpty()
.firstOrNull {
val isNetwork = it.network.backendId.equals(networkId, ignoreCase = true)
val isCurrency = it.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true
.firstOrNull { currency ->
val isNetwork = currency.network.backendId.equals(networkId, ignoreCase = true)
val isCurrency = currency.id.rawCurrencyId?.value?.equals(tokenId, ignoreCase = true) == true
isNetwork && isCurrency
}
@ -63,8 +63,8 @@ internal class DefaultStakingDeepLinkHandler @AssistedInject constructor(
TangemLogger.e(
"""
Could not get crypto currency for
|- $NETWORK_ID_KEY: $networkId
|- $TOKEN_ID_KEY: $tokenId
|- $NETWORK_ID_KEY: ${networkId.orEmpty()}
|- $TOKEN_ID_KEY: ${tokenId.orEmpty()}
""".trimIndent(),
)
return@launch

View file

@ -1,5 +1,6 @@
package com.tangem.features.staking.impl.presentation.model
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.amountScreen.AmountScreenClickIntents
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.notifications.NotificationUM
@ -11,6 +12,7 @@ import java.math.BigDecimal
// TODO split this interface to click intents and other interaction events
@Suppress("TooManyFunctions")
@Immutable
internal interface StakingClickIntents : AmountScreenClickIntents {
fun onBackClick()

View file

@ -26,19 +26,19 @@ internal class StakingStateController @Inject constructor(
private val holdToConfirmButtonFeatureToggles: HoldToConfirmButtonFeatureToggles,
) {
val value: StakingUiState get() = uiState.value
private val mutableUiState: MutableStateFlow<StakingUiState> = MutableStateFlow(value = getInitialState())
val uiState: StateFlow<StakingUiState> get() = mutableUiState.asStateFlow()
val value: StakingUiState get() = uiState.value
private val buttonsTransformer = SetButtonsStateTransformer(urlOpener)
private val titleTransformer = SetTitleTransformer
fun initializeWithUserWallet(userWallet: UserWallet) {
mutableUiState.update { state ->
state.copy(
showColdWalletInteractionIcon = userWallet.isColdWallet,
isColdWalletInteractionIconVisible = userWallet.isColdWallet,
shouldShowHoldToConfirmButton = holdToConfirmButtonFeatureToggles.isHoldToConfirmEnabled &&
userWallet.isHotWallet,
)
@ -89,7 +89,7 @@ internal class StakingStateController @Inject constructor(
actionType = StakingActionCommonType.Enter(skipEnterAmount = false),
buttonsState = NavigationButtonsState.Empty,
balanceState = null,
showColdWalletInteractionIcon = true,
isColdWalletInteractionIconVisible = true,
shouldShowHoldToConfirmButton = false,
)
}

View file

@ -39,22 +39,9 @@ internal data class StakingUiState(
val actionType: StakingActionCommonType,
val buttonsState: NavigationButtonsState,
val balanceState: BalanceState?,
val showColdWalletInteractionIcon: Boolean,
val isColdWalletInteractionIconVisible: Boolean,
val shouldShowHoldToConfirmButton: Boolean,
) {
fun copyWrapped(
initialInfoState: StakingStates.InitialInfoState = this.initialInfoState,
amountState: AmountState = this.amountState,
confirmationState: StakingStates.ConfirmationState = this.confirmationState,
validatorState: StakingStates.ValidatorState = this.validatorState,
): StakingUiState = copy(
initialInfoState = initialInfoState,
amountState = amountState,
confirmationState = confirmationState,
validatorState = validatorState,
)
}
)
internal sealed class StakingStates {
@ -64,7 +51,7 @@ internal sealed class StakingStates {
sealed class InitialInfoState : StakingStates() {
data class Data(
override val isPrimaryButtonEnabled: Boolean,
val showBanner: Boolean,
val isBannerVisible: Boolean,
val infoItems: ImmutableList<RoundedListWithDividersItemData>,
val onInfoClick: (InfoType) -> Unit,
val yieldBalance: InnerYieldBalanceState,

View file

@ -18,7 +18,7 @@ import kotlinx.collections.immutable.persistentListOf
internal object InitialStakingStatePreview {
val defaultState = StakingStates.InitialInfoState.Data(
isPrimaryButtonEnabled = true,
showBanner = true,
isBannerVisible = true,
infoItems = persistentListOf(
RoundedListWithDividersItemData(
id = R.string.staking_details_available,

View file

@ -37,7 +37,7 @@ internal class SetButtonsStateTransformer(
return prevState.copy(buttonsState = buttonsState)
}
private fun getPrimaryButton(prevState: StakingUiState): NavigationButton? {
private fun getPrimaryButton(prevState: StakingUiState): NavigationButton {
val confirmState = prevState.confirmationState as? StakingStates.ConfirmationState.Data
val innerConfirmState = confirmState?.innerState
@ -52,7 +52,7 @@ internal class SetButtonsStateTransformer(
val isPrimaryButtonDisabled = prevState.isPrimaryButtonDisabled()
return NavigationButton(
textReference = prevState.getButtonText(),
iconRes = R.drawable.ic_tangem_24.takeIf { prevState.showColdWalletInteractionIcon },
iconRes = R.drawable.ic_tangem_24.takeIf { prevState.isColdWalletInteractionIconVisible },
isDimmed = isPrimaryButtonDisabled,
isIconVisible = isIconVisible,
shouldShowProgress = isInProgress,

View file

@ -96,7 +96,7 @@ internal class SetInitialDataStateTransformer(
isPrimaryButtonEnabled = with(status) {
!amount.isNullOrZero() && sources.stakingBalanceSource.isActual() && sources.networkSource.isActual()
},
showBanner = !isAnyTokenStaked && yieldBalance == InnerYieldBalanceState.Empty,
isBannerVisible = !isAnyTokenStaked && yieldBalance == InnerYieldBalanceState.Empty,
infoItems = getInfoItems(),
onInfoClick = clickIntents::onInfoClick,
yieldBalance = yieldBalance,

View file

@ -7,11 +7,11 @@ import com.tangem.utils.transformer.Transformer
internal class AmountCurrencyChangeStateTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val value: Boolean,
private val isFiatValue: Boolean,
) : Transformer<StakingUiState> {
override fun transform(prevState: StakingUiState): StakingUiState {
return prevState.copy(
amountState = AmountCurrencyTransformer(cryptoCurrencyStatus, value).transform(prevState.amountState),
amountState = AmountCurrencyTransformer(cryptoCurrencyStatus, isFiatValue).transform(prevState.amountState),
)
}
}

View file

@ -27,7 +27,7 @@ internal class SetApprovalBottomSheetInProgressTransformer(
),
onCancel = onDismiss,
)
} as TangemBottomSheetConfigContent,
} as? TangemBottomSheetConfigContent ?: return prevState,
),
)
}

View file

@ -16,7 +16,7 @@ internal class SetApprovalBottomSheetTypeChangeTransformer(
bottomSheetConfig = prevState.bottomSheetConfig?.copy(
content = approvalBottomSheetConfig?.copy(
data = approvalBottomSheetConfig.data.copy(approveType = approveType),
) as TangemBottomSheetConfigContent,
) as? TangemBottomSheetConfigContent ?: return prevState,
),
)
}

View file

@ -177,12 +177,12 @@ internal class AddStakingNotificationsTransformer(
}
private fun isPrimaryButtonEnabled(notifications: ImmutableList<NotificationUM>, isActualSources: Boolean) =
notifications.none {
it is StakingNotification.Error ||
it is NotificationUM.Error ||
it is NotificationUM.Warning.NetworkFeeUnreachable ||
it is StakingNotification.Warning.TransactionInProgress ||
it is StakingNotification.Warning.InitializeTonAccount
notifications.none { notification ->
notification is StakingNotification.Error ||
notification is NotificationUM.Error ||
notification is NotificationUM.Warning.NetworkFeeUnreachable ||
notification is StakingNotification.Warning.TransactionInProgress ||
notification is StakingNotification.Warning.InitializeTonAccount
} && isActualSources
private fun MutableList<NotificationUM>.addStakingErrorNotifications(
@ -302,8 +302,8 @@ internal class AddStakingNotificationsTransformer(
val balance = cryptoCurrencyStatus.value.amount.orZero()
if (!isSubtractionAvailable) return
val showNotification = sendingAmount + feeAmount > balance
if (showNotification) {
val isExceedsBalance = sendingAmount + feeAmount > balance
if (isExceedsBalance) {
onNotEnoughFeeNotificationShow()
val notification = if (actionType is StakingActionCommonType.Enter) {
NotificationUM.Error.TotalExceedsBalance

View file

@ -150,10 +150,10 @@ internal class StakingInfoNotificationsFactory(
val cryptoCurrencyStatus = cryptoCurrencyStatusProvider()
val isTron = isTron(cryptoCurrencyStatus.currency.network.rawId)
val hasStakedBalance = (cryptoCurrencyStatus.value.stakingBalance as? StakingBalance.Data.StakeKit)?.balance
?.items?.any {
it.type == BalanceType.PREPARING ||
it.type == BalanceType.STAKED ||
it.type == BalanceType.LOCKED
?.items?.any { item ->
item.type == BalanceType.PREPARING ||
item.type == BalanceType.STAKED ||
item.type == BalanceType.LOCKED
} == true
if (isTron && hasStakedBalance) {
add(

View file

@ -87,7 +87,7 @@ internal fun StakingInitialInfoContent(
.background(TangemTheme.colors.background.secondary)
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
if (state.showBanner) {
if (state.isBannerVisible) {
item(key = BANNER_BLOCK_KEY) {
Column(
modifier = Modifier.animateItem(),
@ -175,7 +175,7 @@ private fun LazyListScope.activeStakingBlock(
ActiveStakingBlock(
balance = balance,
isBalanceHidden = isBalanceHidden,
onClick = clickIntents::onActiveStake,
onClick = { clickIntents.onActiveStake(balance) },
onAnalytic = clickIntents::onActiveStakeAnalytic,
modifier = Modifier
.animateItem()
@ -288,7 +288,7 @@ private fun StakingRewardBlock(
private fun ActiveStakingBlock(
balance: BalanceState,
isBalanceHidden: Boolean,
onClick: (BalanceState) -> Unit,
onClick: () -> Unit,
onAnalytic: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -304,7 +304,7 @@ private fun ActiveStakingBlock(
enabled = balance.isClickable,
onClick = {
onAnalytic()
onClick(balance)
onClick()
},
)
.padding(TangemTheme.dimens.spacing12),
@ -351,20 +351,18 @@ private fun ActiveStakingBlock(
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
if (balance.formattedCryptoAmount != null) {
Text(
text = balance.formattedCryptoAmount.orMaskWithStars(isBalanceHidden).resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
)
}
Text(
text = balance.formattedCryptoAmount.orMaskWithStars(isBalanceHidden).resolveReference(),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing2),
)
}
}
}
@Composable
private fun RowScope.StakingBalanceIcon(balance: BalanceState, icon: Int?, iconTint: Color) {
private fun StakingBalanceIcon(balance: BalanceState, icon: Int?, iconTint: Color) {
if (balance.hasImage() || icon != null) {
StakingTargetIcon(
image = if (balance.hasImage()) balance.target?.image.toImageReference() else null,

View file

@ -109,8 +109,8 @@ private fun BoxScope.FeeLoading(feeState: FeeState) {
targetState = feeState,
label = "Fee Loading State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == FeeState.Loading) {
) { state ->
if (state == FeeState.Loading) {
RectangleShimmer(
radius = TangemTheme.dimens.radius3,
modifier = Modifier.size(
@ -128,8 +128,8 @@ private fun BoxScope.FeeError(feeState: FeeState) {
targetState = feeState,
label = "Fee Error State Change",
modifier = Modifier.align(Alignment.CenterEnd),
) {
if (it == FeeState.Error) {
) { state ->
if (state == FeeState.Error) {
Text(
text = DASH_SIGN,
color = TangemTheme.colors.text.primary1,
@ -151,13 +151,6 @@ private fun FeeBlockPreview(@PreviewParameter(FeeBlockPreviewProvider::class) va
private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState> {
override val values: Sequence<FeeState>
get() = sequenceOf(
contentState,
FeeState.Loading,
FeeState.Error,
)
private val fee = Fee.Common(
amount = Amount(
currencySymbol = "MATIC",
@ -174,6 +167,13 @@ private class FeeBlockPreviewProvider : PreviewParameterProvider<FeeState> {
isFeeApproximate = false,
isFeeConvertibleToFiat = true,
)
override val values: Sequence<FeeState>
get() = sequenceOf(
contentState,
FeeState.Loading,
FeeState.Error,
)
}
// endregion