From d58ac113f5efb9dd323adbe1a8171317f0975e15 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 | 2 +- .../bottomsheets/TangemBottomSheet.kt | 4 +- .../common/ui/ChooseWalletBottomSheet.kt | 22 +++ .../managetokens/state/ManageTokensState.kt | 24 +++ .../ManageTokensStatePreviewData.kt | 40 +++++ .../ui/ChooseNetworkBottomSheet.kt | 24 +++ .../managetokens/ui/ManageTokensScreen.kt | 144 ++++++++++++++++++ .../ui/components/AddCustomTokenButton.kt | 65 ++++++++ .../managetokens/ui/components/TokensList.kt | 59 +++++++ 9 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt create mode 100644 features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.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 b584136754..e4198a521c 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 @@ -14,11 +14,11 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import com.tangem.core.ui.components.SpacerH4 import com.tangem.core.ui.components.SpacerW32 +import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.tap.features.details.ui.appsettings.AppSettingsItemsFactory import com.tangem.tap.features.details.ui.appsettings.AppSettingsScreenState.Item -import com.tangem.core.ui.components.TangemSwitch @Composable internal fun SettingsSwitchItem(item: Item.Switch, modifier: Modifier = Modifier) { 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 2d9d85b164..76a172ffd9 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 @@ -6,6 +6,7 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.SheetState import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.* +import androidx.compose.ui.graphics.Color import com.tangem.core.ui.res.TangemTheme import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -20,6 +21,7 @@ import kotlinx.coroutines.launch @Composable inline fun TangemBottomSheet( config: TangemBottomSheetConfig, + contentColor: Color = TangemTheme.colors.background.primary, crossinline content: @Composable ColumnScope.(T) -> Unit, ) { var isVisible by remember { mutableStateOf(value = config.isShow) } @@ -29,7 +31,7 @@ inline fun TangemBottomSheet( ModalBottomSheet( onDismissRequest = config.onDismissRequest, sheetState = sheetState, - containerColor = TangemTheme.colors.background.primary, + containerColor = contentColor, shape = TangemTheme.shapes.bottomSheetLarge, dragHandle = { TangemBottomSheetDraggableHeader() }, ) { diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt new file mode 100644 index 0000000000..610f54edc4 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/common/ui/ChooseWalletBottomSheet.kt @@ -0,0 +1,22 @@ +package com.tangem.managetokens.presentation.common.ui + +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.ChooseWalletState + +@Composable +internal fun ChooseWalletBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + contentColor = TangemTheme.colors.background.tertiary, + ) { + ChooseWalletScreen(state = it.chooseWalletState) + } +} + +internal class ChooseWalletBottomSheetConfig( + val chooseWalletState: ChooseWalletState.Choose, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt new file mode 100644 index 0000000000..1ee0ecebce --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/ManageTokensState.kt @@ -0,0 +1,24 @@ +package com.tangem.managetokens.presentation.managetokens.state + +import androidx.paging.PagingData +import com.tangem.core.ui.event.StateEvent +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.state.Event +import kotlinx.coroutines.flow.Flow + +internal data class ManageTokensState( + val searchBarState: SearchBarState, + val tokens: Flow>, + val isLoading: Boolean, + val addCustomTokenButton: AddCustomTokenButton, + val chooseWalletState: ChooseWalletState, + val derivationNotification: DerivationNotificationState? = null, + val selectedToken: TokenItemState.Loaded? = null, + val showChooseWalletScreen: Boolean = false, + val event: StateEvent, +) + +data class AddCustomTokenButton( + val isVisible: Boolean, + val onClick: () -> Unit, +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt new file mode 100644 index 0000000000..6ea8316ba5 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/state/previewdata/ManageTokensStatePreviewData.kt @@ -0,0 +1,40 @@ +package com.tangem.managetokens.presentation.managetokens.state.previewdata + +import androidx.paging.PagingData +import com.tangem.core.ui.event.consumedEvent +import com.tangem.managetokens.presentation.common.state.previewdata.ChooseWalletStatePreviewData +import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton +import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState +import com.tangem.managetokens.presentation.managetokens.state.SearchBarState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import kotlinx.coroutines.flow.flowOf + +internal object ManageTokensStatePreviewData { + val loadedState: ManageTokensState + get() = ManageTokensState( + searchBarState = searchState, + tokens = flowOf(PagingData.from(tokens)), + isLoading = false, + addCustomTokenButton = AddCustomTokenButton(true, {}), + derivationNotification = DerivationNotificationStatePreviewData.state, + event = consumedEvent(), + chooseWalletState = ChooseWalletStatePreviewData.state, + ) + + val loadingState: ManageTokensState + get() = loadedState.copy(isLoading = true) + + private val tokens: List + get() = listOf( + TokenItemStatePreviewData.loadedPriceDown, + TokenItemStatePreviewData.loadedPriceUp, + ) + + private val searchState: SearchBarState + get() = SearchBarState( + query = "", + onQueryChange = {}, + active = false, + onActiveChange = {}, + ) +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt new file mode 100644 index 0000000000..cd1809a96b --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ChooseNetworkBottomSheet.kt @@ -0,0 +1,24 @@ +package com.tangem.managetokens.presentation.managetokens.ui + +import androidx.compose.runtime.Composable +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState + +@Composable +internal fun ChooseNetworkBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + contentColor = TangemTheme.colors.background.tertiary, + ) { + ChooseNetworkScreen(state = it.selectedToken, walletState = it.chooseWalletState) + } +} + +internal class ChooseNetworkBottomSheetConfig( + val selectedToken: TokenItemState.Loaded, + val chooseWalletState: ChooseWalletState, +) : TangemBottomSheetConfigContent \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt new file mode 100644 index 0000000000..1bbf0c61a9 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/ManageTokensScreen.kt @@ -0,0 +1,144 @@ +package com.tangem.managetokens.presentation.managetokens.ui + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider +import androidx.paging.compose.collectAsLazyPagingItems +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.res.TangemTheme +import com.tangem.managetokens.presentation.common.state.AlertState +import com.tangem.managetokens.presentation.common.state.ChooseWalletState +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheet +import com.tangem.managetokens.presentation.common.ui.ChooseWalletBottomSheetConfig +import com.tangem.managetokens.presentation.common.ui.EventEffect +import com.tangem.managetokens.presentation.common.ui.components.Alert +import com.tangem.managetokens.presentation.managetokens.state.ManageTokensState +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState +import com.tangem.managetokens.presentation.managetokens.state.previewdata.ManageTokensStatePreviewData +import com.tangem.managetokens.presentation.managetokens.ui.components.DerivationNotification +import com.tangem.managetokens.presentation.managetokens.ui.components.TokensList +import com.tangem.managetokens.presentation.managetokens.ui.components.TokensSearchBar + +@Composable +internal fun ManageTokensScreen(state: ManageTokensState) { + var alertState by remember { mutableStateOf(value = null) } + + EventEffect( + event = state.event, + onAlertStateSet = { alertState = it }, + ) + alertState?.let { + Alert(state = it, onDismiss = { alertState = null }) + } + + Content(state) +} + +@Composable +private fun Content(state: ManageTokensState) { + Box( + modifier = Modifier + .fillMaxSize() + .background(color = TangemTheme.colors.background.primary) + .padding(top = TangemTheme.dimens.spacing32), + ) { + Column { + val listState = rememberLazyListState() + val raiseSearchBar by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } + + val elevation by animateDpAsState( + targetValue = if (raiseSearchBar) { + TangemTheme.dimens.elevation8 + } else { + TangemTheme.dimens.elevation0 + }, + label = "top_bar_elevation", + ) + Surface( + elevation = elevation, + modifier = Modifier, + ) { + TokensSearchBar( + state = state.searchBarState, + modifier = Modifier + .background(color = TangemTheme.colors.background.primary) + .padding(horizontal = TangemTheme.dimens.spacing16, vertical = TangemTheme.dimens.spacing20), + ) + } + val tokens = state.tokens.collectAsLazyPagingItems() + TokensList(tokens = tokens, addCustomTokenButton = state.addCustomTokenButton) + } + state.derivationNotification?.let { + DerivationNotification( + config = it.config, + modifier = Modifier + .align(Alignment.BottomCenter), + ) + } + state.selectedToken?.let { selectedToken -> + ManageTokensBottomSheet(selectedToken = selectedToken, state = state) + } + } +} + +@Composable +private fun ManageTokensBottomSheet(selectedToken: TokenItemState.Loaded, state: ManageTokensState) { + if (state.showChooseWalletScreen && state.chooseWalletState is ChooseWalletState.Choose) { + val config = TangemBottomSheetConfig( + isShow = true, + content = ChooseWalletBottomSheetConfig(state.chooseWalletState), + onDismissRequest = state.chooseWalletState.onCloseChoosingWalletClick, + ) + ChooseWalletBottomSheet(config) + } else { + val config = TangemBottomSheetConfig( + isShow = true, + content = ChooseNetworkBottomSheetConfig( + selectedToken = selectedToken, + chooseWalletState = state.chooseWalletState, + ), + onDismissRequest = selectedToken.chooseNetworkState.onCloseChooseNetworkScreen, + ) + ChooseNetworkBottomSheet(config) + } +} + +@Preview +@Composable +private fun Preview_ManageTokensScreen_LightTheme( + @PreviewParameter(ManageTokensConfigProvider::class) + state: ManageTokensState, +) { + TangemTheme(isDark = false) { + ManageTokensScreen(state) + } +} + +@Preview +@Composable +private fun Preview_ManageTokensScreen_DarkTheme( + @PreviewParameter(ManageTokensConfigProvider::class) + state: ManageTokensState, +) { + TangemTheme(isDark = true) { + ManageTokensScreen(state) + } +} + +private class ManageTokensConfigProvider : CollectionPreviewParameterProvider( + collection = listOf( + ManageTokensStatePreviewData.loadingState, + ManageTokensStatePreviewData.loadedState, + ), +) \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt new file mode 100644 index 0000000000..195bb4d60c --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/AddCustomTokenButton.kt @@ -0,0 +1,65 @@ +package com.tangem.managetokens.presentation.managetokens.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.core.ui.components.SpacerW +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R + +@Composable +internal fun AddCustomTokenButton(onButtonClick: () -> Unit, modifier: Modifier = Modifier) { + Row( + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically, + modifier = modifier + .defaultMinSize(minHeight = TangemTheme.dimens.size68) + .fillMaxWidth() + .clickable { onButtonClick() } + .padding(horizontal = TangemTheme.dimens.spacing16), + ) { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size36) + .background(color = TangemTheme.colors.button.secondary, shape = CircleShape), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_plus_24), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + ) + } + SpacerW(width = TangemTheme.dimens.spacing12) + Text( + text = stringResource(id = R.string.add_custom_token_title), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + } +} + +@Preview +@Composable +private fun AddCustomTokenButton_Preview_Light() { + TangemTheme(isDark = false) { + AddCustomTokenButton(onButtonClick = { }) + } +} + +@Preview +@Composable +private fun AddCustomTokenButton_Preview_Dark() { + TangemTheme(isDark = true) { + AddCustomTokenButton(onButtonClick = { }) + } +} \ No newline at end of file diff --git a/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt new file mode 100644 index 0000000000..fe6c1c2bf7 --- /dev/null +++ b/features/manage-tokens/impl/src/main/java/com/tangem/managetokens/presentation/managetokens/ui/components/TokensList.kt @@ -0,0 +1,59 @@ +package com.tangem.managetokens.presentation.managetokens.ui.components + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.paging.LoadState +import androidx.paging.compose.LazyPagingItems +import com.tangem.core.ui.res.TangemTheme +import com.tangem.features.managetokens.impl.R +import com.tangem.managetokens.presentation.managetokens.state.AddCustomTokenButton +import com.tangem.managetokens.presentation.managetokens.state.TokenItemState + +private const val PLACEHOLDER_ITEMS_COUNT = 50 + +@Composable +internal fun TokensList(tokens: LazyPagingItems, addCustomTokenButton: AddCustomTokenButton) { + LazyColumn { + item { + Text( + text = stringResource(id = R.string.manage_tokens_title), + style = TangemTheme.typography.h3, + color = TangemTheme.colors.text.primary1, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + + if (tokens.loadState.refresh is LoadState.Loading) { + items(PLACEHOLDER_ITEMS_COUNT) { + TokenRowItem(state = TokenItemState.Loading(it.toString())) + } + } else { + val tokensList = tokens.itemSnapshotList + if (tokensList.isEmpty()) { + item { + Text( + text = stringResource(id = R.string.manage_tokens_nothing_found), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), + ) + } + } + + items(items = tokensList.items, key = TokenItemState::id) { token -> + TokenRowItem(state = token) + } + + if (addCustomTokenButton.isVisible) { + item { + AddCustomTokenButton(onButtonClick = { addCustomTokenButton.onClick }) + } + } + } + } +} \ No newline at end of file