Collapsable top bar for SecondScreen.kt

This commit is contained in:
Sad Ellie 2022-05-28 23:46:56 +03:00
parent 90780d66ef
commit a37babb63d
2 changed files with 186 additions and 171 deletions

View File

@ -1,15 +1,17 @@
package com.sadellie.unitto.screens.second package com.sadellie.unitto.screens.second
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.sadellie.unitto.R import com.sadellie.unitto.R
import com.sadellie.unitto.data.units.ALL_UNIT_GROUPS import com.sadellie.unitto.data.units.ALL_UNIT_GROUPS
@ -38,53 +40,60 @@ fun SecondScreen(
val chipsRowLazyListState = rememberLazyListState() val chipsRowLazyListState = rememberLazyListState()
val currentUnit = if (leftSide) viewModel.unitFrom else viewModel.unitTo val currentUnit = if (leftSide) viewModel.unitFrom else viewModel.unitTo
var chosenUnitGroup: UnitGroup? by rememberSaveable { mutableStateOf(currentUnit.group) } var chosenUnitGroup: UnitGroup? by rememberSaveable { mutableStateOf(currentUnit.group) }
val scrollBehavior: TopAppBarScrollBehavior = remember {
TopAppBarDefaults.enterAlwaysScrollBehavior()
}
Column { Scaffold(
SearchBar( modifier = Modifier
modifier = Modifier .nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxWidth() topBar = {
.padding(8.dp), SearchBar(
title = stringResource(id = if (leftSide) R.string.units_screen_from else R.string.units_screen_to), title = stringResource(id = if (leftSide) R.string.units_screen_from else R.string.units_screen_to),
value = searchQuery, value = searchQuery,
onValueChange = { onValueChange = {
searchQuery = it searchQuery = it
viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide)
},
favoritesOnly = favoritesOnly,
favoriteAction = {
viewModel.toggleFavoritesOnly()
viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide)
},
navigateUpAction = navigateUp,
focusManager = focusManager
)
if (leftSide) {
ChipsRow(
lazyListState = chipsRowLazyListState,
items = ALL_UNIT_GROUPS,
chosenUnitGroup = chosenUnitGroup,
selectAction = {
chosenUnitGroup = if (it == chosenUnitGroup) null else it
viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide) viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide)
} },
) favoritesOnly = favoritesOnly,
UnitsList( favoriteAction = {
groupedUnits = unitsList, viewModel.toggleFavoritesOnly()
changeAction = { viewModel.changeUnitFrom(it); focusManager.clearFocus(true); navigateUp() }, viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide)
favoriteAction = { viewModel.favoriteUnit(it) }, },
currentUnit = viewModel.unitFrom, navigateUpAction = navigateUp,
) focusManager = focusManager,
} else { scrollBehavior = scrollBehavior
UnitsList(
groupedUnits = unitsList,
changeAction = { viewModel.changeUnitTo(it); focusManager.clearFocus(true); navigateUp() },
favoriteAction = { viewModel.favoriteUnit(it) },
currentUnit = viewModel.unitTo,
inputValue = viewModel.mainUIState.inputValue.toBigDecimal(),
unitFrom = viewModel.unitFrom
) )
} }
) { paddingValues ->
Column(modifier = Modifier.padding(paddingValues)) {
if (leftSide) {
ChipsRow(
lazyListState = chipsRowLazyListState,
items = ALL_UNIT_GROUPS,
chosenUnitGroup = chosenUnitGroup,
selectAction = {
chosenUnitGroup = if (it == chosenUnitGroup) null else it
viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide)
}
)
UnitsList(
groupedUnits = unitsList,
changeAction = { viewModel.changeUnitFrom(it); focusManager.clearFocus(true); navigateUp() },
favoriteAction = { viewModel.favoriteUnit(it) },
currentUnit = viewModel.unitFrom,
)
} else {
UnitsList(
groupedUnits = unitsList,
changeAction = { viewModel.changeUnitTo(it); focusManager.clearFocus(true); navigateUp() },
favoriteAction = { viewModel.favoriteUnit(it) },
currentUnit = viewModel.unitTo,
inputValue = viewModel.mainUIState.inputValue.toBigDecimal(),
unitFrom = viewModel.unitFrom
)
}
}
} }
// This block is called only once on initial composition // This block is called only once on initial composition

View File

