Updated on 2026-08-14

This commit is contained in:
Tangem 2023-09-12 11:55:19 +03:00
parent c62c6b032b
commit cad4da6f19
4 changed files with 442 additions and 0 deletions

View file

@ -95,6 +95,7 @@ dependencies {
/** Compose libraries */
implementation(deps.compose.constraintLayout)
implementation(deps.compose.material)
implementation(deps.compose.material3)
implementation(deps.compose.animation)
implementation(deps.compose.coil)
implementation(deps.compose.constraintLayout)

View file

@ -0,0 +1,30 @@
package com.tangem.tap.features.details.ui.appcurrency
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.SystemBarsEffect
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.core.ui.theme.AppThemeModeHolder
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@AndroidEntryPoint
internal class AppCurrencySelectorFragment : ComposeFragment() {
@Inject
override lateinit var appThemeModeHolder: AppThemeModeHolder
@Composable
override fun ScreenContent(modifier: Modifier) {
val systemBarsColor = TangemTheme.colors.background.secondary
SystemBarsEffect {
setSystemBarsColor(systemBarsColor)
}
AppCurrencySelectorScreen(
modifier = modifier,
state = AppCurrencySelectorState.Loading({}), // TODO: Will be updated in next MR
)
}
}

View file

@ -0,0 +1,363 @@
package com.tangem.tap.features.details.ui.appcurrency
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.res.TangemTheme
import com.tangem.tap.features.details.ui.appcurrency.AppCurrencySelectorState.Currency
import com.tangem.wallet.R
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun AppCurrencySelectorScreen(state: AppCurrencySelectorState, modifier: Modifier = Modifier) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val listState = rememberLazyListState()
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
containerColor = TangemTheme.colors.background.secondary,
topBar = {
TopBar(
modifier = Modifier.fillMaxWidth(),
scrollBehavior = scrollBehavior,
state = state,
)
},
content = { paddingValues ->
val contentModifier = Modifier
.padding(paddingValues)
.fillMaxSize()
when (state) {
is AppCurrencySelectorState.Loading -> LoadingList(
modifier = contentModifier,
)
is AppCurrencySelectorState.Content -> CurrenciesList(
modifier = contentModifier,
listState = listState,
currencies = state.items,
selectedId = state.selectedId.orEmpty(),
onCurrencyClick = state.onCurrencyClick,
)
}
},
)
if (state is AppCurrencySelectorState.Content) {
EventEffect(event = state.scrollToSelected) {
listState.scrollToItem(it)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun TopBar(
scrollBehavior: TopAppBarScrollBehavior,
state: AppCurrencySelectorState,
modifier: Modifier = Modifier,
) {
TopAppBar(
modifier = modifier,
scrollBehavior = scrollBehavior,
colors = TopAppBarColors,
navigationIcon = {
IconButton(
modifier = Modifier
.padding(start = TangemTheme.dimens.spacing8)
.size(TangemTheme.dimens.size32),
onClick = state.onBackClick,
) {
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = R.drawable.ic_back_24),
contentDescription = null,
)
}
},
title = {
when (state) {
is AppCurrencySelectorState.Search -> SearchBar(
modifier = Modifier.fillMaxWidth(),
onInputChange = state.onSearchInputChange,
)
is AppCurrencySelectorState.Loading,
is AppCurrencySelectorState.Default,
-> Text(
text = stringResource(id = R.string.details_row_title_currency),
style = TangemTheme.typography.subtitle1,
)
}
},
actions = {
when (state) {
is AppCurrencySelectorState.Content -> {
IconButton(
modifier = Modifier.size(TangemTheme.dimens.size32),
onClick = state.onTopBarActionClick,
) {
val iconResId = when (state) {
is AppCurrencySelectorState.Default -> R.drawable.ic_search_24
is AppCurrencySelectorState.Search -> R.drawable.ic_close_24
}
val iconTint = when (state) {
is AppCurrencySelectorState.Default -> TangemTheme.colors.icon.primary1
is AppCurrencySelectorState.Search -> TangemTheme.colors.icon.informative
}
Icon(
modifier = Modifier.size(TangemTheme.dimens.size24),
painter = painterResource(id = iconResId),
tint = iconTint,
contentDescription = null,
)
}
}
is AppCurrencySelectorState.Loading -> Unit
}
SpacerW(width = TangemTheme.dimens.spacing8)
},
)
}
@Composable
private fun SearchBar(onInputChange: (String) -> Unit, modifier: Modifier = Modifier) {
val focusRequester = remember { FocusRequester() }
var input by remember { mutableStateOf(value = "") }
TextField(
modifier = modifier
.focusRequester(focusRequester),
value = input,
onValueChange = { input = it },
singleLine = true,
textStyle = TangemTheme.typography.subtitle2,
placeholder = {
Text(text = stringResource(id = R.string.common_search))
},
colors = SearchBarColors,
)
LaunchedEffect(key1 = input) {
onInputChange(input)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
@Composable
private fun LoadingList(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
repeat(times = 10) {
Row(
modifier = Modifier
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing24,
)
.height(TangemTheme.dimens.size56)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
space = TangemTheme.dimens.spacing16,
alignment = Alignment.Start,
),
) {
CircleShimmer(modifier = Modifier.size(TangemTheme.dimens.size24))
RectangleShimmer(
modifier = Modifier
.height(TangemTheme.dimens.size24)
.fillMaxWidth(),
)
}
}
}
}
@Composable
private fun CurrenciesList(
listState: LazyListState,
currencies: ImmutableList<Currency>,
selectedId: String,
onCurrencyClick: (Currency) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier,
state = listState,
) {
items(
items = currencies,
key = Currency::id,
) { currency ->
val onClick = remember(key1 = currency) {
{ onCurrencyClick(currency) }
}
CurrencyItem(
modifier = Modifier.fillMaxWidth(),
name = currency.name,
isSelected = currency.id == selectedId,
onClick = onClick,
)
}
}
}
@Composable
private fun CurrencyItem(name: String, isSelected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val interactionSource = remember { MutableInteractionSource() }
Row(
modifier = modifier
.clickable(
interactionSource = interactionSource,
indication = LocalIndication.current,
onClick = onClick,
)
.padding(
start = TangemTheme.dimens.spacing16,
end = TangemTheme.dimens.spacing24,
)
.heightIn(min = TangemTheme.dimens.size56),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(
space = TangemTheme.dimens.spacing16,
alignment = Alignment.Start,
),
) {
RadioButton(
modifier = Modifier.size(TangemTheme.dimens.size24),
selected = isSelected,
onClick = onClick,
interactionSource = interactionSource,
colors = RadioButtonColors,
)
Text(
text = name,
style = TangemTheme.typography.body1,
color = TangemTheme.colors.text.primary1,
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
private val TopAppBarColors: TopAppBarColors
@Composable
get() = TopAppBarDefaults.topAppBarColors(
containerColor = TangemTheme.colors.background.secondary,
// Currently (08.09.23) it's not working when scrolling programmatically
scrolledContainerColor = TangemTheme.colors.background.secondary,
navigationIconContentColor = TangemTheme.colors.icon.primary1,
titleContentColor = TangemTheme.colors.text.primary1,
actionIconContentColor = TangemTheme.colors.icon.primary1,
)
private val SearchBarColors: TextFieldColors
@Composable
get() = TextFieldDefaults.colors(
unfocusedContainerColor = TangemTheme.colors.background.secondary,
focusedContainerColor = TangemTheme.colors.background.secondary,
focusedTextColor = TangemTheme.colors.text.primary1,
unfocusedTextColor = TangemTheme.colors.text.secondary,
focusedPlaceholderColor = TangemTheme.colors.text.disabled,
unfocusedPlaceholderColor = TangemTheme.colors.text.disabled,
focusedIndicatorColor = TangemTheme.colors.background.secondary,
unfocusedIndicatorColor = TangemTheme.colors.background.secondary,
cursorColor = TangemTheme.colors.icon.primary1,
)
private val RadioButtonColors: RadioButtonColors
@Composable
get() = RadioButtonDefaults.colors(
selectedColor = TangemTheme.colors.icon.accent,
unselectedColor = TangemTheme.colors.icon.secondary,
)
// region Preview
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
@Composable
private fun AppCurrencySelectorScreenPreview_Light(
@PreviewParameter(AppCurrencySelectorStateProvider::class) param: AppCurrencySelectorState,
) {
TangemTheme(isDark = false) {
AppCurrencySelectorScreen(param)
}
}
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
@Composable
private fun AppCurrencySelectorScreenPreview_Dark(
@PreviewParameter(AppCurrencySelectorStateProvider::class) param: AppCurrencySelectorState,
) {
TangemTheme(isDark = true) {
AppCurrencySelectorScreen(param)
}
}
private class AppCurrencySelectorStateProvider : CollectionPreviewParameterProvider<AppCurrencySelectorState>(
collection = buildList {
val items = listOf(
"US Dollar (USD) $",
"Inited Arab Emirates Dirham (AED) DH",
"Argentine Peso (ARS) $",
"Australian Dollar (AUD) A$",
"Bangladeshi Taka (BDT) ",
"Bahraini Dinar (BHD) BD",
"Bermudian Dollar (BMD) $",
"Brazil Real (BRL) R$",
"Canadian Dollar (CAD) CA$",
"Swiss Franc (CHF) Fr",
"Chilean Peso (CLP) CLP$",
"Chinese Yan (CNY)",
)
.mapIndexed { index, s -> Currency(index.toString(), s) }
.toPersistentList()
AppCurrencySelectorState.Loading(onBackClick = {}).let(::add)
AppCurrencySelectorState.Default(
selectedId = "0",
items = items,
scrollToSelected = consumedEvent(),
onCurrencyClick = {},
onBackClick = {},
onTopBarActionClick = {},
).let(::add)
AppCurrencySelectorState.Search(
selectedId = "0",
items = items,
scrollToSelected = consumedEvent(),
onCurrencyClick = {},
onBackClick = {},
onSearchInputChange = {},
onTopBarActionClick = {},
).let(::add)
},
)
// endregion Preview

View file

@ -0,0 +1,48 @@
package com.tangem.tap.features.details.ui.appcurrency
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.event.StateEvent
import kotlinx.collections.immutable.PersistentList
@Immutable
internal sealed class AppCurrencySelectorState {
abstract val onBackClick: () -> Unit
data class Loading(
override val onBackClick: () -> Unit,
) : AppCurrencySelectorState()
@Immutable
sealed class Content : AppCurrencySelectorState() {
abstract val selectedId: String
abstract val scrollToSelected: StateEvent<Int>
abstract val items: PersistentList<Currency>
abstract val onCurrencyClick: (Currency) -> Unit
abstract val onTopBarActionClick: () -> Unit
}
data class Default(
override val selectedId: String,
override val items: PersistentList<Currency>,
override val onCurrencyClick: (Currency) -> Unit,
override val onBackClick: () -> Unit,
override val onTopBarActionClick: () -> Unit,
override val scrollToSelected: StateEvent<Int>,
) : Content()
data class Search(
override val selectedId: String,
override val items: PersistentList<Currency>,
override val scrollToSelected: StateEvent<Int>,
override val onCurrencyClick: (Currency) -> Unit,
override val onBackClick: () -> Unit,
override val onTopBarActionClick: () -> Unit,
val onSearchInputChange: (String) -> Unit,
) : Content()
data class Currency(
val id: String,
val name: String,
)
}