Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-29 22:55:34 +01:00
commit 536dcb4f3e
9 changed files with 382 additions and 2 deletions

View file

@ -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) {

View file

@ -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 <reified T : TangemBottomSheetConfigContent> 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 <reified T : TangemBottomSheetConfigContent> TangemBottomSheet(
ModalBottomSheet(
onDismissRequest = config.onDismissRequest,
sheetState = sheetState,
containerColor = TangemTheme.colors.background.primary,
containerColor = contentColor,
shape = TangemTheme.shapes.bottomSheetLarge,
dragHandle = { TangemBottomSheetDraggableHeader() },
) {

View file

@ -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<ChooseWalletBottomSheetConfig>(
config = config,
contentColor = TangemTheme.colors.background.tertiary,
) {
ChooseWalletScreen(state = it.chooseWalletState)
}
}
internal class ChooseWalletBottomSheetConfig(
val chooseWalletState: ChooseWalletState.Choose,
) : TangemBottomSheetConfigContent

View file

@ -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<PagingData<TokenItemState>>,
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<Event>,
)
data class AddCustomTokenButton(
val isVisible: Boolean,
val onClick: () -> Unit,
)

View file

@ -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<TokenItemState>
get() = listOf(
TokenItemStatePreviewData.loadedPriceDown,
TokenItemStatePreviewData.loadedPriceUp,
)
private val searchState: SearchBarState
get() = SearchBarState(
query = "",
onQueryChange = {},
active = false,
onActiveChange = {},
)
}

View file

@ -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<ChooseNetworkBottomSheetConfig>(
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

View file

@ -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<AlertState?>(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<ManageTokensState>(
collection = listOf(
ManageTokensStatePreviewData.loadingState,
ManageTokensStatePreviewData.loadedState,
),
)

View file

@ -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 = { })
}
}

View file

@ -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<TokenItemState>, 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 })
}
}
}
}
}