From 836fa6f4aba6f6b6a8b4f9acb93a571c9773a1fc Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 15:19:13 +0400 Subject: [PATCH 1/7] Updated on 2026-08-14 --- features/manage-tokens/api/build.gradle.kts | 3 +++ .../component/AddCustomTokenComponent.kt | 18 ++++++++++++++++++ features/manage-tokens/impl/build.gradle.kts | 5 +++++ gradle/dependencies.toml | 1 + .../extension/BaseExtensionConfigurations.kt | 1 + 5 files changed, 28 insertions(+) create mode 100644 features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt diff --git a/features/manage-tokens/api/build.gradle.kts b/features/manage-tokens/api/build.gradle.kts index 0cf94d3840..8bbbd4e02b 100644 --- a/features/manage-tokens/api/build.gradle.kts +++ b/features/manage-tokens/api/build.gradle.kts @@ -15,4 +15,7 @@ dependencies { /* Project - Core */ implementation(projects.core.ui) implementation(projects.core.decompose) + + /* Compose */ + implementation(deps.compose.runtime) } \ No newline at end of file diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt new file mode 100644 index 0000000000..761758d465 --- /dev/null +++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt @@ -0,0 +1,18 @@ +package com.tangem.features.managetokens.component + +import androidx.compose.runtime.Composable +import com.tangem.domain.wallets.models.UserWalletId + +interface AddCustomTokenComponent { + + @Composable + fun BottomSheet(isVisible: Boolean, onDismiss: () -> Unit) + + data class Params( + val userWalletId: UserWalletId, + ) + + interface Factory { + fun create(params: Params): AddCustomTokenComponent + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts index 05647d3743..1305e90261 100644 --- a/features/manage-tokens/impl/build.gradle.kts +++ b/features/manage-tokens/impl/build.gradle.kts @@ -21,6 +21,10 @@ dependencies { implementation(projects.common.routing) implementation(projects.core.featuretoggles) + /* Project - Domain */ + implementation(projects.domain.wallets.models) + implementation(projects.domain.tokens.models) + /* AndroidX */ implementation(deps.androidx.activity.compose) implementation(deps.lifecycle.compose) @@ -29,6 +33,7 @@ dependencies { implementation(deps.compose.ui) implementation(deps.compose.ui.tooling) implementation(deps.compose.foundation) + implementation(deps.compose.material) // For button colors implementation(deps.compose.material3) implementation(deps.compose.shimmer) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 5dd3fc5ba9..ba5cf565d8 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -155,6 +155,7 @@ androidx-datastore = { module = "androidx.datastore:datastore-preferences", vers # region AndroidX # region Compose +compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose-runtime" } compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose-runtime" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-runtime" } compose-ui-utils = { module = "androidx.compose.ui:ui-util", version.ref = "compose-runtime" } diff --git a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt index 49a6869c47..fe2f2c147f 100644 --- a/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt +++ b/plugins/configuration/src/main/kotlin/com/tangem/plugin/configuration/configurations/extension/BaseExtensionConfigurations.kt @@ -25,6 +25,7 @@ internal fun BaseExtension.configureCompose(project: Project) { contains(Regex(pattern = ":presentation\$")) || contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA] contains(Regex(pattern = ":features:markets:api\$")) || // provides Composable function + contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function contains(Regex(pattern = ":impl\$")) } From a87c618ec6ad054b8f2f61f42e02b11510189c73 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 15:19:57 +0400 Subject: [PATCH 2/7] Updated on 2026-08-14 --- .../ui/components/fields/SimpleTextField.kt | 65 +++++++++---------- .../com/tangem/core/ui/res/TangemTheme.kt | 14 ++-- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt index 59128a93a9..c37307f136 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/fields/SimpleTextField.kt @@ -1,12 +1,11 @@ package com.tangem.core.ui.components.fields import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.text.selection.LocalTextSelectionColors -import androidx.compose.foundation.text.selection.TextSelectionColors import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Modifier @@ -40,6 +39,7 @@ fun SimpleTextField( textStyle: TextStyle = TangemTheme.typography.body2.copy(color = color), placeholderColor: Color = TangemTheme.colors.text.disabled, readOnly: Boolean = false, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, isValuePasted: Boolean = false, onValuePastedTriggerDismiss: () -> Unit = {}, decorationBox: (@Composable (innerTextField: @Composable () -> Unit) -> Unit)? = null, @@ -54,10 +54,6 @@ fun SimpleTextField( ) } val focusRequester = remember { FocusRequester.Default } - val customTextSelectionColors = TextSelectionColors( - handleColor = TangemTheme.colors.text.accent, - backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), - ) val textFieldValue = textFieldValueState.copy(text = value) var lastTextValue by remember(proxyValue, isValuePasted) { textFieldValueState = textFieldValueState.copy( @@ -85,37 +81,36 @@ fun SimpleTextField( } } - CompositionLocalProvider(LocalTextSelectionColors provides customTextSelectionColors) { - BasicTextField( - value = textFieldValue, - onValueChange = { newTextFieldValueState -> - textFieldValueState = newTextFieldValueState + BasicTextField( + value = textFieldValue, + onValueChange = { newTextFieldValueState -> + textFieldValueState = newTextFieldValueState - val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text - lastTextValue = newTextFieldValueState.text + val stringChangedSinceLastInvocation = lastTextValue != newTextFieldValueState.text + lastTextValue = newTextFieldValueState.text - if (stringChangedSinceLastInvocation) onValueChange(newTextFieldValueState.text) - }, - textStyle = textStyle.copy(color = color), - cursorBrush = SolidColor(TangemTheme.colors.text.primary1), - singleLine = singleLine, - readOnly = readOnly, - visualTransformation = visualTransformation, - keyboardOptions = keyboardOptions, - keyboardActions = keyboardActions, - decorationBox = decorationBox ?: { textValue -> - SimpleTextPlaceholder( - placeholder = placeholder, - value = value, - textStyle = textStyle, - textValue = textValue, - color = placeholderColor, - ) - }, - modifier = modifier - .focusRequester(focusRequester), - ) - } + if (stringChangedSinceLastInvocation) onValueChange(newTextFieldValueState.text) + }, + textStyle = textStyle.copy(color = color), + cursorBrush = SolidColor(TangemTheme.colors.text.primary1), + singleLine = singleLine, + readOnly = readOnly, + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + interactionSource = interactionSource, + decorationBox = decorationBox ?: { textValue -> + SimpleTextPlaceholder( + placeholder = placeholder, + value = value, + textStyle = textStyle, + textValue = textValue, + color = placeholderColor, + ) + }, + modifier = modifier + .focusRequester(focusRequester), + ) } @Composable diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index f62345873b..65ffdd461b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -52,11 +52,11 @@ fun TangemTheme( LocalHapticManager provides hapticManager, LocalSnackbarHostState provides snackbarHostState, LocalWindowSize provides windowSize, - LocalTextSelectionColors provides TangemTextSelectionColors, ) { CompositionLocalProvider( LocalTangemShimmer provides TangemShimmer, LocalMainBottomSheetColor provides remember { mutableStateOf(Color.Unspecified) }, + LocalTextSelectionColors provides TangemTextSelectionColors, ) { ProvideTextStyle( value = TangemTheme.typography.body1, @@ -209,11 +209,13 @@ private fun darkThemeColors(): TangemColors { ) } -@Stable -private val TangemTextSelectionColors = TextSelectionColors( - handleColor = TangemColorPalette.Azure, - backgroundColor = TangemColorPalette.Azure.copy(alpha = 0.4f), -) +private val TangemTextSelectionColors: TextSelectionColors + @Composable + @ReadOnlyComposable + get() = TextSelectionColors( + handleColor = TangemTheme.colors.text.accent, + backgroundColor = TangemTheme.colors.text.accent.copy(alpha = 0.3f), + ) private val LocalTangemColors = staticCompositionLocalOf { error("No TangemColors provided") From 6e67390a0d307bfc49f60e73980070276ee4067b Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 15:21:33 +0400 Subject: [PATCH 3/7] Updated on 2026-08-14 --- .../preview/PreviewManageTokensComponent.kt | 28 ++++++++----------- .../managetokens/entity/CurrencyNetworkUM.kt | 9 ++++-- .../managetokens/ui/ManageTokensScreen.kt | 11 +++++++- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt index 8fabf7cfd4..6923cb7136 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewManageTokensComponent.kt @@ -8,9 +8,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.util.fastForEachIndexed import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.components.rows.model.BlockchainRowUM import com.tangem.core.ui.components.rows.model.ChainRowUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.tokens.model.Network import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.entity.CurrencyItemUM import com.tangem.features.managetokens.entity.CurrencyNetworkUM @@ -129,14 +129,11 @@ internal class PreviewManageTokensComponent : ManageTokensComponent { private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex -> CurrencyNetworkUM( - id = networkIndex.toString(), - model = BlockchainRowUM( - name = "NETWORK$networkIndex", - type = "N$networkIndex", - iconResId = R.drawable.ic_eth_16, - isMainNetwork = networkIndex == 0, - isSelected = false, - ), + id = Network.ID(networkIndex.toString()), + name = "NETWORK$networkIndex", + type = "N$networkIndex", + iconResId = R.drawable.ic_eth_16, + isMainNetwork = networkIndex == 0, isSelected = false, onSelectedStateChange = { toggleNetwork(currencyIndex, networkIndex, isSelected = it) }, ) @@ -171,14 +168,11 @@ internal class PreviewManageTokensComponent : ManageTokensComponent { it.fastForEachIndexed { index, network -> if (index == networkIndex) { it[index] = network.copy( - model = network.model.copy( - iconResId = if (isSelected) { - R.drawable.img_eth_22 - } else { - R.drawable.ic_eth_16 - }, - isSelected = isSelected, - ), + iconResId = if (isSelected) { + R.drawable.img_eth_22 + } else { + R.drawable.ic_eth_16 + }, isSelected = isSelected, ) } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CurrencyNetworkUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CurrencyNetworkUM.kt index 8ed8dc5f9d..477585db3f 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CurrencyNetworkUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CurrencyNetworkUM.kt @@ -1,12 +1,15 @@ package com.tangem.features.managetokens.entity import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.rows.model.BlockchainRowUM +import com.tangem.domain.tokens.model.Network @Immutable internal data class CurrencyNetworkUM( - val id: String, - val model: BlockchainRowUM, + val id: Network.ID, + val name: String, + val type: String, + val iconResId: Int, + val isMainNetwork: Boolean, val isSelected: Boolean, val onSelectedStateChange: (Boolean) -> Unit, ) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt index 63a14af8d4..e21347f830 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/ManageTokensScreen.kt @@ -35,6 +35,7 @@ import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.components.rows.ArrowRow import com.tangem.core.ui.components.rows.BlockchainRow import com.tangem.core.ui.components.rows.ChainRow +import com.tangem.core.ui.components.rows.model.BlockchainRowUM import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -245,7 +246,15 @@ private fun NetworksList(networks: NetworksUM, currencyId: String, modifier: Mod isLastItem = index == currentItems.lastIndex, content = { BlockchainRow( - model = network.model, + model = with(network) { + BlockchainRowUM( + name = name, + type = type, + iconResId = iconResId, + isMainNetwork = isMainNetwork, + isSelected = isSelected, + ) + }, action = { TangemSwitch( checked = network.isSelected, From 2aea20e5977f81657d3567807bd6db0094e422c9 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 15:21:44 +0400 Subject: [PATCH 4/7] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/Keyboard.kt | 7 +- .../component/CustomTokenFormComponent.kt | 19 ++ .../CustomTokenNetworkSelectorComponent.kt | 20 ++ .../preview/PreviewAddCustomTokenComponent.kt | 81 ++++++++ .../PreviewCustomTokenFormComponent.kt | 81 ++++++++ ...viewCustomTokenNetworkSelectorComponent.kt | 49 +++++ .../managetokens/entity/AddCustomTokenUM.kt | 50 +++++ .../managetokens/entity/CustomTokenFormUM.kt | 44 ++++ .../entity/CustomTokenNetworkSelectorUM.kt | 9 + .../ui/AddCustomTokenBottomSheet.kt | 148 +++++++++++++ .../managetokens/ui/CustomTokenFormContent.kt | 196 ++++++++++++++++++ .../ui/CustomTokenNetworkSelectorContent.kt | 125 +++++++++++ 12 files changed, 828 insertions(+), 1 deletion(-) create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenNetworkSelectorComponent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenFormUM.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt create mode 100644 features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt index a920fda100..72a9f3a11e 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/Keyboard.kt @@ -3,6 +3,7 @@ package com.tangem.core.ui.components import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.ime import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.platform.LocalDensity @@ -14,11 +15,15 @@ sealed interface Keyboard { data class Opened(override val height: Dp) : Keyboard - object Closed : Keyboard { + data object Closed : Keyboard { override val height: Dp = 0.dp } } +val Keyboard.isOpened: Boolean + @Stable + get() = this is Keyboard.Opened + /** * Allows to subscribe to a soft keyboard to detect when it's open/closed */ diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt new file mode 100644 index 0000000000..acb8dc42a4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt @@ -0,0 +1,19 @@ +package com.tangem.features.managetokens.component + +import androidx.compose.foundation.lazy.LazyListScope +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId + +internal interface CustomTokenFormComponent { + + fun content(scope: LazyListScope) + + data class Params( + val userWalletId: UserWalletId, + val networkId: Network.ID, + ) + + interface Factory { + fun create(params: Params): CustomTokenFormComponent + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenNetworkSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenNetworkSelectorComponent.kt new file mode 100644 index 0000000000..a2551d430c --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenNetworkSelectorComponent.kt @@ -0,0 +1,20 @@ +package com.tangem.features.managetokens.component + +import androidx.compose.foundation.lazy.LazyListScope +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.managetokens.entity.SelectedNetworkUM + +internal interface CustomTokenNetworkSelectorComponent { + + fun content(scope: LazyListScope) + + data class Params( + val userWalletId: UserWalletId, + val selectedNetwork: SelectedNetworkUM?, + val onNetworkSelected: (SelectedNetworkUM) -> Unit, + ) + + interface Factory { + fun create(params: Params): CustomTokenNetworkSelectorComponent + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt new file mode 100644 index 0000000000..a5d8b9f55b --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt @@ -0,0 +1,81 @@ +package com.tangem.features.managetokens.component.preview + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.managetokens.component.AddCustomTokenComponent +import com.tangem.features.managetokens.component.CustomTokenNetworkSelectorComponent +import com.tangem.features.managetokens.entity.AddCustomTokenButtonUM +import com.tangem.features.managetokens.entity.AddCustomTokenUM +import com.tangem.features.managetokens.entity.ClickableFieldUM +import com.tangem.features.managetokens.entity.SelectedNetworkUM +import com.tangem.features.managetokens.impl.R +import com.tangem.features.managetokens.ui.AddCustomTokenBottomSheet +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update + +internal class PreviewAddCustomTokenComponent( + initialState: AddCustomTokenUM = AddCustomTokenUM.NetworkSelector(), +) : AddCustomTokenComponent { + + private val userWalletId = UserWalletId(stringValue = "321") + + private val previewState: MutableStateFlow = MutableStateFlow(initialState) + + @Composable + override fun BottomSheet(isVisible: Boolean, onDismiss: () -> Unit) { + val state by previewState.collectAsStateWithLifecycle() + val config = TangemBottomSheetConfig( + isShow = isVisible, + onDismissRequest = onDismiss, + content = state, + ) + + AddCustomTokenBottomSheet( + config = config, + content = { + when (val s = state) { + is AddCustomTokenUM.Form -> { + PreviewCustomTokenFormComponent( + networkName = ClickableFieldUM( + label = resourceReference(R.string.custom_token_network_input_title), + value = stringReference(s.selectedNetwork.name), + onClick = { showNetworkSelector(s.selectedNetwork) }, + ), + ).content(this) + } + is AddCustomTokenUM.NetworkSelector -> { + PreviewCustomTokenNetworkSelectorComponent( + params = CustomTokenNetworkSelectorComponent.Params( + userWalletId = userWalletId, + selectedNetwork = s.selectedNetwork, + onNetworkSelected = ::showForm, + ), + networksSize = 20, + ).content(this) + } + } + }, + ) + } + + private fun showNetworkSelector(selectedNetwork: SelectedNetworkUM) { + previewState.update { AddCustomTokenUM.NetworkSelector(selectedNetwork) } + } + + private fun showForm(network: SelectedNetworkUM) { + previewState.update { + AddCustomTokenUM.Form( + selectedNetwork = network, + addTokenButton = AddCustomTokenButtonUM.Visible( + isEnabled = false, + onClick = {}, + ), + ) + } + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt new file mode 100644 index 0000000000..625090f28d --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt @@ -0,0 +1,81 @@ +package com.tangem.features.managetokens.component.preview + +import androidx.compose.foundation.lazy.LazyListScope +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.managetokens.component.CustomTokenFormComponent +import com.tangem.features.managetokens.entity.ClickableFieldUM +import com.tangem.features.managetokens.entity.CustomTokenFormUM +import com.tangem.features.managetokens.entity.TextInputFieldUM +import com.tangem.features.managetokens.impl.R +import com.tangem.features.managetokens.ui.customTokenFormContent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +internal class PreviewCustomTokenFormComponent( + networkName: ClickableFieldUM = ClickableFieldUM( + label = resourceReference(R.string.custom_token_network_input_title), + value = stringReference(value = "Ethereum"), + onClick = {}, + ), + derivationPath: ClickableFieldUM = ClickableFieldUM( + label = resourceReference(R.string.custom_token_derivation_path), + value = stringReference(value = "Default"), + onClick = {}, + ), + canAddToken: Boolean = false, + contractAddress: TextInputFieldUM = TextInputFieldUM( + label = resourceReference(R.string.custom_token_contract_address_input_title), + placeholder = stringReference(value = "0x000000000000000000000000000"), + value = "", + onValueChange = {}, + ), + tokenName: TextInputFieldUM = TextInputFieldUM( + label = resourceReference(R.string.custom_token_name_input_title), + placeholder = stringReference(value = "E.g. USD Coin"), + value = "", + onValueChange = {}, + ), + tokenSymbol: TextInputFieldUM = TextInputFieldUM( + label = resourceReference(R.string.custom_token_token_symbol_input_title), + placeholder = stringReference(value = "E.g. USDC"), + value = "", + onValueChange = {}, + ), + tokenDecimals: TextInputFieldUM = TextInputFieldUM( + label = resourceReference(R.string.custom_token_decimals_input_title), + placeholder = stringReference(value = "8"), + value = "", + onValueChange = {}, + ), + notifications: ImmutableList = persistentListOf( + CustomTokenFormUM.NotificationUM( + id = "1", + config = NotificationConfig( + title = stringReference(value = "Note that tokens can be created by anyone"), + subtitle = stringReference(value = "Be aware of adding scam tokens, they can cost nothing"), + iconResId = R.drawable.img_attention_20, + ), + ), + ), +) : CustomTokenFormComponent { + + private val previewState = CustomTokenFormUM( + networkName = networkName, + contractAddress = contractAddress, + tokenName = tokenName, + tokenSymbol = tokenSymbol, + tokenDecimals = tokenDecimals, + derivationPath = derivationPath, + notifications = notifications, + canAddToken = canAddToken, + onDerivationPathClick = {}, + onNetworkClick = {}, + onAddClick = {}, + ) + + override fun content(scope: LazyListScope) { + scope.customTokenFormContent(model = previewState) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt new file mode 100644 index 0000000000..a5713bce2c --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt @@ -0,0 +1,49 @@ +package com.tangem.features.managetokens.component.preview + +import androidx.compose.foundation.lazy.LazyListScope +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.managetokens.component.CustomTokenNetworkSelectorComponent +import com.tangem.features.managetokens.entity.CurrencyNetworkUM +import com.tangem.features.managetokens.entity.CustomTokenNetworkSelectorUM +import com.tangem.features.managetokens.entity.SelectedNetworkUM +import com.tangem.features.managetokens.impl.R +import com.tangem.features.managetokens.ui.customTokenNetworkSelectorContent +import kotlinx.collections.immutable.toImmutableList + +internal class PreviewCustomTokenNetworkSelectorComponent( + private val params: CustomTokenNetworkSelectorComponent.Params = CustomTokenNetworkSelectorComponent.Params( + userWalletId = UserWalletId(stringValue = "321"), + selectedNetwork = null, + onNetworkSelected = {}, + ), + networksSize: Int = 5, +) : CustomTokenNetworkSelectorComponent { + + private val previewNetworks = List(size = networksSize) { networkIndex -> + val n = SelectedNetworkUM( + id = Network.ID(networkIndex.toString()), + name = "Network $networkIndex", + ) + + CurrencyNetworkUM( + id = n.id, + name = n.name, + type = "N$networkIndex", + iconResId = R.drawable.ic_eth_16, + isMainNetwork = false, + isSelected = n.id == params.selectedNetwork?.id, + onSelectedStateChange = { params.onNetworkSelected(n) }, + ) + }.toImmutableList() + + private val previewState = CustomTokenNetworkSelectorUM( + networks = previewNetworks, + ) + + override fun content(scope: LazyListScope) { + scope.customTokenNetworkSelectorContent( + model = previewState, + ) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt new file mode 100644 index 0000000000..c4d4cec789 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt @@ -0,0 +1,50 @@ +package com.tangem.features.managetokens.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.domain.tokens.model.Network + +@Immutable +internal sealed class AddCustomTokenUM : TangemBottomSheetConfigContent { + + abstract val selectedNetwork: SelectedNetworkUM? + abstract val addTokenButton: AddCustomTokenButtonUM + + data class NetworkSelector( + override val selectedNetwork: SelectedNetworkUM? = null, + ) : AddCustomTokenUM() { + + override val addTokenButton: AddCustomTokenButtonUM = AddCustomTokenButtonUM.Hidden + } + + data class Form( + override val selectedNetwork: SelectedNetworkUM, + override val addTokenButton: AddCustomTokenButtonUM.Visible, + ) : AddCustomTokenUM() +} + +@Immutable +internal data class SelectedNetworkUM( + val id: Network.ID, + val name: String, +) + +@Immutable +internal sealed class AddCustomTokenButtonUM { + + open val onClick: () -> Unit = {} + + open val isEnabled: Boolean = false + + val isVisible: Boolean + get() = this is Visible + + data object Hidden : AddCustomTokenButtonUM() { + override val onClick: () -> Unit = {} + } + + data class Visible( + override val isEnabled: Boolean, + override val onClick: () -> Unit, + ) : AddCustomTokenButtonUM() +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenFormUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenFormUM.kt new file mode 100644 index 0000000000..ea9bfbceb0 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenFormUM.kt @@ -0,0 +1,44 @@ +package com.tangem.features.managetokens.entity + +import androidx.compose.runtime.Immutable +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal data class CustomTokenFormUM( + val networkName: ClickableFieldUM, + val contractAddress: TextInputFieldUM, + val tokenName: TextInputFieldUM, + val tokenSymbol: TextInputFieldUM, + val tokenDecimals: TextInputFieldUM, + val derivationPath: ClickableFieldUM, + val notifications: ImmutableList, + val canAddToken: Boolean, + val onNetworkClick: () -> Unit, + val onDerivationPathClick: () -> Unit, + val onAddClick: () -> Unit, +) { + + @Immutable + data class NotificationUM( + val id: String, + val config: NotificationConfig, + ) +} + +@Immutable +internal data class TextInputFieldUM( + val label: TextReference, + val placeholder: TextReference, + val value: String, + val onValueChange: (String) -> Unit, + val error: TextReference? = null, +) + +@Immutable +internal data class ClickableFieldUM( + val label: TextReference, + val value: TextReference, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt new file mode 100644 index 0000000000..4d8480eee3 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt @@ -0,0 +1,9 @@ +package com.tangem.features.managetokens.entity + +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable +internal data class CustomTokenNetworkSelectorUM( + val networks: ImmutableList, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt new file mode 100644 index 0000000000..a955df759a --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt @@ -0,0 +1,148 @@ +package com.tangem.features.managetokens.ui + +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material3.FabPosition +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.isOpened +import com.tangem.core.ui.components.keyboardAsState +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.tokens.model.Network +import com.tangem.features.managetokens.component.AddCustomTokenComponent +import com.tangem.features.managetokens.component.preview.PreviewAddCustomTokenComponent +import com.tangem.features.managetokens.entity.AddCustomTokenButtonUM +import com.tangem.features.managetokens.entity.AddCustomTokenUM +import com.tangem.features.managetokens.entity.SelectedNetworkUM +import com.tangem.features.managetokens.impl.R + +@Composable +internal fun AddCustomTokenBottomSheet(config: TangemBottomSheetConfig, content: LazyListScope.() -> Unit) { + TangemBottomSheet( + config = config, + titleText = resourceReference(R.string.add_custom_token_title), + containerColor = TangemTheme.colors.background.secondary, + content = { model: AddCustomTokenUM -> + Content( + model = model, + content = content, + ) + }, + ) +} + +@Composable +private fun Content(model: AddCustomTokenUM, content: LazyListScope.() -> Unit, modifier: Modifier = Modifier) { + val density = LocalDensity.current + val keyboardState by keyboardAsState() + + var fabHeight by remember { mutableStateOf(0.dp) } + + Scaffold( + modifier = modifier.imePadding(), + containerColor = TangemTheme.colors.background.secondary, + floatingActionButtonPosition = FabPosition.Center, + floatingActionButton = { + AnimatedVisibility( + modifier = Modifier.onSizeChanged { + fabHeight = with(density) { it.height.toDp() } + }, + visible = model.addTokenButton.isVisible && !keyboardState.isOpened, + enter = fadeIn(), + exit = fadeOut(), + label = "Add button visibility", + ) { + PrimaryButton( + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing16) + .padding(horizontal = TangemTheme.dimens.spacing16) + .fillMaxWidth(), + text = stringResource(id = R.string.custom_token_add_token), + enabled = model.addTokenButton.isEnabled, + onClick = model.addTokenButton.onClick, + ) + } + }, + ) { paddingValues -> + LazyColumn( + modifier = Modifier.padding(paddingValues), + contentPadding = PaddingValues( + start = TangemTheme.dimens.spacing16, + end = TangemTheme.dimens.spacing16, + bottom = TangemTheme.dimens.spacing32 + fabHeight, + ), + ) { + item { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = TangemTheme.dimens.spacing16), + contentAlignment = Alignment.Center, + ) { + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7f), + text = stringResource(id = R.string.custom_token_subtitle), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + } + } + + content() + } + } +} + +// region Preview +@Composable +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_AddCustomTokenBottomSheet( + @PreviewParameter(AddCustomTokenComponentPreviewProvider::class) component: AddCustomTokenComponent, +) { + TangemThemePreview { + component.BottomSheet(isVisible = true, onDismiss = {}) + } +} + +private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + PreviewAddCustomTokenComponent(), + PreviewAddCustomTokenComponent( + initialState = AddCustomTokenUM.Form( + selectedNetwork = SelectedNetworkUM( + id = Network.ID(value = "1"), + name = "Ethereum", + ), + addTokenButton = AddCustomTokenButtonUM.Visible( + isEnabled = false, + onClick = {}, + ), + ), + ), + ) +} +// endregion Preview \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt new file mode 100644 index 0000000000..e898e44150 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt @@ -0,0 +1,196 @@ +package com.tangem.features.managetokens.ui + +import android.content.res.Configuration +import androidx.compose.animation.animateColorAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.block.information.InformationBlock +import com.tangem.core.ui.components.fields.SimpleTextField +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.managetokens.component.preview.PreviewCustomTokenFormComponent +import com.tangem.features.managetokens.entity.ClickableFieldUM +import com.tangem.features.managetokens.entity.CustomTokenFormUM +import com.tangem.features.managetokens.entity.TextInputFieldUM + +internal fun LazyListScope.customTokenFormContent(model: CustomTokenFormUM) { + item { + ClickableField( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + model = model.networkName, + ) + } + + item { + Column( + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing12) + .background( + color = TangemTheme.colors.background.action, + shape = TangemTheme.shapes.roundedCornersXMedium, + ), + ) { + TextField( + model = model.contractAddress, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Next, + ), + ) + TextField( + model = model.tokenName, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Next, + ), + ) + TextField( + model = model.tokenSymbol, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Next, + ), + ) + TextField( + model = model.tokenDecimals, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Decimal, + imeAction = ImeAction.Next, + ), + ) + } + } + + item { + ClickableField( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + model = model.derivationPath, + ) + } + + items( + items = model.notifications, + key = { it.id }, + ) { notification -> + Notification( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + config = notification.config, + containerColor = TangemTheme.colors.button.disabled, + ) + } +} + +@Composable +private fun TextField( + model: TextInputFieldUM, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, +) { + InformationBlock( + modifier = modifier, + title = { + val color by animateColorAsState( + targetValue = if (model.error != null) { + TangemTheme.colors.text.warning + } else { + TangemTheme.colors.text.tertiary + }, + label = "Field label color", + ) + + Text( + text = (model.error ?: model.label).resolveReference(), + style = TangemTheme.typography.subtitle2, + color = color, + ) + }, + content = { + SimpleTextField( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + value = model.value, + onValueChange = model.onValueChange, + readOnly = false, + placeholder = model.placeholder, + singleLine = true, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + ) + }, + ) +} + +@Composable +private fun ClickableField(model: ClickableFieldUM, modifier: Modifier = Modifier) { + InformationBlock( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .clickable(onClick = model.onClick), + title = { + Text( + text = model.label.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + }, + content = { + Text( + modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12), + text = model.value.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + }, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_CustomTokenFormContent( + @PreviewParameter(PreviewCustomTokenFormComponentProvider::class) + component: PreviewCustomTokenFormComponent, +) { + TangemThemePreview { + LazyColumn( + modifier = Modifier.background(color = TangemTheme.colors.background.secondary), + ) { component.content(scope = this) } + } +} + +private class PreviewCustomTokenFormComponentProvider : + PreviewParameterProvider { + + override val values: Sequence + get() = sequenceOf( + PreviewCustomTokenFormComponent(), + PreviewCustomTokenFormComponent( + contractAddress = TextInputFieldUM( + label = stringReference("Contract address"), + value = "0x1234567890", + error = stringReference("Contract address is invalid"), + placeholder = stringReference("0x1234567890"), + onValueChange = {}, + ), + ), + ) +} +// endregion Preview \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt new file mode 100644 index 0000000000..10e6f1f107 --- /dev/null +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt @@ -0,0 +1,125 @@ +package com.tangem.features.managetokens.ui + +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +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.graphics.RectangleShape +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.rows.ChainRow +import com.tangem.core.ui.components.rows.model.ChainRowUM +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.features.managetokens.component.preview.PreviewCustomTokenNetworkSelectorComponent +import com.tangem.features.managetokens.entity.CurrencyNetworkUM +import com.tangem.features.managetokens.entity.CustomTokenNetworkSelectorUM +import com.tangem.features.managetokens.impl.R + +internal fun LazyListScope.customTokenNetworkSelectorContent(model: CustomTokenNetworkSelectorUM) { + val lastIndex = model.networks.lastIndex + + item { + Box( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size36) + .background( + color = TangemTheme.colors.background.primary, + shape = TangemTheme.shapes.bottomSheet, + ), + ) { + Text( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing6, + ) + .padding(horizontal = TangemTheme.dimens.spacing12), + text = "Choose network", + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + + itemsIndexed( + items = model.networks, + key = { _, item -> item.id.value }, + ) { index, item -> + NetworkItem( + modifier = Modifier + .fillMaxWidth() + .clip( + shape = if (index == lastIndex) { + RoundedCornerShape( + bottomStart = TangemTheme.dimens.radius16, + bottomEnd = TangemTheme.dimens.radius16, + ) + } else { + RectangleShape + }, + ) + .background(color = TangemTheme.colors.background.primary) + .clickable(onClick = { item.onSelectedStateChange(true) }) + .padding(horizontal = TangemTheme.dimens.spacing4), + model = item, + ) + } +} + +@Composable +private fun NetworkItem(model: CurrencyNetworkUM, modifier: Modifier = Modifier) { + ChainRow( + modifier = modifier, + model = with(model) { + ChainRowUM( + name = name, + type = type, + icon = CurrencyIconState.CoinIcon( + url = null, + fallbackResId = model.iconResId, + isGrayscale = false, + showCustomBadge = false, + ), + showCustom = false, + ) + }, + action = { + AnimatedVisibility( + modifier = Modifier.size(TangemTheme.dimens.size24), + visible = model.isSelected, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_check_24), + tint = TangemTheme.colors.icon.accent, + contentDescription = null, + ) + } + }, + ) +} + +// region Preview +@Composable +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +private fun Preview_CustomTokenNetworkSelectorContent() { + TangemThemePreview { + LazyColumn { + PreviewCustomTokenNetworkSelectorComponent().content(this) + } + } +} +// endregion Preview \ No newline at end of file From 3315c1b92aec64cbcb016b98a2f599cf852d0d99 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 19:36:30 +0400 Subject: [PATCH 5/7] Updated on 2026-08-14 --- .../core/ui/components/bottomsheets/TangemBottomSheet.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt index 67466771ba..388e6b69c2 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/TangemBottomSheet.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.LocalBottomSheetAlwaysVisible @@ -114,6 +115,7 @@ inline fun PreviewBottomSheet( crossinline content: @Composable (ColumnScope.(T) -> Unit), ) { BasicBottomSheet( + modifier = Modifier.width(360.dp), config = config, sheetState = SheetState( skipPartiallyExpanded = true, @@ -137,6 +139,7 @@ inline fun BasicBottomSheet( addBottomInsets: Boolean, crossinline title: @Composable (BoxScope.(T) -> Unit), crossinline content: @Composable (ColumnScope.(T) -> Unit), + modifier: Modifier = Modifier, ) { val model = config.content as? T ?: return @@ -145,7 +148,7 @@ inline fun BasicBottomSheet( ModalBottomSheet( // FIXME temporary solution to fix height of the bottom sheet - modifier = Modifier.sizeIn(maxHeight = LocalWindowSize.current.height - statusBarHeight), + modifier = modifier.heightIn(max = LocalWindowSize.current.height - statusBarHeight), onDismissRequest = config.onDismissRequest, sheetState = sheetState, containerColor = containerColor, From f599b2f4ce4bb3c22f422b861c847342cb5bf105 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 1 Aug 2024 20:14:43 +0400 Subject: [PATCH 6/7] Updated on 2026-08-14 --- .../preview/PreviewAddCustomTokenComponent.kt | 7 +- ...viewCustomTokenNetworkSelectorComponent.kt | 1 + .../managetokens/entity/AddCustomTokenUM.kt | 4 + .../entity/CustomTokenNetworkSelectorUM.kt | 1 + .../ui/AddCustomTokenBottomSheet.kt | 72 ++++++++++++---- .../ui/CustomTokenNetworkSelectorContent.kt | 85 +++++++++++++------ 6 files changed, 126 insertions(+), 44 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt index a5d8b9f55b..b428c0ed2d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewAddCustomTokenComponent.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update internal class PreviewAddCustomTokenComponent( - initialState: AddCustomTokenUM = AddCustomTokenUM.NetworkSelector(), + initialState: AddCustomTokenUM = AddCustomTokenUM.NetworkSelector(popBack = {}), ) : AddCustomTokenComponent { private val userWalletId = UserWalletId(stringValue = "321") @@ -64,12 +64,15 @@ internal class PreviewAddCustomTokenComponent( } private fun showNetworkSelector(selectedNetwork: SelectedNetworkUM) { - previewState.update { AddCustomTokenUM.NetworkSelector(selectedNetwork) } + previewState.update { + AddCustomTokenUM.NetworkSelector(selectedNetwork, popBack = { showForm(selectedNetwork) }) + } } private fun showForm(network: SelectedNetworkUM) { previewState.update { AddCustomTokenUM.Form( + popBack = {}, selectedNetwork = network, addTokenButton = AddCustomTokenButtonUM.Visible( isEnabled = false, diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt index a5713bce2c..620dbe1222 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenNetworkSelectorComponent.kt @@ -38,6 +38,7 @@ internal class PreviewCustomTokenNetworkSelectorComponent( }.toImmutableList() private val previewState = CustomTokenNetworkSelectorUM( + showTitle = params.selectedNetwork == null, networks = previewNetworks, ) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt index c4d4cec789..a34939d61f 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/AddCustomTokenUM.kt @@ -10,8 +10,11 @@ internal sealed class AddCustomTokenUM : TangemBottomSheetConfigContent { abstract val selectedNetwork: SelectedNetworkUM? abstract val addTokenButton: AddCustomTokenButtonUM + abstract val popBack: () -> Unit + data class NetworkSelector( override val selectedNetwork: SelectedNetworkUM? = null, + override val popBack: () -> Unit, ) : AddCustomTokenUM() { override val addTokenButton: AddCustomTokenButtonUM = AddCustomTokenButtonUM.Hidden @@ -20,6 +23,7 @@ internal sealed class AddCustomTokenUM : TangemBottomSheetConfigContent { data class Form( override val selectedNetwork: SelectedNetworkUM, override val addTokenButton: AddCustomTokenButtonUM.Visible, + override val popBack: () -> Unit, ) : AddCustomTokenUM() } diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt index 4d8480eee3..1435d25f7d 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/CustomTokenNetworkSelectorUM.kt @@ -5,5 +5,6 @@ import kotlinx.collections.immutable.ImmutableList @Immutable internal data class CustomTokenNetworkSelectorUM( + val showTitle: Boolean, val networks: ImmutableList, ) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt index a955df759a..a002b54b4c 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt @@ -22,8 +22,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.TangemTopAppBarHeight +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle import com.tangem.core.ui.components.isOpened import com.tangem.core.ui.components.keyboardAsState import com.tangem.core.ui.extensions.resourceReference @@ -39,11 +43,13 @@ import com.tangem.features.managetokens.impl.R @Composable internal fun AddCustomTokenBottomSheet(config: TangemBottomSheetConfig, content: LazyListScope.() -> Unit) { - TangemBottomSheet( + TangemBottomSheet( config = config, - titleText = resourceReference(R.string.add_custom_token_title), + title = { model -> + Title(model) + }, containerColor = TangemTheme.colors.background.secondary, - content = { model: AddCustomTokenUM -> + content = { model -> Content( model = model, content = content, @@ -52,6 +58,26 @@ internal fun AddCustomTokenBottomSheet(config: TangemBottomSheetConfig, content: ) } +@Composable +private fun Title(model: AddCustomTokenUM, modifier: Modifier = Modifier) { + val showTokenNetworkTitle = model is AddCustomTokenUM.NetworkSelector && model.selectedNetwork != null + + if (showTokenNetworkTitle) { + TangemTopAppBar( + modifier = modifier, + title = resourceReference(R.string.custom_token_network_selector_title), + titleAlignment = Alignment.CenterHorizontally, + startButton = TopAppBarButtonUM.Back(model.popBack), + height = TangemTopAppBarHeight.BOTTOM_SHEET, + ) + } else { + TangemBottomSheetTitle( + modifier = modifier, + title = resourceReference(R.string.add_custom_token_title), + ) + } +} + @Composable private fun Content(model: AddCustomTokenUM, content: LazyListScope.() -> Unit, modifier: Modifier = Modifier) { val density = LocalDensity.current @@ -94,19 +120,23 @@ private fun Content(model: AddCustomTokenUM, content: LazyListScope.() -> Unit, ), ) { item { - Box( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = TangemTheme.dimens.spacing16), - contentAlignment = Alignment.Center, - ) { - Text( - modifier = Modifier.fillMaxWidth(fraction = 0.7f), - text = stringResource(id = R.string.custom_token_subtitle), - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, - ) + if (model is AddCustomTokenUM.NetworkSelector && model.selectedNetwork != null) { + Spacer(modifier = Modifier.size(TangemTheme.dimens.spacing12)) + } else { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = TangemTheme.dimens.spacing16), + contentAlignment = Alignment.Center, + ) { + Text( + modifier = Modifier.fillMaxWidth(fraction = 0.7f), + text = stringResource(id = R.string.custom_token_subtitle), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + ) + } } } @@ -131,8 +161,18 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider< override val values: Sequence get() = sequenceOf( PreviewAddCustomTokenComponent(), + PreviewAddCustomTokenComponent( + initialState = AddCustomTokenUM.NetworkSelector( + popBack = {}, + selectedNetwork = SelectedNetworkUM( + id = Network.ID(value = "0"), + name = "Ethereum", + ), + ), + ), PreviewAddCustomTokenComponent( initialState = AddCustomTokenUM.Form( + popBack = {}, selectedNetwork = SelectedNetworkUM( id = Network.ID(value = "1"), name = "Ethereum", diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt index 10e6f1f107..57abdb15b5 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenNetworkSelectorContent.kt @@ -16,41 +16,50 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.RectangleShape 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.PreviewParameterProvider import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.rows.ChainRow import com.tangem.core.ui.components.rows.model.ChainRowUM import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.managetokens.component.CustomTokenNetworkSelectorComponent import com.tangem.features.managetokens.component.preview.PreviewCustomTokenNetworkSelectorComponent import com.tangem.features.managetokens.entity.CurrencyNetworkUM import com.tangem.features.managetokens.entity.CustomTokenNetworkSelectorUM +import com.tangem.features.managetokens.entity.SelectedNetworkUM import com.tangem.features.managetokens.impl.R internal fun LazyListScope.customTokenNetworkSelectorContent(model: CustomTokenNetworkSelectorUM) { val lastIndex = model.networks.lastIndex - item { - Box( - modifier = Modifier - .fillMaxWidth() - .heightIn(min = TangemTheme.dimens.size36) - .background( - color = TangemTheme.colors.background.primary, - shape = TangemTheme.shapes.bottomSheet, - ), - ) { - Text( + if (model.showTitle) { + item { + Box( modifier = Modifier - .padding( - top = TangemTheme.dimens.spacing12, - bottom = TangemTheme.dimens.spacing6, - ) - .padding(horizontal = TangemTheme.dimens.spacing12), - text = "Choose network", - style = TangemTheme.typography.subtitle2, - color = TangemTheme.colors.text.tertiary, - ) + .fillMaxWidth() + .heightIn(min = TangemTheme.dimens.size36) + .background( + color = TangemTheme.colors.background.primary, + shape = TangemTheme.shapes.bottomSheet, + ), + ) { + Text( + modifier = Modifier + .padding( + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing6, + ) + .padding(horizontal = TangemTheme.dimens.spacing12), + text = stringResource(R.string.add_custom_token_choose_network), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } } } @@ -62,13 +71,16 @@ internal fun LazyListScope.customTokenNetworkSelectorContent(model: CustomTokenN modifier = Modifier .fillMaxWidth() .clip( - shape = if (index == lastIndex) { - RoundedCornerShape( + shape = when { + !model.showTitle && index == 0 -> RoundedCornerShape( + topStart = TangemTheme.dimens.radius16, + topEnd = TangemTheme.dimens.radius16, + ) + index == lastIndex -> RoundedCornerShape( bottomStart = TangemTheme.dimens.radius16, bottomEnd = TangemTheme.dimens.radius16, ) - } else { - RectangleShape + else -> RectangleShape }, ) .background(color = TangemTheme.colors.background.primary) @@ -115,11 +127,32 @@ private fun NetworkItem(model: CurrencyNetworkUM, modifier: Modifier = Modifier) @Composable @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) -private fun Preview_CustomTokenNetworkSelectorContent() { +private fun Preview_CustomTokenNetworkSelectorContent( + @PreviewParameter(CustomTokenNetworkSelectorComponentPreviewProvider::class) + component: CustomTokenNetworkSelectorComponent, +) { TangemThemePreview { LazyColumn { - PreviewCustomTokenNetworkSelectorComponent().content(this) + component.content(this) } } } + +private class CustomTokenNetworkSelectorComponentPreviewProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + PreviewCustomTokenNetworkSelectorComponent(), + PreviewCustomTokenNetworkSelectorComponent( + params = CustomTokenNetworkSelectorComponent.Params( + userWalletId = UserWalletId(stringValue = "321"), + selectedNetwork = SelectedNetworkUM( + id = Network.ID(value = "0"), + name = "", + ), + onNetworkSelected = {}, + ), + ), + ) +} // endregion Preview \ No newline at end of file From 9e54099b63c41e0f5bca5a56333221535fb5a14c Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 5 Aug 2024 13:25:59 +0400 Subject: [PATCH 7/7] Updated on 2026-08-14 --- .../managetokens/model/ManageTokensModel.kt | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt index b7527834c1..2361655244 100644 --- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt +++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt @@ -7,9 +7,9 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.fields.entity.SearchBarUM -import com.tangem.core.ui.components.rows.model.BlockchainRowUM import com.tangem.core.ui.components.rows.model.ChainRowUM import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.tokens.model.Network import com.tangem.features.managetokens.entity.CurrencyItemUM import com.tangem.features.managetokens.entity.CurrencyNetworkUM import com.tangem.features.managetokens.entity.ManageTokensUM @@ -109,14 +109,11 @@ internal class ManageTokensModel @Inject constructor( private fun getCurrencyNetworks(currencyIndex: Int) = List(size = 3) { networkIndex -> CurrencyNetworkUM( - id = networkIndex.toString(), - model = BlockchainRowUM( - name = "NETWORK$networkIndex", - type = "N$networkIndex", - iconResId = R.drawable.ic_eth_16, - isMainNetwork = networkIndex == 0, - isSelected = false, - ), + id = Network.ID(networkIndex.toString()), + name = "NETWORK$networkIndex", + type = "N$networkIndex", + iconResId = R.drawable.ic_eth_16, + isMainNetwork = networkIndex == 0, isSelected = false, onSelectedStateChange = { toggleNetwork(currencyIndex, networkIndex, isSelected = it) }, ) @@ -151,14 +148,11 @@ internal class ManageTokensModel @Inject constructor( it.fastForEachIndexed { index, network -> if (index == networkIndex) { it[index] = network.copy( - model = network.model.copy( - iconResId = if (isSelected) { - R.drawable.img_eth_22 - } else { - R.drawable.ic_eth_16 - }, - isSelected = isSelected, - ), + iconResId = if (isSelected) { + R.drawable.img_eth_22 + } else { + R.drawable.ic_eth_16 + }, isSelected = isSelected, ) }