diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index af94ad33ae..245601c136 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -471,6 +471,7 @@ Аккаунт не создан Эта карта не поддерживается Ваша карта Tangem предназначена для работы с другим приложением. Пожалуйста, ознакомьтесь с названием и инструкциями на вашей карте и установите правильное приложение. + Сеть %s Адрес скопирован в буфер обмена Нет соединения с интернетом Получение diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 038f2a7fe9..3874bb14cb 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -463,6 +463,7 @@ Account is not created This card is not supported Your Tangem card was made to work with a different application. Please see the name and instructions on your card, and install the correct app + %s network Address was copied to clipboard No internet connection Receiving diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt index 50651d2e35..8e63ac74d1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Buttons.kt @@ -1,29 +1,9 @@ package com.tangem.core.ui.components import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.IntrinsicSize -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.material.Button -import androidx.compose.material.ButtonColors -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.ButtonElevation -import androidx.compose.material.CircularProgressIndicator -import androidx.compose.material.Divider -import androidx.compose.material.Icon -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable -import androidx.compose.runtime.State -import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -244,6 +224,78 @@ fun SelectorButton(text: String, onClick: () -> Unit, modifier: Modifier = Modif } // endregion Other +// region Action + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=290-305&t=3z98eFnTeyIx5TH5-4) + * */ +@Composable +fun RoundedActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.secondaryButtonColors, + size = TangemButtonSize.RoundedAction, + ) +} + +/** + * [Show in Figma](https://www.figma.com/file/14ISV23YB1yVW1uNVwqrKv/Android?type=design&node-id=1208-1395&t=3z98eFnTeyIx5TH5-4) + * */ +@Composable +fun ActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.secondaryButtonColors, + size = TangemButtonSize.Action, + ) +} + +/** + * Same as [RoundedActionButton] but colored in primary background color + * */ +@Composable +fun BackgroundActionButton( + text: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, +) { + TangemButton( + modifier = modifier, + text = text, + icon = TangemButtonIcon.Left(icon), + onClick = onClick, + enabled = enabled, + showProgress = false, + colors = TangemButtonsDefaults.backgroundButtonColors, + size = TangemButtonSize.RoundedAction, + ) +} +// endregion Action + // region Defaults @Suppress("LongParameterList") @Composable @@ -335,13 +387,12 @@ private fun ButtonContent( } } -sealed interface TangemButtonIcon { +@Immutable +private sealed interface TangemButtonIcon { val painter: Painter? - @Immutable data class Left(override val painter: Painter) : TangemButtonIcon - @Immutable data class Right(override val painter: Painter) : TangemButtonIcon object None : TangemButtonIcon { @@ -349,10 +400,12 @@ sealed interface TangemButtonIcon { } } -enum class TangemButtonSize { +private enum class TangemButtonSize { Default, Text, Selector, + Action, + RoundedAction, } @Composable @@ -360,6 +413,9 @@ private fun TangemButtonSize.toHeightDp(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.size48 TangemButtonSize.Text -> TangemTheme.dimens.size40 TangemButtonSize.Selector -> TangemTheme.dimens.size24 + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> TangemTheme.dimens.size36 } @Composable @@ -367,6 +423,8 @@ private fun TangemButtonSize.toShape(): Shape = when (this) { TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall + TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.RoundedAction -> TangemTheme.shapes.roundedCornersLarge } @Composable @@ -374,6 +432,9 @@ private fun TangemButtonSize.toIconPadding(): Dp = when (this) { TangemButtonSize.Default -> TangemTheme.dimens.spacing8 TangemButtonSize.Text -> TangemTheme.dimens.spacing8 TangemButtonSize.Selector -> 0.dp + TangemButtonSize.Action, + TangemButtonSize.RoundedAction, + -> TangemTheme.dimens.spacing8 } object TangemButtonsDefaults { @@ -423,6 +484,14 @@ object TangemButtonsDefaults { disabledBackgroundColor = Color.Transparent, disabledContentColor = TangemTheme.colors.text.disabled, ) + + val backgroundButtonColors: ButtonColors + @Composable get() = TangemButtonColors( + backgroundColor = TangemTheme.colors.background.primary, + contentColor = TangemTheme.colors.text.primary1, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ) } @Immutable @@ -650,4 +719,67 @@ private fun TextButtonPreview_Dark() { TextButtonSample() } } + +@Composable +private fun ActionButtonSample(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .background(TangemTheme.colors.background.primary), + ) { + RoundedActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + ActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + BackgroundActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + RoundedActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + ActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + Divider(modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing8)) + BackgroundActionButton( + text = "Send", + icon = painterResource(id = R.drawable.ic_arrow_up_24), + enabled = false, + onClick = { /* [REDACTED_TODO_COMMENT]*/ }, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ActionButtonPreview_Light() { + TangemTheme { + ActionButtonSample() + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun ActionButtonPreview_Dark() { + TangemTheme(isDark = true) { + ActionButtonSample() + } +} // endregion Preview \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt index 3b6227abbc..e793e21eaf 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Shimmers.kt @@ -14,7 +14,7 @@ import com.valentinilk.shimmer.shimmer * Rectangle shimmer item with rounded shape from DS */ @Composable -fun ShimmerRectangle(modifier: Modifier = Modifier) { +fun RectangleShimmer(modifier: Modifier = Modifier) { Box( modifier = modifier .shimmer() @@ -51,7 +51,7 @@ private fun ShimmersPreview() { modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), ) { - ShimmerRectangle( + RectangleShimmer( modifier = Modifier.size( width = TangemTheme.dimens.size72, height = TangemTheme.dimens.size12, diff --git a/core/ui/src/main/res/drawable/ic_group_drop_24.xml b/core/ui/src/main/res/drawable/ic_group_drop_24.xml new file mode 100644 index 0000000000..6e8c84c533 --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_group_drop_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index c0a25c65df..183e43c174 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(deps.compose.ui.tooling) implementation(deps.compose.shimmer) implementation(deps.compose.accompanist.systemUiController) + implementation(deps.kotlin.immutable.collections) /** DI */ implementation(deps.hilt.android) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt new file mode 100644 index 0000000000..619f57d8bb --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/WalletPreviewData.kt @@ -0,0 +1,161 @@ +package com.tangem.feature.wallet.presentation.common + +import com.tangem.core.ui.R +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import kotlinx.collections.immutable.persistentListOf +import java.util.UUID + +internal object WalletPreviewData { + + val walletCardContent = WalletCardState.Content( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + balance = "8923,05 $", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardLoading = WalletCardState.Loading( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardHiddenContent = WalletCardState.HiddenContent( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletCardError = WalletCardState.Error( + id = UUID.randomUUID().toString(), + title = "Wallet 1", + additionalInfo = "3 cards • Seed enabled", + imageResId = R.drawable.ill_businessman_3d, + onClick = {}, + ) + + val walletScreenState = WalletStateHolder( + onBackClick = {}, + headerConfig = WalletStateHolder.HeaderConfig( + wallets = persistentListOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), + onScanCardClick = {}, + onMoreClick = {}, + ), + ) + + val tokenItemVisibleState = TokenItemState.Content( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + amount = "5,412 MATIC", + hasPending = true, + tokenOptions = TokenOptionsState.Visible( + fiatAmount = "321 $", + priceChange = TokenOptionsState.PriceChange( + valuePercent = "2%", + type = TokenOptionsState.PriceChange.Type.UP, + ), + ), + ) + + val tokenItemHiddenState = TokenItemState.Content( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + amount = "5,412 MATIC", + hasPending = true, + tokenOptions = TokenOptionsState.Hidden( + priceChange = TokenOptionsState.PriceChange( + valuePercent = "2%", + type = TokenOptionsState.PriceChange.Type.UP, + ), + ), + ) + + val tokenItemDragState = TokenItemState.Draggable( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + fiatAmount = "3 172,14 $", + ) + + val tokenItemUnreachableState = TokenItemState.Unreachable( + id = UUID.randomUUID().toString(), + tokenIconUrl = null, + tokenIconResId = R.drawable.img_polygon_22, + networkIconResId = R.drawable.img_polygon_22, + name = "Polygon", + ) + + val loadingTokenItemState = TokenItemState.Loading(id = UUID.randomUUID().toString()) + + private val draggableTokenList = persistentListOf( + tokenItemDragState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + fiatAmount = "3 172,14 $", + ), + tokenItemDragState.copy( + id = "token_2", + networkIconResId = R.drawable.img_eth_22, + fiatAmount = "803,65 $", + ), + tokenItemDragState.copy( + id = "token_3", + name = "USDT", + tokenIconResId = R.drawable.img_arbitrum_22, + networkIconResId = R.drawable.img_eth_22, + fiatAmount = "88,01 $", + ), + ) + + val networkGroup = NetworkGroupState.Content( + id = UUID.randomUUID().toString(), + networkName = "Ethereum", + tokens = persistentListOf( + tokenItemVisibleState.copy( + id = "token_1", + name = "Ethereum", + tokenIconResId = R.drawable.img_eth_22, + networkIconResId = null, + amount = "1,89340821 ETH", + ), + tokenItemVisibleState.copy( + id = "token_2", + networkIconResId = R.drawable.img_eth_22, + amount = "733,71097 MATIC", + ), + tokenItemVisibleState.copy( + id = "token_3", + name = "USDT", + tokenIconResId = R.drawable.img_arbitrum_22, + networkIconResId = R.drawable.img_eth_22, + amount = "0,25404523 ARB", + ), + ), + ) + + val draggableNetworkGroup = NetworkGroupState.Draggable( + id = UUID.randomUUID().toString(), + networkName = "Ethereum", + tokens = draggableTokenList, + ) +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt new file mode 100644 index 0000000000..ee2d924e85 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/NetworkGroupItem.kt @@ -0,0 +1,79 @@ +package com.tangem.feature.wallet.presentation.common.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.key +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +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.res.TangemTheme +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.state.NetworkGroupState + +@Composable +internal fun NetworkGroupItem(state: NetworkGroupState, modifier: Modifier = Modifier) { + Column(modifier = modifier) { + Row( + modifier = Modifier + .background(TangemTheme.colors.background.primary) + .padding(horizontal = TangemTheme.dimens.spacing12) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size48), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(id = R.string.wallet_network_group_title, state.networkName), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + if (state is NetworkGroupState.Draggable) { + Icon( + painter = painterResource(id = R.drawable.ic_group_drop_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + } + } + Column { + state.tokens.forEach { token -> + key(token.id) { + TokenItem(state = token) + } + } + } + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun NetworkGroupItemPreview_Light(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { + TangemTheme { + NetworkGroupItem(group) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun NetworkGroupItemPreview_Dark(@PreviewParameter(NetworkGroupProvider::class) group: NetworkGroupState) { + TangemTheme(isDark = true) { + NetworkGroupItem(group) + } +} + +private class NetworkGroupProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.networkGroup, + WalletPreviewData.draggableNetworkGroup, + ), +) +// endregion Preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt new file mode 100644 index 0000000000..78c5b5f322 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/component/TokenItem.kt @@ -0,0 +1,412 @@ +package com.tangem.feature.wallet.presentation.common.component + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstrainedLayoutReference +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.ConstraintLayoutScope +import androidx.constraintlayout.compose.Dimension +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.* +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemTypography +import com.tangem.feature.wallet.impl.R +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.state.TokenItemState +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState + +private const val DOTS = "•••" + +@Composable +internal fun TokenItem(state: TokenItemState, modifier: Modifier = Modifier) { + when (state) { + is TokenItemState.Content -> ContentTokenItem(state, modifier) + is TokenItemState.Loading -> LoadingTokenItem(modifier) + is TokenItemState.Draggable -> DraggableTokenItem(state, modifier) + is TokenItemState.Unreachable -> UnreachableTokenItem(state, modifier) + } +} + +@Composable +private fun ContentTokenItem(content: TokenItemState.Content, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = content.name, + tokenIconUrl = content.tokenIconUrl, + tokenIconResId = content.tokenIconResId, + networkIconResId = content.networkIconResId, + amount = if (content.tokenOptions is TokenOptionsState.Hidden) DOTS else content.amount, + hasPending = content.hasPending, + options = { ref -> + TokenOptionsBlock( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + state = content.tokenOptions, + ) + }, + ) +} + +@Composable +internal fun DraggableTokenItem(state: TokenItemState.Draggable, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = state.name, + tokenIconUrl = state.tokenIconUrl, + tokenIconResId = state.tokenIconResId, + networkIconResId = state.networkIconResId, + amount = state.fiatAmount, + hasPending = false, + options = { ref -> + Icon( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + painter = painterResource(id = R.drawable.ic_drag_24), + tint = TangemTheme.colors.icon.informative, + contentDescription = null, + ) + }, + ) +} + +@Composable +internal fun UnreachableTokenItem(state: TokenItemState.Unreachable, modifier: Modifier = Modifier) { + InternalTokenItem( + modifier = modifier, + name = state.name, + tokenIconUrl = state.tokenIconUrl, + tokenIconResId = state.tokenIconResId, + networkIconResId = state.networkIconResId, + amount = null, + hasPending = false, + options = { ref -> + Text( + modifier = Modifier.constrainAsOptionsItem(scope = this, ref), + text = "Unreachable", // TODO (conform this text) + style = TangemTypography.body2, + color = TangemTheme.colors.text.tertiary, + ) + }, + ) +} + +@Composable +private fun LoadingTokenItem(modifier: Modifier = Modifier) { + BaseSurface(modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing4, + ), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), + ) { + CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size72, + height = TangemTheme.dimens.size12, + ), + ) + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size50, + height = TangemTheme.dimens.size12, + ), + ) + } + Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size40, + height = TangemTheme.dimens.size12, + ), + ) + RectangleShimmer( + modifier = Modifier.size( + width = TangemTheme.dimens.size40, + height = TangemTheme.dimens.size12, + ), + ) + } + } + } + } +} + +/** + * Block for end part of token item + * shows status is reachable, is drag, hidden or show balance + */ +@Composable +private fun TokenOptionsBlock(state: TokenOptionsState, modifier: Modifier = Modifier) { + when (state) { + is TokenOptionsState.Visible -> { + TokenFiatPercentageBlock( + modifier = modifier, + fiatAmount = state.fiatAmount, + priceChange = state.priceChange, + ) + } + is TokenOptionsState.Hidden -> { + TokenFiatPercentageBlock( + modifier = modifier, + fiatAmount = DOTS, + priceChange = state.priceChange, + ) + } + } +} + +@Suppress("LongParameterList") +@Composable +private fun InternalTokenItem( + name: String, + tokenIconUrl: String?, + @DrawableRes tokenIconResId: Int, + @DrawableRes networkIconResId: Int?, + amount: String?, + hasPending: Boolean, + options: @Composable ConstraintLayoutScope.(ref: ConstrainedLayoutReference) -> Unit, + modifier: Modifier = Modifier, +) { + BaseSurface(modifier) { + ConstraintLayout( + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = TangemTheme.dimens.spacing12, + vertical = TangemTheme.dimens.spacing4, + ), + ) { + val (iconItem, tokenNameItem, optionsItem) = createRefs() + + TokenIcon( + modifier = Modifier.constrainAs(iconItem) { + centerVerticallyTo(parent) + start.linkTo(parent.start) + }, + tokenIconUrl = tokenIconUrl, + tokenIconResId = tokenIconResId, + networkIconRes = networkIconResId, + ) + + TokenTitleAmountBlock( + modifier = Modifier + .padding(horizontal = TangemTheme.dimens.spacing12) + .constrainAs(tokenNameItem) { + centerVerticallyTo(parent) + start.linkTo(iconItem.end) + end.linkTo(optionsItem.start) + width = Dimension.fillToConstraints + }, + title = name, + amount = amount, + hasPending = hasPending, + ) + + options(optionsItem) + } + } +} + +@Stable +private fun Modifier.constrainAsOptionsItem(scope: ConstraintLayoutScope, ref: ConstrainedLayoutReference): Modifier { + return with(scope) { + this@constrainAsOptionsItem.constrainAs(ref) { + centerVerticallyTo(parent) + end.linkTo(parent.end) + } + } +} + +@Composable +private fun BaseSurface( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Surface( + modifier = modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), + color = TangemTheme.colors.background.primary, + onClick = onClick ?: {}, + enabled = onClick != null, + ) { + content() + } +} + +@Composable +private fun TokenTitleAmountBlock(title: String, amount: String?, hasPending: Boolean, modifier: Modifier = Modifier) { + Column( + modifier = modifier, + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), + ) { + Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { + Text( + text = title, + style = TangemTypography.subtitle2, + color = TangemTheme.colors.text.primary1, + ) + if (hasPending) { + Image( + modifier = Modifier.align(Alignment.CenterVertically), + painter = painterResource(id = R.drawable.img_loader_15), + contentDescription = null, + ) + } + } + if (!amount.isNullOrBlank()) { + Text( + text = amount, + style = TangemTypography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } + } +} + +@Composable +private fun TokenFiatPercentageBlock( + fiatAmount: String, + priceChange: TokenOptionsState.PriceChange, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) { + Text( + modifier = Modifier.align(Alignment.End), + text = fiatAmount, + style = TangemTypography.body2, + color = TangemTheme.colors.text.primary1, + ) + SpacerH2() + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + val iconChangeArrow: Int + val changeTextColor: Color + when (priceChange.type) { + TokenOptionsState.PriceChange.Type.UP -> { + iconChangeArrow = R.drawable.img_arrow_up_8 + changeTextColor = TangemTheme.colors.text.accent + } + TokenOptionsState.PriceChange.Type.DOWN -> { + iconChangeArrow = R.drawable.img_arrow_down_8 + changeTextColor = TangemTheme.colors.text.warning + } + } + Image( + modifier = Modifier.align(Alignment.CenterVertically), + painter = painterResource(id = iconChangeArrow), + contentDescription = null, + ) + SpacerW4() + Text( + modifier = Modifier.align(Alignment.CenterVertically), + text = priceChange.valuePercent, + style = TangemTypography.body2, + color = changeTextColor, + ) + } + } +} + +@Composable +private fun TokenIcon( + tokenIconUrl: String?, + modifier: Modifier = Modifier, + @DrawableRes tokenIconResId: Int? = null, + @DrawableRes networkIconRes: Int? = null, +) { + Box( + modifier = modifier + .padding(end = TangemTheme.dimens.spacing16) + .size(TangemTheme.dimens.size42), + ) { + val tokenImageModifier = Modifier + .align(Alignment.BottomStart) + .size(TangemTheme.dimens.size36) + + val data = if (tokenIconUrl.isNullOrEmpty()) tokenIconResId else tokenIconUrl + SubcomposeAsyncImage( + modifier = tokenImageModifier, + model = ImageRequest.Builder(LocalContext.current) + .data(data) + .crossfade(true) + .build(), + loading = { CircleShimmer(modifier = tokenImageModifier) }, + contentDescription = null, + ) + + if (networkIconRes != null) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size18) + .background(color = Color.White, shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + Image( + modifier = Modifier.padding(all = 0.5.dp), + painter = painterResource(id = networkIconRes), + contentDescription = null, + ) + } + } + } +} + +// region preview + +@Preview +@Composable +private fun Preview_Tokens_LightTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { + TangemTheme(isDark = false) { + TokenItem(state) + } +} + +@Preview +@Composable +private fun Preview_Tokens_DarkTheme(@PreviewParameter(TokenConfigProvider::class) state: TokenItemState) { + TangemTheme(isDark = true) { + TokenItem(state) + } +} + +private class TokenConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + WalletPreviewData.tokenItemVisibleState, + WalletPreviewData.tokenItemUnreachableState, + WalletPreviewData.tokenItemDragState, + WalletPreviewData.tokenItemHiddenState, + WalletPreviewData.loadingTokenItemState, + ), +) + +// endregion preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt new file mode 100644 index 0000000000..706387e9d6 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenItemState.kt @@ -0,0 +1,111 @@ +package com.tangem.feature.wallet.presentation.common.state + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Immutable +import com.tangem.feature.wallet.presentation.common.state.TokenItemState.TokenOptionsState.PriceChange.Type + +/** Token item state */ +@Immutable +internal sealed interface TokenItemState { + + /** Token id */ + val id: String + + /** + * Loading token state + * + * @property id token id + */ + data class Loading(override val id: String) : TokenItemState + + /** + * Content token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + * @property amount amount of token + * @property hasPending pending tx in blockchain + * @property tokenOptions state for token options + */ + data class Content( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + val amount: String, + val hasPending: Boolean, + val tokenOptions: TokenOptionsState, + ) : TokenItemState + + /** + * Draggable token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + */ + data class Draggable( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + val fiatAmount: String, + ) : TokenItemState + + /** + * Unreachable token state + * + * @property id token id + * @property tokenIconUrl token icon url + * @property tokenIconResId token icon resource id + * @property networkIconResId network icon resource id, may be null if it is a coin + * @property name token name + */ + data class Unreachable( + override val id: String, + val tokenIconUrl: String?, + @DrawableRes val tokenIconResId: Int, + @DrawableRes val networkIconResId: Int?, + val name: String, + ) : TokenItemState + + /** Token options state */ + sealed interface TokenOptionsState { + + /** + * Visible token options state + * + * @property fiatAmount fiat amount of token + * @property priceChange value of price changing + */ + data class Visible(val fiatAmount: String, val priceChange: PriceChange) : TokenOptionsState + + /** + * Hidden token options state + * + * @property priceChange value of price changing + */ + data class Hidden(val priceChange: PriceChange) : TokenOptionsState + + /** + * Price changing option state + * + * @property valuePercent value in percent + * @property type type [Type] + */ + data class PriceChange(val valuePercent: String, val type: Type) { + + /** Price changing type */ + enum class Type { + UP, DOWN + } + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt new file mode 100644 index 0000000000..c504d1c81e --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/common/state/TokenListState.kt @@ -0,0 +1,35 @@ +package com.tangem.feature.wallet.presentation.common.state + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal sealed interface TokenListState { + + data class GroupedByNetwork( + val groups: ImmutableList, + ) : TokenListState + + data class Ungrouped( + val tokens: ImmutableList, + ) : TokenListState +} + +@Immutable +internal sealed interface NetworkGroupState { + val id: String + val networkName: String + val tokens: ImmutableList + + data class Draggable( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState + + data class Content( + override val id: String, + override val networkName: String, + override val tokens: ImmutableList, + ) : NetworkGroupState +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt index 90a84bef1c..74751d0c6b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt @@ -10,8 +10,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.WalletFragment -import com.tangem.feature.wallet.presentation.ui.WalletScreen -import com.tangem.feature.wallet.presentation.viewmodels.WalletViewModel +import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen +import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel import kotlin.properties.Delegates /** Default implementation of wallet feature router */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt index 00782aa81d..7558ac61c7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/InnerWalletRouter.kt @@ -1,13 +1,18 @@ package com.tangem.feature.wallet.presentation.router import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import com.tangem.features.wallet.navigation.WalletRouter /** * Interface of inner wallet feature router * + * Annotated as [Stable] because implementation of this interface passed as argument to [Initialize] method by compose + * compiler + * [REDACTED_AUTHOR] */ +@Stable internal interface InnerWalletRouter : WalletRouter { /** Initialize router */ diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt deleted file mode 100644 index ace321b30a..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenUIState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.tangem.feature.wallet.presentation.state - -internal enum class TokenUIState { - LOADED, LOADING -} - -internal enum class TokenOptionsUIState { - VISIBLE, HIDDEN, DRAG, UNREACHABLE -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt deleted file mode 100644 index dca7440c39..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/TokenV2Config.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.tangem.feature.wallet.presentation.state - -import androidx.annotation.DrawableRes - -/** - * Token config - * - * @property name of token/coin - * @property amount of token - * @property fiatAmount amount in fiat - * @property priceChange value of price changing - * @property iconUrl - * @property icon token/coin icon - * @property networkIcon - * @property tokenUIState token state [TokenUIState] loading etc. - * @property tokenOptionsUIState state for token options like 'unreachable', 'hidden' etc - * @property hasPending pending tx in blockchain - */ -internal data class TokenV2Config( - val name: String, - val amount: String, - val fiatAmount: String, - val priceChange: PriceChange, - val iconUrl: String?, - @DrawableRes val icon: Int?, - @DrawableRes val networkIcon: Int, - val tokenUIState: TokenUIState, - val tokenOptionsUIState: TokenOptionsUIState, - val hasPending: Boolean, -) - -data class PriceChange( - val valuePercent: String, - val type: PriceChangeType, -) - -enum class PriceChangeType { - UP, DOWN -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt deleted file mode 100644 index 45b3eb843d..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/Tokens.kt +++ /dev/null @@ -1,352 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.Icon -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.constraintlayout.compose.ConstraintLayout -import androidx.constraintlayout.compose.Dimension -import coil.compose.SubcomposeAsyncImage -import coil.request.ImageRequest -import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.res.TangemTypography -import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.* - -private const val DOTS = "•••" - -@Composable -internal fun TokenItemV2(config: TokenV2Config) { - when (config.tokenUIState) { - TokenUIState.LOADED -> LoadedTokenState(config) - TokenUIState.LOADING -> LoadingTokenState() - } -} - -@Composable -private fun LoadedTokenState(config: TokenV2Config) { - BaseSurface { - ConstraintLayout( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = TangemTheme.dimens.spacing12, - vertical = TangemTheme.dimens.spacing4, - ), - ) { - val (iconItem, tokenNameItem, fiatItem) = createRefs() - TokenIcon( - modifier = Modifier.constrainAs(iconItem) { - centerVerticallyTo(parent) - start.linkTo(parent.start) - }, - tokenIconUrl = config.iconUrl, - icon = config.icon, - networkIconRes = config.networkIcon, - ) - TokenTitleAmountBlock( - modifier = Modifier - .padding(horizontal = TangemTheme.dimens.spacing12) - .constrainAs(tokenNameItem) { - centerVerticallyTo(parent) - start.linkTo(iconItem.end) - end.linkTo(fiatItem.start) - width = Dimension.fillToConstraints - }, - title = config.name, - amount = config.amount, - hasPending = config.hasPending, - ) - TokenOptionsBlock( - config = config, - modifier = Modifier.constrainAs(fiatItem) { - centerVerticallyTo(parent) - end.linkTo(parent.end) - }, - ) - } - } -} - -/** - * Block for end part of token item - * shows status is reachable, is drag, hidden or show balance - */ -@Composable -private fun TokenOptionsBlock(config: TokenV2Config, modifier: Modifier = Modifier) { - when (config.tokenOptionsUIState) { - TokenOptionsUIState.VISIBLE -> TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = config.fiatAmount, - priceChange = config.priceChange, - ) - TokenOptionsUIState.UNREACHABLE -> Text( - modifier = modifier, - text = "Unreachable", // TODO (conform this text) - style = TangemTypography.body2, - color = TangemTheme.colors.text.tertiary, - ) - TokenOptionsUIState.HIDDEN -> TokenFiatPercentageBlock( - modifier = modifier, - fiatAmount = DOTS, - priceChange = config.priceChange, - ) - TokenOptionsUIState.DRAG -> Icon( - modifier = modifier, - painter = painterResource(id = R.drawable.ic_drag_24), - tint = TangemTheme.colors.icon.informative, - contentDescription = null, - ) - } -} - -@Composable -private fun LoadingTokenState() { - BaseSurface { - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = TangemTheme.dimens.spacing12, - vertical = TangemTheme.dimens.spacing4, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - CircleShimmer(modifier = Modifier.size(size = TangemTheme.dimens.size42)) - SpacerW12() - Column(verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size72, - height = TangemTheme.dimens.size12, - ), - ) - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size50, - height = TangemTheme.dimens.size12, - ), - ) - } - } - Column( - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8), - ) { - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size40, - height = TangemTheme.dimens.size12, - ), - ) - ShimmerRectangle( - modifier = Modifier.size( - width = TangemTheme.dimens.size40, - height = TangemTheme.dimens.size12, - ), - ) - } - } -} - -@Composable -private fun BaseSurface(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { - Surface( - modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size68), - color = TangemTheme.colors.background.primary, - onClick = onClick ?: {}, - enabled = onClick != null, - ) { - content() - } -} - -@Composable -private fun TokenTitleAmountBlock(title: String, amount: String, hasPending: Boolean, modifier: Modifier = Modifier) { - Column( - modifier = modifier, - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing2), - ) { - Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8)) { - Text( - text = title, - style = TangemTypography.subtitle2, - color = TangemTheme.colors.text.primary1, - ) - if (hasPending) { - Image( - modifier = Modifier.align(Alignment.CenterVertically), - painter = painterResource(id = R.drawable.img_loader_15), - contentDescription = null, - ) - } - } - Text( - text = amount, - style = TangemTypography.body2, - color = TangemTheme.colors.text.tertiary, - ) - } -} - -@Composable -private fun TokenFiatPercentageBlock(fiatAmount: String, priceChange: PriceChange, modifier: Modifier = Modifier) { - Column(modifier = modifier.requiredWidth(IntrinsicSize.Max)) { - Text( - modifier = Modifier.align(Alignment.End), - text = fiatAmount, - style = TangemTypography.body2, - color = TangemTheme.colors.text.primary1, - ) - SpacerH2() - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End, - ) { - val iconChangeArrow: Int - val changeTextColor: Color - when (priceChange.type) { - PriceChangeType.UP -> { - iconChangeArrow = R.drawable.img_arrow_up_8 - changeTextColor = TangemTheme.colors.text.accent - } - PriceChangeType.DOWN -> { - iconChangeArrow = R.drawable.img_arrow_down_8 - changeTextColor = TangemTheme.colors.text.warning - } - } - Image( - modifier = Modifier.align(Alignment.CenterVertically), - painter = painterResource(id = iconChangeArrow), - contentDescription = null, - ) - SpacerW4() - Text( - modifier = Modifier.align(Alignment.CenterVertically), - text = priceChange.valuePercent, - style = TangemTypography.body2, - color = changeTextColor, - ) - } - } -} - -@Composable -private fun TokenIcon( - tokenIconUrl: String?, - modifier: Modifier = Modifier, - @DrawableRes icon: Int? = null, - @DrawableRes networkIconRes: Int? = null, -) { - Box( - modifier = modifier - .padding(end = TangemTheme.dimens.spacing16) - .size(TangemTheme.dimens.size42), - ) { - val tokenImageModifier = Modifier - .align(Alignment.BottomStart) - .size(TangemTheme.dimens.size36) - - val data = if (tokenIconUrl.isNullOrEmpty()) { - icon - } else { - tokenIconUrl - } - SubcomposeAsyncImage( - modifier = tokenImageModifier, - model = ImageRequest.Builder(LocalContext.current) - .data(data) - .crossfade(true) - .build(), - loading = { CircleShimmer(modifier = tokenImageModifier) }, - contentDescription = null, - ) - - if (networkIconRes != null) { - Box( - modifier = Modifier - .align(Alignment.TopEnd) - .size(TangemTheme.dimens.size18) - .background(color = Color.White, shape = CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - modifier = Modifier.padding(all = 0.5.dp), - painter = painterResource(id = networkIconRes), - contentDescription = null, - ) - } - } - } -} - -// region preview - -@Composable -private fun TokensPreview() { - Column( - modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing18), - ) { - val config = TokenV2Config( - name = "Polygon", - amount = "5,412 MATIC", - fiatAmount = "321 $", - priceChange = PriceChange( - valuePercent = "2%", - type = PriceChangeType.UP, - ), - iconUrl = null, - icon = R.drawable.img_polygon_22, - networkIcon = R.drawable.img_polygon_22, - tokenUIState = TokenUIState.LOADED, - tokenOptionsUIState = TokenOptionsUIState.VISIBLE, - hasPending = true, - ) - // Loaded item - TokenItemV2(config) - // Unreachable item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE)) - // Drag item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.DRAG)) - // Hidden item - TokenItemV2(config.copy(tokenOptionsUIState = TokenOptionsUIState.UNREACHABLE)) - // Loading item - TokenItemV2( - config.copy( - tokenUIState = TokenUIState.LOADING, - ), - ) - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Tokens_InLightTheme() { - TangemTheme(isDark = false) { - TokensPreview() - } -} - -@Preview(showBackground = true) -@Composable -private fun Preview_Tokens_InDarkTheme() { - TangemTheme(isDark = true) { - TokensPreview() - } -} - -// endregion preview \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt deleted file mode 100644 index b716022f9b..0000000000 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletPreviewData.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.tangem.feature.wallet.presentation.ui - -import com.tangem.core.ui.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import java.util.UUID - -internal object WalletPreviewData { - - val walletCardContent = WalletCardState.Content( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - balance = "8923,05 $", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardLoading = WalletCardState.Loading( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardHiddenContent = WalletCardState.HiddenContent( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletCardError = WalletCardState.Error( - id = UUID.randomUUID().toString(), - title = "Wallet 1", - additionalInfo = "3 cards • Seed enabled", - imageResId = R.drawable.ill_businessman_3d, - onClick = {}, - ) - - val walletScreenState = WalletStateHolder( - onBackClick = {}, - headerConfig = WalletStateHolder.HeaderConfig( - wallets = listOf(walletCardContent, walletCardLoading, walletCardHiddenContent, walletCardError), - onScanCardClick = {}, - onMoreClick = {}, - ), - ) -} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt similarity index 97% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt index 0837a3ef7d..7685a267da 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletCardState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletCardState.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.state +package com.tangem.feature.wallet.presentation.wallet.state import androidx.annotation.DrawableRes diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt similarity index 79% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt index c69ab425ae..8142667ad3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/state/WalletStateHolder.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateHolder.kt @@ -1,4 +1,6 @@ -package com.tangem.feature.wallet.presentation.state +package com.tangem.feature.wallet.presentation.wallet.state + +import kotlinx.collections.immutable.ImmutableList /** * Wallet state holder @@ -21,7 +23,7 @@ internal data class WalletStateHolder( * @property onMoreClick lambda be invoked when more button is clicked */ data class HeaderConfig( - val wallets: List, + val wallets: ImmutableList, val onScanCardClick: () -> Unit, val onMoreClick: () -> Unit, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt similarity index 69% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index dc166a77a2..ba17e6e692 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -1,10 +1,10 @@ -package com.tangem.feature.wallet.presentation.ui +package com.tangem.feature.wallet.presentation.wallet.ui import androidx.activity.compose.BackHandler import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.components.WalletHeader +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletHeader /** * Wallet screen diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt similarity index 95% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt index 25a427408a..562c954023 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletCard.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletCard.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.ui.components +package com.tangem.feature.wallet.presentation.wallet.ui.components import androidx.annotation.DrawableRes import androidx.compose.foundation.Image @@ -17,12 +17,12 @@ import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import com.tangem.core.ui.components.FontSizeRange +import com.tangem.core.ui.components.RectangleShimmer import com.tangem.core.ui.components.ResizableText -import com.tangem.core.ui.components.ShimmerRectangle import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState private const val DOTS = "•••" @@ -119,7 +119,7 @@ private fun Balance(state: WalletCardState) { ) } is WalletCardState.Loading -> { - ShimmerRectangle( + RectangleShimmer( modifier = Modifier.size( width = TangemTheme.dimens.size102, height = TangemTheme.dimens.size24, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt similarity index 91% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt index acfa7b9a49..cb01b48377 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/ui/components/WalletHeader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/WalletHeader.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.wallet.presentation.ui.components +package com.tangem.feature.wallet.presentation.wallet.ui.components import androidx.compose.animation.core.* import androidx.compose.foundation.ExperimentalFoundationApi @@ -21,9 +21,10 @@ import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameter import androidx.compose.ui.unit.dp import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.state.WalletCardState -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.common.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletCardState +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder +import kotlinx.collections.immutable.persistentListOf /** * Wallet screen header @@ -109,7 +110,7 @@ private fun Preview_WalletHeader_DarkTheme( private class WalletHeaderProvider : CollectionPreviewParameterProvider( collection = listOf( WalletStateHolder.HeaderConfig( - wallets = listOf( + wallets = persistentListOf( WalletPreviewData.walletCardContent, WalletPreviewData.walletCardLoading, WalletPreviewData.walletCardHiddenContent, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt similarity index 83% rename from features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt rename to features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index c69e4f439b..a565bb8c91 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -1,12 +1,12 @@ -package com.tangem.feature.wallet.presentation.viewmodels +package com.tangem.feature.wallet.presentation.wallet.viewmodels import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel +import com.tangem.feature.wallet.presentation.common.WalletPreviewData import com.tangem.feature.wallet.presentation.router.InnerWalletRouter -import com.tangem.feature.wallet.presentation.state.WalletStateHolder -import com.tangem.feature.wallet.presentation.ui.WalletPreviewData +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateHolder import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlin.properties.Delegates