From 51540c732a5a9e40c158160ad5d047d504e5bef1 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 14 Nov 2023 15:44:02 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../components/SettingsSwitchItem.kt | 1 + .../com/tangem/core/ui/components/Cards.kt | 22 +- .../tangem/core/ui/components/TangemSwitch.kt | 2 +- .../com/tangem/core/ui/components/Warnings.kt | 41 +++- .../core/ui/decorations/RoundedDecorations.kt | 32 ++- .../common/state/ChooseWalletState.kt | 31 +++ .../common/state/NetworkItemState.kt | 90 ++++++++ .../presentation/common/state/WalletState.kt | 8 + .../ChooseWalletStatePreviewData.kt | 27 +++ .../common/ui/ChooseWalletScreen.kt | 151 +++++++++++++ .../common/ui/components/NetworkItem.kt | 157 ++++++++++++++ .../ui/components/SimpleSelectionBlock.kt | 70 ++++++ .../managetokens/state/ChooseNetworkState.kt | 11 + .../state/DerivationNotificationState.kt | 7 +- .../managetokens/state/TokenItemState.kt | 10 +- .../ChooseNetworkStatePreviewData.kt | 60 +++++ .../DerivationNotificationStatePreviewData.kt | 5 +- .../previewdata/TokenItemStatePreviewData.kt | 13 +- .../managetokens/ui/ChooseNetworkScreen.kt | 205 ++++++++++++++++++ .../ui/components/TokenRowItem.kt | 6 +- 20 files changed, 915 insertions(+), 34 deletions(-) create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt index 74815a675f..b584136754 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/appsettings/components/SettingsSwitchItem.kt @@ -63,6 +63,7 @@ internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier checked = item.isChecked, enabled = item.isEnabled, onCheckedChange = item.onCheckedChange, + checkedColor = TangemTheme.colors.icon.accent, ) } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt index 847c27307f..91dd9a2506 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Cards.kt @@ -13,6 +13,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -162,11 +163,12 @@ fun CardWithIcon( * >Figma component */ @Composable -fun IconWithTitleAndDescription( +internal fun IconWithTitleAndDescription( title: String, - description: String, + description: String?, icon: @Composable () -> Unit, additionalContent: @Composable () -> Unit = {}, + iconBackground: Color = TangemTheme.colors.background.secondary, ) { Row( modifier = Modifier @@ -182,7 +184,7 @@ fun IconWithTitleAndDescription( Box( modifier = Modifier .background( - color = TangemTheme.colors.background.secondary, + color = iconBackground, shape = CircleShape, ) .height(TangemTheme.dimens.size40) @@ -205,12 +207,14 @@ fun IconWithTitleAndDescription( color = TangemTheme.colors.text.primary1, style = TangemTheme.typography.subtitle1, ) - SpacerH4() - Text( - text = description, - color = TangemTheme.colors.text.secondary, - style = TangemTheme.typography.body2, - ) + if (description != null) { + SpacerH4() + Text( + text = description, + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + ) + } } additionalContent() diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt index c4d41ef91b..8e0081a633 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/TangemSwitch.kt @@ -23,7 +23,7 @@ import com.tangem.core.ui.res.TangemTheme @Composable fun TangemSwitch( onCheckedChange: (Boolean) -> Unit, - checkedColor: Color = TangemTheme.colors.icon.accent, + checkedColor: Color = TangemTheme.colors.control.checked, uncheckedColor: Color = TangemTheme.colors.icon.informative, size: Dp = 48.dp, checked: Boolean = false, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt index b6b858d2cd..d0d3315f20 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Warnings.kt @@ -9,6 +9,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R @@ -36,6 +37,28 @@ fun WarningCard(title: String, description: String, icon: @Composable (() -> Uni ) } +/** + * A card with a warning icon to the left and title without description shown to the right of it. + * + * @param title title of the warning in bold + * + * @see Figma component + */ +@Composable +fun WarningCardTitleOnly(title: String, icon: @Composable (() -> Unit)? = null) { + WarningCardMaterial3Style( + content = { + WarningBody( + title = title, + description = null, + icon = icon, + iconBackground = TangemTheme.colors.button.disabled, + ) + }, + ) +} + /** * [WarningCard], but clickable (with an 'greater then' icon to the left) * @@ -105,7 +128,8 @@ fun RefreshableWaringCard( @Composable private fun WarningBody( title: String, - description: String, + description: String?, + iconBackground: Color = TangemTheme.colors.background.secondary, icon: @Composable (() -> Unit)? = null, additionalContent: @Composable () -> Unit = {}, ) { @@ -119,6 +143,7 @@ private fun WarningBody( contentDescription = null, ) }, + iconBackground = iconBackground, ) } @@ -136,6 +161,20 @@ private fun WarningCardSurface(onClick: (() -> Unit)? = null, content: @Composab } } +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun WarningCardMaterial3Style(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { + Card( + shape = RoundedCornerShape(TangemTheme.dimens.radius16), + backgroundColor = TangemTheme.colors.button.disabled, + elevation = TangemTheme.dimens.elevation0, + onClick = onClick ?: {}, + enabled = onClick != null, + ) { + content() + } +} + // endregion elements // region Preview diff --git a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt index 362f4d95e9..4a014cd824 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/decorations/RoundedDecorations.kt @@ -7,18 +7,34 @@ import androidx.compose.ui.composed import androidx.compose.ui.draw.clip import com.tangem.core.ui.res.TangemTheme -fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modifier = composed { - val modifierWithHorizontalPadding = this.padding(horizontal = TangemTheme.dimens.spacing16) +fun Modifier.roundedShapeItemDecoration( + currentIndex: Int, + lastIndex: Int, + addDefaultPadding: Boolean = true, +): Modifier = composed { + val modifier = if (addDefaultPadding) this.padding(horizontal = TangemTheme.dimens.spacing16) else this val isSingleItem = currentIndex == 0 && lastIndex == 0 when { isSingleItem -> { - modifierWithHorizontalPadding - .padding(top = TangemTheme.dimens.spacing14) + modifier + .then( + if (addDefaultPadding) { + Modifier.padding(top = TangemTheme.dimens.spacing14) + } else { + Modifier + }, + ) .clip(shape = TangemTheme.shapes.roundedCornersXMedium) } currentIndex == 0 -> { - modifierWithHorizontalPadding - .padding(top = TangemTheme.dimens.spacing14) + modifier + .then( + if (addDefaultPadding) { + Modifier.padding(top = TangemTheme.dimens.spacing14) + } else { + Modifier + }, + ) .clip( shape = RoundedCornerShape( topStart = TangemTheme.dimens.radius16, @@ -27,7 +43,7 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi ) } currentIndex == lastIndex -> { - modifierWithHorizontalPadding + modifier .clip( shape = RoundedCornerShape( bottomStart = TangemTheme.dimens.radius16, @@ -35,6 +51,6 @@ fun Modifier.roundedShapeItemDecoration(currentIndex: Int, lastIndex: Int): Modi ), ) } - else -> modifierWithHorizontalPadding + else -> modifier } } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt new file mode 100644 index 0000000000..44a0529872 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/ChooseWalletState.kt @@ -0,0 +1,31 @@ +package com.tangem.managetokens.presentation.common.state + +import com.tangem.core.ui.extensions.TextReference +import com.tangem.features.managetokens.impl.R +import kotlinx.collections.immutable.ImmutableList + +internal sealed class ChooseWalletState { + data class Choose( + val wallets: ImmutableList, + val selectedWallet: WalletState?, + val onChooseWalletClick: () -> Unit, + val onCloseChoosingWalletClick: () -> Unit, + ) : ChooseWalletState() + + object NoSelection : ChooseWalletState() + + class Warning(val type: ChooseWalletWarning) : ChooseWalletState() { + val message: TextReference + get() = when (type) { + ChooseWalletWarning.SINGLE_CURRENCY -> + TextReference.Res(R.string.manage_tokens_wallet_support_only_one_network_title) + ChooseWalletWarning.WALLET_INCOMPATIBLE -> + TextReference.Res(R.string.manage_tokens_wallet_does_not_supported_blockchain) + } + } +} + +enum class ChooseWalletWarning { + SINGLE_CURRENCY, + WALLET_INCOMPATIBLE, +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt new file mode 100644 index 0000000000..a759836afc --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/NetworkItemState.kt @@ -0,0 +1,90 @@ +package com.tangem.managetokens.presentation.common.state + +import androidx.compose.runtime.MutableState +import com.tangem.blockchain.common.Blockchain +import com.tangem.core.ui.extensions.getActiveIconRes +import com.tangem.core.ui.extensions.getGreyedOutIconRes +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState + +/** + * Network item state + * + * @property name network name + * @property protocolName network protocol name + * @property id network id + * @property blockchain blockchain + * @property iconRes network icon id from resources + */ +internal sealed interface NetworkItemState { + + val name: String + val protocolName: String + val id: String + val blockchain: Blockchain + + val iconRes: Int + get() = when (this) { + is Selectable -> this.iconResId + is Toggleable -> this.iconResId.value + } + + /** + * Network item state that can be added and deleted + * + * @property name network name + * @property protocolName network protocol name + * @property id network id + * @property blockchain blockchain + * @property iconResId network icon id from resources + * @property isMainNetwork flag that determines if the network is the main network for the token + * @property isAdded flag that determines if the user has saved the network + * @property address contract address + * @property decimals decimal count + * @property onToggleClick lambda be invoked when switch is been toggled + */ + @Suppress("LongParameterList") + class Toggleable( + override val name: String, + override val protocolName: String, + override val id: String, + override val blockchain: Blockchain, + val iconResId: MutableState, + val isMainNetwork: Boolean, + val isAdded: MutableState, + val address: String?, + val decimals: Int?, + val onToggleClick: (TokenItemState.Loaded, Toggleable) -> Unit, + ) : NetworkItemState { + + /** + * Change toggle state [isAdded]. + * + * It is a hack that helps us to change element of flow + */ + fun changeToggleState() { + val reverseState = !isAdded.value + isAdded.value = reverseState + iconResId.value = if (reverseState) getActiveIconRes(blockchain.id) else getGreyedOutIconRes(blockchain.id) + } + } + + /** + * Network item state that can be selected + * + * @property name network name + * @property protocolName network protocol name + * @property iconResId network icon id from resources + * @property id network id + * @property blockchain blockchain + * @property onNetworkClick lambda be invoked when network item is been clicked + * + */ + class Selectable( + override val name: String, + override val protocolName: String, + val iconResId: Int, + override val id: String, + override val blockchain: Blockchain, + val onNetworkClick: (NetworkItemState) -> Unit, + ) : NetworkItemState +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt new file mode 100644 index 0000000000..6af48736a4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/WalletState.kt @@ -0,0 +1,8 @@ +package com.tangem.managetokens.presentation.common.state + +internal data class WalletState( + val walletId: String, + val artworkUrl: String?, + val walletName: String, + val onSelected: (String) -> Unit, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt new file mode 100644 index 0000000000..f231654b96 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/state/previewdata/ChooseWalletStatePreviewData.kt @@ -0,0 +1,27 @@ +package com.tangem.managetokens.presentation.common.state.previewdata + +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.WalletState +import kotlinx.collections.immutable.persistentListOf + +internal object ChooseWalletStatePreviewData { + + val state: ChooseWalletState.Choose + get() = ChooseWalletState.Choose( + wallets = persistentListOf( + walletState, + walletState.copy(walletId = "2"), + ), + selectedWallet = walletState, + onChooseWalletClick = {}, + onCloseChoosingWalletClick = {}, + ) + + private val walletState: WalletState + get() = WalletState( + walletName = "My wallet", + walletId = "1", + artworkUrl = "", + onSelected = {}, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt new file mode 100644 index 0000000000..d0dab333d8 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletScreen.kt @@ -0,0 +1,151 @@ +package com.tangem.managetokens.presentation.common.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerW12 +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.WalletState +import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData + +@Composable +internal fun ChooseWalletScreen(state: ChooseWalletState.Choose, modifier: Modifier = Modifier) { + LazyColumn( + modifier = modifier + .background(TangemTheme.colors.background.tertiary) + .padding(TangemTheme.dimens.spacing16), + ) { + item { + Box( + modifier = Modifier.defaultMinSize(minHeight = TangemTheme.dimens.size44), + ) { + IconButton( + onClick = state.onCloseChoosingWalletClick, + modifier = Modifier.align(Alignment.CenterStart), + ) { + Icon( + painterResource(id = R.drawable.ic_back_24), + contentDescription = null, + tint = TangemTheme.colors.icon.primary1, + ) + } + Text( + text = stringResource(id = R.string.manage_tokens_wallet_selector_title), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + textAlign = TextAlign.Center, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center), + ) + } + } + items( + count = state.wallets.count(), + key = { index -> state.wallets[index].walletId }, + ) { index -> + WalletItem( + wallet = state.wallets[index], + selectedWallet = state.selectedWallet, + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = state.wallets.lastIndex, + addDefaultPadding = false, + ), + ) + } + item { + SpacerH(height = TangemTheme.dimens.spacing16) + } + } +} + +@Composable +private fun WalletItem(wallet: WalletState, selectedWallet: WalletState?, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .clickable { wallet.onSelected(wallet.walletId) } + .background(TangemTheme.colors.background.action) + .defaultMinSize(minHeight = TangemTheme.dimens.size72) + .padding(horizontal = TangemTheme.dimens.spacing16), + verticalAlignment = Alignment.CenterVertically, + ) { + SubcomposeAsyncImage( + modifier = Modifier.size(height = TangemTheme.dimens.size30, width = TangemTheme.dimens.size50), + + model = ImageRequest.Builder(context = LocalContext.current) + .data(wallet.artworkUrl) + .crossfade(enable = true) + .build(), + loading = { + Image( + painter = painterResource(R.drawable.card_placeholder_primary), + contentDescription = null, + ) + }, + error = { + Image( + painter = painterResource(R.drawable.card_placeholder_primary), + contentDescription = null, + ) + }, + contentDescription = null, + ) + SpacerW12() + Text( + text = wallet.walletName, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + SpacerWMax() + if (selectedWallet == wallet) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + contentDescription = null, + tint = TangemTheme.colors.icon.accent, + ) + } + } +} + +@Preview +@Composable +private fun Preview_ChooseWalletScreen_Light() { + TangemTheme(isDark = false) { + ChooseWalletScreen( + state = ChooseWalletStatePreviewData.state, + ) + } +} + +@Preview +@Composable +private fun Preview_ChooseWalletScreen_Dark() { + TangemTheme(isDark = false) { + ChooseWalletScreen( + state = ChooseWalletStatePreviewData.state, + ) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt new file mode 100644 index 0000000000..18de0a171a --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/NetworkItem.kt @@ -0,0 +1,157 @@ +package com.tangem.managetokens.presentation.common.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +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.blockchain.common.Blockchain +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.components.TangemSwitch +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.NetworkItemState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import com.tangem.managetokens.presentation.managetokens.state.previewdata.TokenItemStatePreviewData + +@Composable +internal fun NetworkItem(state: NetworkItemState, tokenState: TokenItemState.Loaded?, modifier: Modifier = Modifier) { + Row( + modifier = modifier + .background(TangemTheme.colors.background.action) + .defaultMinSize(minHeight = TangemTheme.dimens.size68) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + ) { + NetworkIcon(model = state) + SpacerW(width = TangemTheme.dimens.spacing12) + Text( + text = state.name, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle2, + ) + SpacerW(width = TangemTheme.dimens.spacing6) + Text( + text = state.protocolName, + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.body2, + modifier = Modifier + .weight(1f), + ) + if (state is NetworkItemState.Toggleable) { + TangemSwitch( + onCheckedChange = { + state.onToggleClick(tokenState!!, state) + }, + checked = state.isAdded.value, + ) + } + } +} + +@Composable +internal fun NetworkIcon(model: NetworkItemState, modifier: Modifier = Modifier) { + Box(modifier = modifier.size(size = TangemTheme.dimens.size36)) { + val isAdded = when (model) { + is NetworkItemState.Selectable -> true + is NetworkItemState.Toggleable -> model.isAdded.value + } + + if (!isAdded) { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .clip(CircleShape) + .background(TangemTheme.colors.control.unchecked), + ) + } + Icon( + painter = painterResource(id = model.iconRes), + contentDescription = null, + modifier = Modifier.size(size = TangemTheme.dimens.size36), + tint = if (isAdded) Color.Unspecified else TangemTheme.colors.text.tertiary, + ) + + if (model is NetworkItemState.Toggleable && model.isMainNetwork) { + Box( + modifier = Modifier + .align(Alignment.TopEnd) + .size(TangemTheme.dimens.size10) + .clip(CircleShape) + .background(TangemTheme.colors.stroke.transparency), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size8) + .clip(CircleShape) + .background(TangemTheme.colors.icon.accent), + ) + } + } + } +} + +@Preview +@Composable +private fun Preview_NetworkItem_Light(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) { + TangemTheme(isDark = false) { + NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded) + } +} + +@Preview +@Composable +private fun Preview_NetworkItem_Dark(@PreviewParameter(NetworkItemStateProvider::class) state: NetworkItemState) { + TangemTheme(isDark = true) { + NetworkItem(state, tokenState = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded) + } +} + +private class NetworkItemStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + NetworkItemState.Toggleable( + name = "Ethereum", + protocolName = "ETH", + iconResId = mutableStateOf(R.drawable.img_polygon_22), + isMainNetwork = true, + isAdded = mutableStateOf(true), + id = "", + address = "", + onToggleClick = { _, _ -> }, + blockchain = Blockchain.Ethereum, + decimals = 0, + ), + NetworkItemState.Toggleable( + name = "BNB SMART CHAIN", + protocolName = "BEP20", + iconResId = mutableStateOf(R.drawable.ic_bsc_16), + isMainNetwork = false, + isAdded = mutableStateOf(false), + id = "", + address = "", + onToggleClick = { _, _ -> }, + blockchain = Blockchain.BSC, + decimals = 0, + ), + NetworkItemState.Selectable( + name = "Ethereum", + protocolName = "ETH", + iconResId = R.drawable.img_polygon_22, + id = "", + onNetworkClick = { }, + blockchain = Blockchain.Ethereum, + ), + ), +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt new file mode 100644 index 0000000000..c136cb1e28 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/components/SimpleSelectionBlock.kt @@ -0,0 +1,70 @@ +package com.tangem.managetokens.presentation.common.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun SimpleSelectionBlock( + title: String, + subtitle: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + roundedCorners: Boolean = true, +) { + Column( + modifier = modifier + .then( + if (roundedCorners) { + Modifier.clip(shape = RoundedCornerShape(TangemTheme.dimens.radius16)) + } else { + Modifier + }, + ) + .background(color = TangemTheme.colors.background.action) + .clickable { onClick() } + .padding( + horizontal = TangemTheme.dimens.spacing20, + vertical = TangemTheme.dimens.spacing16, + ) + .fillMaxWidth(), + ) { + Text( + text = title, + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + ) + SpacerH(height = TangemTheme.dimens.spacing4) + Text( + text = subtitle, + color = TangemTheme.colors.text.secondary, + style = TangemTheme.typography.body2, + ) + } +} + +@Preview +@Composable +private fun Preview_SimpleSelectionBlock_Light() { + TangemTheme(isDark = false) { + SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { }) + } +} + +@Preview +@Composable +private fun Preview_SimpleSelectionBlock_Dark() { + TangemTheme(isDark = true) { + SimpleSelectionBlock(title = "Wallet", subtitle = "Family Wallet", onClick = { }) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt new file mode 100644 index 0000000000..df2a7f015f --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ChooseNetworkState.kt @@ -0,0 +1,11 @@ +package com.tangem.managetokens.presentation.managetokens.state + +import com.tangem.managetokens.presentation.common.state.NetworkItemState +import kotlinx.collections.immutable.ImmutableList + +internal data class ChooseNetworkState( + val nativeNetworks: ImmutableList, + val nonNativeNetworks: ImmutableList, + val onNonNativeNetworkHintClick: () -> Unit, + val onCloseChooseNetworkScreen: () -> Unit, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt index 8856ab58c5..0cfdfa16d1 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/DerivationNotificationState.kt @@ -8,7 +8,8 @@ import com.tangem.features.managetokens.impl.R data class DerivationNotificationState( val totalNeeded: Int, - val missingAddressesCount: Int, + val totalWallets: Int, + val walletsToDerive: Int, val onGenerateClick: () -> Unit, ) { val config = NotificationConfig( @@ -25,8 +26,8 @@ data class DerivationNotificationState( onClick = onGenerateClick, additionalText = pluralReference( id = R.plurals.manage_tokens_number_of_wallets_android, - count = totalNeeded, - formatArgs = wrappedList(missingAddressesCount, totalNeeded), + count = totalWallets, + formatArgs = wrappedList(walletsToDerive, totalWallets), ), ), ) diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt index ae386cc7c1..7262bbe337 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/TokenItemState.kt @@ -1,5 +1,7 @@ package com.tangem.managetokens.presentation.managetokens.state +import androidx.compose.runtime.MutableState + internal sealed class TokenItemState { abstract val id: String @@ -9,11 +11,13 @@ internal sealed class TokenItemState { data class Loaded( override val id: String, val name: String, - val currencyId: String, + val currencySymbol: String, + val tokenId: String, val tokenIcon: TokenIconState, val quotes: QuotesState, val rate: String?, - val availableAction: TokenButtonType, - val onButtonClick: (String) -> Unit, + val availableAction: MutableState, + val chooseNetworkState: ChooseNetworkState, + val onButtonClick: (Loaded) -> Unit, ) : TokenItemState() } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt new file mode 100644 index 0000000000..12be7db588 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ChooseNetworkStatePreviewData.kt @@ -0,0 +1,60 @@ +package com.tangem.managetokens.presentation.managetokens.state.previewdata + +import androidx.compose.runtime.mutableStateOf +import com.tangem.blockchain.common.Blockchain +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.NetworkItemState +import com.tangem.managetokens.presentation.managetokens.state.ChooseNetworkState +import kotlinx.collections.immutable.toImmutableList + +internal object ChooseNetworkStatePreviewData { + + val state = ChooseNetworkState( + nativeNetworks = nativeNetworks.toImmutableList(), + nonNativeNetworks = nonNativeNetworks.toImmutableList(), + onNonNativeNetworkHintClick = {}, + onCloseChooseNetworkScreen = {}, + ) +} + +internal val nativeNetworks = listOf( + NetworkItemState.Toggleable( + name = "Ethereum", + protocolName = "ETH", + iconResId = mutableStateOf(R.drawable.img_polygon_22), + isMainNetwork = true, + isAdded = mutableStateOf(true), + id = "", + onToggleClick = { _, _ -> }, + blockchain = Blockchain.Ethereum, + address = "", + decimals = 0, + ), +) + +internal val nonNativeNetworks = listOf( + NetworkItemState.Toggleable( + name = "Ethereum", + protocolName = "ETH", + iconResId = mutableStateOf(R.drawable.img_kusama_22), + isMainNetwork = false, + isAdded = mutableStateOf(true), + id = "", + onToggleClick = { _, _ -> }, + blockchain = Blockchain.Ethereum, + address = "", + decimals = 0, + ), + NetworkItemState.Toggleable( + name = "BNB SMART CHAIN", + protocolName = "BEP20", + iconResId = mutableStateOf(R.drawable.ic_bsc_16), + isMainNetwork = false, + isAdded = mutableStateOf(false), + id = "", + onToggleClick = { _, _ -> }, + blockchain = Blockchain.BSC, + address = "", + decimals = 0, + ), +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt index 207ea75cb8..b5d88b814a 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/DerivationNotificationStatePreviewData.kt @@ -4,8 +4,9 @@ import com.tangem.managetokens.presentation.managetokens.state.DerivationNotific object DerivationNotificationStatePreviewData { val state = DerivationNotificationState( - totalNeeded = 3, - missingAddressesCount = 2, + totalNeeded = 5, + totalWallets = 3, + walletsToDerive = 2, onGenerateClick = {}, ) } \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt index bdfedacd3e..01ed9bdda9 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/TokenItemStatePreviewData.kt @@ -1,5 +1,6 @@ package com.tangem.managetokens.presentation.managetokens.state.previewdata +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.Color import com.tangem.managetokens.presentation.managetokens.state.* import kotlinx.collections.immutable.persistentListOf @@ -13,7 +14,8 @@ internal object TokenItemStatePreviewData { get() = TokenItemState.Loaded( id = "BTC", name = "Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin Bitcoin", - currencyId = "BTC", + tokenId = "BTC", + currencySymbol = "BTC", tokenIcon = tokenIconState, quotes = QuotesState.Content( priceChange = "0.43%", @@ -21,15 +23,17 @@ internal object TokenItemStatePreviewData { chartData = persistentListOf(10f, 2f, 5f, 3f, 4f, 8f, 9f, 7f, 4f), ), rate = "31 285.72$", - availableAction = TokenButtonType.ADD, + availableAction = mutableStateOf(TokenButtonType.ADD), onButtonClick = {}, + chooseNetworkState = ChooseNetworkStatePreviewData.state, ) val loadedPriceUp: TokenItemState get() = TokenItemState.Loaded( id = "BTC", name = "Bitcoin", - currencyId = "BTC", + tokenId = "BTC", + currencySymbol = "BTC", tokenIcon = tokenIconState, quotes = QuotesState.Content( priceChange = "0.43%", @@ -37,8 +41,9 @@ internal object TokenItemStatePreviewData { chartData = persistentListOf(1f, 3f, 4f, 8f, 12f, 10f, 8f, 3f, 5f, 7f), ), rate = "31 285.72$", - availableAction = TokenButtonType.NOT_AVAILABLE, + availableAction = mutableStateOf(TokenButtonType.NOT_AVAILABLE), onButtonClick = {}, + chooseNetworkState = ChooseNetworkStatePreviewData.state, ) private val tokenIconState: TokenIconState diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt new file mode 100644 index 0000000000..ebc600073f --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkScreen.kt @@ -0,0 +1,205 @@ +package com.tangem.managetokens.presentation.managetokens.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerH +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.components.WarningCardTitleOnly +import com.tangem.core.ui.decorations.roundedShapeItemDecoration +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData +import com.tangem.managetokens.presentation.common.ui.components.NetworkItem +import com.tangem.managetokens.presentation.common.ui.components.SimpleSelectionBlock +import com.tangem.managetokens.presentation.managetokens.state.ChooseNetworkState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import com.tangem.managetokens.presentation.managetokens.state.previewdata.TokenItemStatePreviewData + +@Composable +internal fun ChooseNetworkScreen( + state: TokenItemState.Loaded, + walletState: ChooseWalletState, + modifier: Modifier = Modifier, +) { + val networkState = state.chooseNetworkState + LazyColumn( + modifier = modifier + .background(TangemTheme.colors.background.tertiary) + .padding(TangemTheme.dimens.spacing16), + ) { + item { + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_title), + color = TangemTheme.colors.text.primary1, + style = TangemTheme.typography.subtitle1, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth(), + ) + } + + when (walletState) { + is ChooseWalletState.Choose -> { + item { + SpacerH(height = TangemTheme.dimens.spacing10) + } + item { + SimpleSelectionBlock( + title = stringResource(id = R.string.manage_tokens_network_selector_wallet), + subtitle = walletState.selectedWallet?.walletName ?: "", + onClick = walletState.onChooseWalletClick, + ) + } + } + ChooseWalletState.NoSelection -> Unit + is ChooseWalletState.Warning -> { + item { + SpacerH(height = TangemTheme.dimens.spacing10) + } + item { + WarningCardTitleOnly( + title = stringResource(id = R.string.manage_tokens_wallet_support_only_one_network_title), + ) + } + } + } + + item { + SpacerH(height = TangemTheme.dimens.spacing16) + } + + item { + if (networkState.nativeNetworks.isNotEmpty()) { + NativeNetworks(networkState = networkState, tokenState = state) + } + } + + if (networkState.nonNativeNetworks.isNotEmpty()) { + item { + NonNativeNetworksHeader(networkState.onNonNativeNetworkHintClick) + } + item { + SpacerH(height = TangemTheme.dimens.spacing8) + } + item { + this@LazyColumn.NonNativeNetworks(networkState = networkState, tokenState = state) + } + } + } +} + +@Composable +private fun NativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { + Column { + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_native_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption1, + ) + SpacerH(height = TangemTheme.dimens.spacing2) + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_native_subtitle), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + ) + SpacerH(height = TangemTheme.dimens.spacing8) + + networkState.nativeNetworks.forEachIndexed { index, network -> + NetworkItem( + state = network, + tokenState = tokenState, + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = networkState.nativeNetworks.lastIndex, + addDefaultPadding = false, + ), + ) + } + SpacerH(height = TangemTheme.dimens.spacing16) + } +} + +@Composable +private fun LazyListScope.NonNativeNetworks(networkState: ChooseNetworkState, tokenState: TokenItemState.Loaded) { + items( + count = networkState.nonNativeNetworks.count(), + key = { index -> networkState.nonNativeNetworks[index].id }, + ) { index -> + NetworkItem( + state = networkState.nonNativeNetworks[index], + tokenState = tokenState, + modifier = Modifier + .roundedShapeItemDecoration( + currentIndex = index, + lastIndex = networkState.nonNativeNetworks.lastIndex, + addDefaultPadding = false, + ), + ) + } + item { + SpacerH(height = TangemTheme.dimens.spacing16) + } +} + +@Composable +private fun NonNativeNetworksHeader(onNonNativeNetworkHintClick: () -> Unit) { + Column { + Row { + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_non_native_title), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption1, + ) + SpacerW(width = TangemTheme.dimens.spacing2) + Icon( + painter = painterResource(id = R.drawable.ic_information_24), + tint = TangemTheme.colors.icon.inactive, + contentDescription = null, + modifier = Modifier + .size(TangemTheme.dimens.size16) + .clickable { onNonNativeNetworkHintClick() }, + ) + } + SpacerH(height = TangemTheme.dimens.spacing2) + Text( + text = stringResource(id = R.string.manage_tokens_network_selector_non_native_subtitle), + color = TangemTheme.colors.text.tertiary, + style = TangemTheme.typography.caption2, + ) + } +} + +@Preview +@Composable +private fun Preview_ChooseNetworkScreen_Light() { + TangemTheme(isDark = false) { + ChooseNetworkScreen( + state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded, + walletState = ChooseWalletStatePreviewData.state, + ) + } +} + +@Preview +@Composable +private fun Preview_ChooseNetworkScreen_Dark() { + TangemTheme(isDark = true) { + ChooseNetworkScreen( + state = TokenItemStatePreviewData.loadedPriceDown as TokenItemState.Loaded, + walletState = ChooseWalletStatePreviewData.state, + ) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt index 139e4f9652..12a403e3e4 100644 --- a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokenRowItem.kt @@ -54,7 +54,7 @@ private fun LoadedTokenItem(state: TokenItemState.Loaded, modifier: Modifier = M modifier = Modifier .weight(weight = 1f), ) { - TokenName(name = state.name, currencyId = state.currencyId) + TokenName(name = state.name, currencyId = state.currencySymbol) TokenPriceData(price = state.rate, quotesState = state.quotes) } SpacerW24() @@ -67,8 +67,8 @@ private fun LoadedTokenItem(state: TokenItemState.Loaded, modifier: Modifier = M } TokenButton( - type = state.availableAction, - onClick = { state.onButtonClick(state.currencyId) }, + type = state.availableAction.value, + onClick = { state.onButtonClick(state) }, ) } }