From a37babb63d63eaf9582c35e3b24e91ca9138c74c Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Sat, 28 May 2022 23:46:56 +0300 Subject: [PATCH] Collapsable top bar for SecondScreen.kt --- .../unitto/screens/second/SecondScreen.kt | 99 ++++--- .../screens/second/components/SearchBar.kt | 258 +++++++++--------- 2 files changed, 186 insertions(+), 171 deletions(-) diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt index ea080b55..923ccc0c 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/SecondScreen.kt @@ -1,15 +1,17 @@ package com.sadellie.unitto.screens.second import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding 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.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.sadellie.unitto.R import com.sadellie.unitto.data.units.ALL_UNIT_GROUPS @@ -38,53 +40,60 @@ fun SecondScreen( val chipsRowLazyListState = rememberLazyListState() val currentUnit = if (leftSide) viewModel.unitFrom else viewModel.unitTo var chosenUnitGroup: UnitGroup? by rememberSaveable { mutableStateOf(currentUnit.group) } + val scrollBehavior: TopAppBarScrollBehavior = remember { + TopAppBarDefaults.enterAlwaysScrollBehavior() + } - Column { - SearchBar( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - title = stringResource(id = if (leftSide) R.string.units_screen_from else R.string.units_screen_to), - value = searchQuery, - onValueChange = { - 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 + Scaffold( + modifier = Modifier + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + SearchBar( + title = stringResource(id = if (leftSide) R.string.units_screen_from else R.string.units_screen_to), + value = searchQuery, + onValueChange = { + searchQuery = 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 + }, + favoritesOnly = favoritesOnly, + favoriteAction = { + viewModel.toggleFavoritesOnly() + viewModel.loadUnitsToShow(searchQuery, chosenUnitGroup, leftSide) + }, + navigateUpAction = navigateUp, + focusManager = focusManager, + scrollBehavior = scrollBehavior ) } + ) { 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 diff --git a/app/src/main/java/com/sadellie/unitto/screens/second/components/SearchBar.kt b/app/src/main/java/com/sadellie/unitto/screens/second/components/SearchBar.kt index a30e5597..9747a3be 100644 --- a/app/src/main/java/com/sadellie/unitto/screens/second/components/SearchBar.kt +++ b/app/src/main/java/com/sadellie/unitto/screens/second/components/SearchBar.kt @@ -8,15 +8,12 @@ import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions 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.FavoriteBorder import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable 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 * - * @param modifier Modifier to be applied to the Row that contains search bar elements * @param title Search bar title * @param value Current query * @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 navigateUpAction Function to navigate to previous screen * @param focusManager Used to hide keyboard when leaving unit selection screen + * @param scrollBehavior [TopAppBarScrollBehavior] that is used for collapsing and container color */ @Composable fun SearchBar( - modifier: Modifier = Modifier, title: String = String(), value: String = String(), onValueChange: (String) -> Unit = {}, favoritesOnly: Boolean, favoriteAction: () -> Unit, navigateUpAction: () -> Unit = {}, - focusManager: FocusManager + focusManager: FocusManager, + scrollBehavior: TopAppBarScrollBehavior ) { var showSearch by rememberSaveable { mutableStateOf(false) } val focusRequester = remember { FocusRequester() } - Crossfade(targetState = showSearch) { textFieldShown -> - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ) { - when (textFieldShown) { - // No search text field - false -> { - IconButton(onClick = 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() - } + fun stagedNavigateUp() { + if (showSearch) { + // Search text field is open, need to close it and clear search query + onValueChange("") + focusManager.clearFocus() + showSearch = false + } else { + // No search text field is 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() } }