diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/ImageReference.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/ImageReference.kt index 767065f05a..97397435f8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/ImageReference.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/ImageReference.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.Immutable * It necessary to use [Immutable] annotation because all sealed interface has runtime stability. * All subclasses are stable. */ +@Immutable sealed class ImageReference { data class Url(val url: String) : ImageReference() diff --git a/core/ui/src/main/res/drawable/ic_p2p_logo.xml b/core/ui/src/main/res/drawable/ic_p2p_logo.xml new file mode 100644 index 0000000000..f7070f1376 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_p2p_logo.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt index c6c99e7fab..9d7b65a30e 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTarget.kt @@ -25,8 +25,8 @@ sealed interface StakingTarget { /** Whether this target is active and available for staking */ val isActive: Boolean - /** Image URL for display (validator logo or vault icon) */ - val image: String? + /** Image for display (validator logo or vault icon) */ + val image: StakingTargetImage? /** Whether this is a strategic partner (shows special badge in UI) */ val isStrategicPartner: Boolean @@ -40,7 +40,7 @@ sealed interface StakingTarget { override val rewardInfo: RewardInfo? = delegate.rewardInfo override val isPreferred: Boolean = delegate.preferred override val isActive: Boolean = delegate.status == Yield.Validator.ValidatorStatus.ACTIVE - override val image: String? = delegate.image + override val image: StakingTargetImage? = delegate.image?.let { StakingTargetImage.Url(it) } override val isStrategicPartner: Boolean = delegate.isStrategicPartner } @@ -56,7 +56,7 @@ sealed interface StakingTarget { ) override val isPreferred: Boolean = true override val isActive: Boolean = true - override val image: String? = null + override val image = StakingTargetImage.Local(StakingLocalImageType.P2P_VAULT) override val isStrategicPartner: Boolean = true } } diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTargetImage.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTargetImage.kt new file mode 100644 index 0000000000..bc6a9eae07 --- /dev/null +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/StakingTargetImage.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.staking.model + +sealed interface StakingTargetImage { + data class Url(val url: String) : StakingTargetImage + data class Local(val type: StakingLocalImageType) : StakingTargetImage +} + +enum class StakingLocalImageType { + P2P_VAULT, +} \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt index 1e2949326b..27ea9df657 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/ethpool/P2PEthPoolStakingConfig.kt @@ -7,7 +7,7 @@ package com.tangem.domain.staking.model.ethpool */ object P2PEthPoolStakingConfig { - const val USE_TESTNET: Boolean = true + const val USE_TESTNET: Boolean = false val activeNetwork: P2PEthPoolNetwork get() = if (USE_TESTNET) P2PEthPoolNetwork.TESTNET else P2PEthPoolNetwork.MAINNET diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt index 20f8688108..b28afeb422 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingClaimRewardsValidatorContent.kt @@ -20,6 +20,8 @@ import com.tangem.features.staking.impl.presentation.model.StakingClickIntents import com.tangem.features.staking.impl.presentation.state.BalanceState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.utils.getRewardTypeShortText +import com.tangem.features.staking.impl.presentation.ui.utils.toDrawableRes +import com.tangem.features.staking.impl.presentation.ui.utils.toImageUrl import com.tangem.utils.extensions.orZero @Composable @@ -42,7 +44,8 @@ internal fun StakingClaimRewardsValidatorContent( caption = item.getAprTextNeutral(), infoTitle = item.formattedFiatAmount, infoSubtitle = item.formattedCryptoAmount, - imageUrl = item.target?.image.orEmpty(), + imageUrl = item.target?.image.toImageUrl(), + iconRes = item.target?.image.toDrawableRes(), onImageError = { ValidatorImagePlaceholder() }, modifier = Modifier .roundedShapeItemDecoration(index, state.rewards.lastIndex, false) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 1a2d54564a..190331da23 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Icon import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape @@ -19,7 +20,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle @@ -32,9 +36,9 @@ import androidx.compose.ui.unit.Density import com.tangem.common.ui.navigationButtons.NavigationButtonsState import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer import com.tangem.core.ui.components.inputrow.InputRowDefault -import com.tangem.core.ui.components.inputrow.InputRowImageInfo import com.tangem.core.ui.components.list.roundedListWithDividersItems import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.* @@ -55,6 +59,7 @@ import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.previewdata.InitialStakingStatePreview import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub import com.tangem.features.staking.impl.presentation.state.utils.getRewardTypeShortText +import com.tangem.features.staking.impl.presentation.ui.utils.toImageReference import com.tangem.utils.StringsSigns.DOT import com.tangem.utils.extensions.orZero @@ -275,16 +280,9 @@ private fun ActiveStakingBlock( modifier: Modifier = Modifier, ) { val (icon, iconTint) = balance.type.getIcon() - InputRowImageInfo( - subtitle = balance.title, - caption = balance.subtitle ?: balance.getAprTextNeutral(), - infoTitle = balance.formattedFiatAmount.orMaskWithStars(isBalanceHidden), - infoSubtitle = balance.formattedCryptoAmount.orMaskWithStars(isBalanceHidden), - imageUrl = balance.getImage(), - iconRes = icon, - iconTint = iconTint, - subtitleEndIconRes = R.drawable.ic_staking_pending_transaction.takeIf { balance.isPending }, - onImageError = { ValidatorImagePlaceholder() }, + + Row( + verticalAlignment = Alignment.CenterVertically, modifier = modifier .background(TangemTheme.colors.background.action) .clickable( @@ -295,8 +293,96 @@ private fun ActiveStakingBlock( onAnalytic() onClick(balance) }, - ), - ) + ) + .padding(TangemTheme.dimens.spacing12), + ) { + StakingBalanceIcon( + balance = balance, + icon = icon, + iconTint = iconTint, + ) + + Column(modifier = Modifier.weight(1f)) { + Row { + Text( + text = balance.title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + if (balance.isPending) { + Spacer(modifier = Modifier.width(TangemTheme.dimens.spacing4)) + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(id = R.drawable.ic_staking_pending_transaction), + ), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size18), + ) + } + } + val caption = balance.subtitle ?: balance.getAprTextNeutral() + Text( + text = caption.resolveAnnotatedReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing2), + ) + } + + SpacerW12() + + Column(horizontalAlignment = Alignment.End) { + Text( + text = balance.formattedFiatAmount.orMaskWithStars(isBalanceHidden).resolveReference(), + 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), + ) + } + } + } +} + +@Composable +private fun RowScope.StakingBalanceIcon(balance: BalanceState, icon: Int?, iconTint: Color) { + if (balance.hasImage() || icon != null) { + StakingTargetIcon( + image = if (balance.hasImage()) balance.target?.image.toImageReference() else null, + onImageError = if (icon != null) { + { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(TangemTheme.dimens.spacing36) + .clip(TangemTheme.shapes.roundedCornersXLarge) + .background(iconTint.copy(alpha = 0.08f)), + ) { + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(id = icon), + ), + tint = iconTint, + contentDescription = null, + modifier = Modifier.size(TangemTheme.dimens.size18), + ) + } + } + } else { + { ValidatorImagePlaceholder() } + }, + modifier = Modifier + .size(TangemTheme.dimens.spacing36) + .clip(TangemTheme.shapes.roundedCornersXLarge), + ) + SpacerW12() + } } @Composable @@ -341,13 +427,12 @@ private fun BalanceType.getIcon() = when (this) { else -> null to TangemTheme.colors.icon.informative } -@Composable -private fun BalanceState.getImage() = when (type) { +private fun BalanceState.hasImage() = when (type) { BalanceType.UNSTAKING, BalanceType.UNSTAKED, BalanceType.LOCKED, - -> null - else -> target?.image + -> false + else -> target?.image != null } private val textGradientColors = listOf( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTargetIcon.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTargetIcon.kt new file mode 100644 index 0000000000..ba02bbd2fa --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingTargetIcon.kt @@ -0,0 +1,35 @@ +package com.tangem.features.staking.impl.presentation.ui + +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage +import com.tangem.core.ui.extensions.ImageReference + +@Composable +internal fun StakingTargetIcon( + image: ImageReference?, + modifier: Modifier = Modifier, + onImageError: (@Composable () -> Unit)? = null, +) { + when (image) { + is ImageReference.Url -> { + InputRowAsyncImage( + imageUrl = image.url, + onImageError = onImageError, + modifier = modifier, + ) + } + is ImageReference.Res -> { + Image( + painter = painterResource(image.resId), + contentDescription = null, + modifier = modifier, + ) + } + null -> { + onImageError?.invoke() + } + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt index 3f79c1729d..147bb67a96 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingValidatorListContent.kt @@ -3,12 +3,16 @@ package com.tangem.features.staking.impl.presentation.ui import android.content.res.Configuration import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape 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.draw.clip @@ -20,7 +24,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.inputrow.InputRowImageSelector +import com.tangem.core.ui.components.SpacerW12 import com.tangem.core.ui.decorations.roundedShapeItemDecoration import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.format @@ -35,6 +39,7 @@ import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.previewdata.ValidatorStatePreviewData import com.tangem.features.staking.impl.presentation.state.stub.StakingClickIntentsStub import com.tangem.features.staking.impl.presentation.state.utils.getRewardTypeLongText +import com.tangem.features.staking.impl.presentation.ui.utils.toImageReference import com.tangem.utils.extensions.orZero /** @@ -61,47 +66,80 @@ internal fun StakingValidatorListContent( key = { targets[it].address }, contentType = { targets[it]::class.java }, ) { index -> - val item = targets[index] - - InputRowImageSelector( - subtitle = stringReference(item.name), - caption = item.getAprTextNeutral(), - imageUrl = item.image.orEmpty(), - isSelected = item == state.chosenTarget, - onSelect = { clickIntents.onTargetSelect(item) }, - modifier = Modifier - .roundedShapeItemDecoration( - currentIndex = index, - lastIndex = targets.lastIndex, - radius = TangemTheme.dimens.radius12, - addDefaultPadding = - false, - ) - .background(TangemTheme.colors.background.action), - subtitleExtraContent = { - ValidatorLabel(item.isStrategicPartner) - }, - selectorContent = { checked, _, _ -> - AnimatedVisibility( - modifier = Modifier, - visible = checked, - ) { - Icon( - painter = rememberVectorPainter( - image = ImageVector.vectorResource(id = R.drawable.ic_check_24), - ), - tint = TangemTheme.colors.icon.accent, - contentDescription = null, - ) - } - }, - onImageError = { ValidatorImagePlaceholder() }, + ValidatorListItem( + item = targets[index], + isSelected = targets[index] == state.chosenTarget, + index = index, + lastIndex = targets.lastIndex, + onSelect = { clickIntents.onTargetSelect(targets[index]) }, ) } } } } +@Composable +private fun ValidatorListItem( + item: StakingTarget, + isSelected: Boolean, + index: Int, + lastIndex: Int, + onSelect: () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = lastIndex, + radius = TangemTheme.dimens.radius12, + addDefaultPadding = false, + ) + .background(TangemTheme.colors.background.action) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = ripple(), + onClick = onSelect, + ) + .padding(TangemTheme.dimens.spacing12), + ) { + StakingTargetIcon( + image = item.image.toImageReference(), + onImageError = { ValidatorImagePlaceholder() }, + modifier = Modifier + .size(TangemTheme.dimens.spacing36) + .clip(TangemTheme.shapes.roundedCornersXLarge), + ) + SpacerW12() + Column(modifier = Modifier.weight(1f)) { + Row { + Text( + text = stringReference(item.name).resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + ValidatorLabel(item.isStrategicPartner) + } + Text( + text = item.getAprTextNeutral().resolveAnnotatedReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(top = TangemTheme.dimens.spacing2), + ) + } + SpacerW12() + AnimatedVisibility(visible = isSelected) { + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(id = R.drawable.ic_check_24), + ), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + } +} + /** * For FCA fixes remove coloring for now */ diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt index 44012d5abc..005d38c850 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/ValidatorBlock.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.testTag import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SpacerWMax -import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage import com.tangem.core.ui.extensions.* import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent @@ -30,7 +29,9 @@ import com.tangem.features.staking.impl.R import com.tangem.features.staking.impl.presentation.state.StakingStates.ValidatorState import com.tangem.features.staking.impl.presentation.state.StakingStates import com.tangem.features.staking.impl.presentation.state.utils.getRewardTypeShortText +import com.tangem.features.staking.impl.presentation.ui.StakingTargetIcon import com.tangem.features.staking.impl.presentation.ui.ValidatorImagePlaceholder +import com.tangem.features.staking.impl.presentation.ui.utils.toImageReference import com.tangem.utils.extensions.orZero @Composable @@ -63,8 +64,8 @@ internal fun ValidatorBlock(validatorState: StakingStates.ValidatorState, isClic horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(12.dp), ) { - InputRowAsyncImage( - imageUrl = state.chosenTarget.image.orEmpty(), + StakingTargetIcon( + image = state.chosenTarget.image.toImageReference(), onImageError = { ValidatorImagePlaceholder() }, modifier = Modifier .size(24.dp) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/utils/StakingImageUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/utils/StakingImageUtils.kt new file mode 100644 index 0000000000..7c1dc4d212 --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/utils/StakingImageUtils.kt @@ -0,0 +1,23 @@ +package com.tangem.features.staking.impl.presentation.ui.utils + +import androidx.annotation.DrawableRes +import com.tangem.core.ui.extensions.ImageReference +import com.tangem.core.ui.R as CoreR +import com.tangem.domain.staking.model.StakingLocalImageType +import com.tangem.domain.staking.model.StakingTargetImage + +fun StakingTargetImage?.toImageReference(): ImageReference? = when (this) { + is StakingTargetImage.Url -> url.takeIf { it.isNotBlank() }?.let { ImageReference.Url(it) } + is StakingTargetImage.Local -> ImageReference.Res(type.toDrawableRes()) + null -> null +} + +fun StakingTargetImage?.toImageUrl(): String? = (this as? StakingTargetImage.Url)?.url?.takeIf { it.isNotBlank() } + +@DrawableRes +fun StakingTargetImage?.toDrawableRes(): Int? = (this as? StakingTargetImage.Local)?.type?.toDrawableRes() + +@DrawableRes +fun StakingLocalImageType.toDrawableRes(): Int = when (this) { + StakingLocalImageType.P2P_VAULT -> CoreR.drawable.ic_p2p_logo +} \ No newline at end of file