@ -8,15 +8,12 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.icons.filled.FavoriteBorder
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.Icon import androidx.compose.material3.*
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -33,7 +30,6 @@ import com.sadellie.unitto.R
/** /**
* Search bar on the Second screen. Controls what will be shown in the list above this component * Search bar on the Second screen. Controls what will be shown in the list above this component
* *
* @param modifier Modifier to be applied to the Row that contains search bar elements
* @param title Search bar title * @param title Search bar title
* @param value Current query * @param value Current query
* @param onValueChange Action to perform when search query changes * @param onValueChange Action to perform when search query changes
@ -41,139 +37,149 @@ import com.sadellie.unitto.R
* @param favoriteAction Function to toggle favorite filter * @param favoriteAction Function to toggle favorite filter
* @param navigateUpAction Function to navigate to previous screen * @param navigateUpAction Function to navigate to previous screen
* @param focusManager Used to hide keyboard when leaving unit selection screen * @param focusManager Used to hide keyboard when leaving unit selection screen
* @param scrollBehavior [TopAppBarScrollBehavior] that is used for collapsing and container color
*/ */
@Composable @Composable
fun SearchBar( fun SearchBar(
modifier: Modifier = Modifier,
title: String = String(), title: String = String(),
value: String = String(), value: String = String(),
onValueChange: (String) -> Unit = {}, onValueChange: (String) -> Unit = {},
favoritesOnly: Boolean, favoritesOnly: Boolean,
favoriteAction: () -> Unit, favoriteAction: () -> Unit,
navigateUpAction: () -> Unit = {}, navigateUpAction: () -> Unit = {},
focusManager: FocusManager focusManager: FocusManager,
scrollBehavior: TopAppBarScrollBehavior
) { ) {
var showSearch by rememberSaveable { mutableStateOf(false) } var showSearch by rememberSaveable { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
Crossfade(targetState = showSearch) { textFieldShown -> fun stagedNavigateUp() {
Row( if (showSearch) {
modifier = modifier, // Search text field is open, need to close it and clear search query
verticalAlignment = Alignment.CenterVertically onValueChange("")
) { focusManager.clearFocus()
when (textFieldShown) { showSearch = false
// No search text field } else {
false -> { // No search text field is shown, can go back as usual
IconButton(onClick = navigateUpAction) { navigateUpAction()
Icon(
Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.navigate_up_description)
)
}
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
text = title,
style = MaterialTheme.typography.titleLarge
)
// Search button
IconButton(onClick = { onValueChange(""); showSearch = true }) {
Icon(
Icons.Default.Search,
contentDescription = stringResource(id = R.string.search_button_description)
)
}
// Favorites button
IconButton(onClick = favoriteAction) {
AnimatedContent(
targetState = favoritesOnly,
transitionSpec = {
(scaleIn() with scaleOut()).using(SizeTransform(clip = false))
}
) {
Icon(
if (favoritesOnly) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
contentDescription = stringResource(id = R.string.favorite_button_description)
)
}
}
}
// With text field
true -> {
IconButton(
onClick = {
// Resetting query
onValueChange("")
focusManager.clearFocus()
showSearch = false
}
) {
Icon(
Icons.Default.ArrowBack,
contentDescription = stringResource(id = R.string.navigate_up_description)
)
}
BasicTextField(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.focusRequester(focusRequester),
value = value,
onValueChange = onValueChange,
singleLine = true,
textStyle = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onSurface),
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = {
// Close searchbar if there is nothing in search query and user
// clicks search button on his keyboard
if (value.isEmpty()) {
showSearch = false
} else {
focusManager.clearFocus()
}
}),
decorationBox = { innerTextField ->
// Showing placeholder only when there is query is empty
if (value.isEmpty()) {
innerTextField()
Text(
modifier = Modifier.alpha(0.7f),
text = stringResource(id = R.string.search_bar_placeholder),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
)
} else {
innerTextField()
}
}
)
// Clear button
IconButton(
modifier = Modifier.alpha(if (value != String()) 1f else 0f),
onClick = { onValueChange("") }
) {
Icon(
Icons.Outlined.Clear,
contentDescription = stringResource(id = R.string.clear_input_description)
)
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}
}
BackHandler {
if (textFieldShown) {
// Search text field is open, need to close it and clear search query
onValueChange("")
showSearch = false
} else {
// No MyTextField shown, can go back as usual
navigateUpAction()
}
} }
} }
SmallTopAppBar(
title = {
Crossfade(targetState = showSearch) { textFieldShown ->
Row(
verticalAlignment = Alignment.CenterVertically
) {
when (textFieldShown) {
// No search text field
false -> {
Text(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
text = title,
style = MaterialTheme.typography.titleLarge
)
}
// With text field
true -> {
BasicTextField(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.focusRequester(focusRequester),
value = value,
onValueChange = onValueChange,
singleLine = true,
textStyle = MaterialTheme.typography.titleLarge.copy(color = MaterialTheme.colorScheme.onSurface),
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = {
// Close searchbar if there is nothing in search query and user
// clicks search button on his keyboard
if (value.isEmpty()) {
showSearch = false
} else {
focusManager.clearFocus()
}
}),
decorationBox = { innerTextField ->
// Showing placeholder only when there is query is empty
if (value.isEmpty()) {
innerTextField()
Text(
modifier = Modifier.alpha(0.7f),
text = stringResource(id = R.string.search_bar_placeholder),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface
)
} else {
innerTextField()
}
}
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}
}
}
},
actions = {
Crossfade(targetState = showSearch) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
when (it) {
false -> {
// Search button
IconButton(onClick = { onValueChange(""); showSearch = true }) {
Icon(
Icons.Default.Search,
contentDescription = stringResource(id = R.string.search_button_description)
)
}
// Favorites button
IconButton(onClick = favoriteAction) {
AnimatedContent(
targetState = favoritesOnly,
transitionSpec = {
(scaleIn() with scaleOut()).using(SizeTransform(clip = false))
}
) {
Icon(
if (favoritesOnly) Icons.Filled.Favorite else Icons.Filled.FavoriteBorder,
contentDescription = stringResource(id = R.string.favorite_button_description)
)
}
}
}
true -> {
// Clear button
IconButton(onClick = { onValueChange("") }) {
Icon(
modifier = Modifier.alpha(if (value.isBlank()) 0f else 1f),
imageVector = Icons.Outlined.Clear,
contentDescription = stringResource(id = R.string.clear_input_description)
)
}
}
}
}
}
},
navigationIcon = {
IconButton(onClick = { stagedNavigateUp() }) {
Icon(
Icons.Outlined.ArrowBack,
contentDescription = stringResource(id = R.string.navigate_up_description)
)
}
},
scrollBehavior = scrollBehavior
)
BackHandler { stagedNavigateUp() }
} }