From a8434ada403c09d938ada4ef197f9d4ea9c03616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C9=91rry=20Shiv=C9=91m?= Date: Thu, 9 May 2024 10:34:16 +0530 Subject: [PATCH] feat: Request focus on search bar and consume back press to close before exit (#124) Signed-off-by: starry-shivam --- .../ui/screens/home/HomeViewModel.kt | 12 +++---- .../screens/home/composables/HomeAppBars.kt | 29 +++++++++++++---- .../ui/screens/home/composables/HomeScreen.kt | 32 ++++++++++++------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt index 83eafdb..a988205 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt @@ -43,7 +43,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch import javax.inject.Inject -enum class SearchWidgetState { OPENED, CLOSED } +enum class SearchBarState { OPENED, CLOSED } enum class FilterField { Title, Amount, Priority } enum class FilterSortType(val value: Int) { Ascending(1), Descending(2) } enum class GoalCardStyle { Classic, Compact } @@ -97,9 +97,9 @@ class HomeViewModel @Inject constructor( } val goalsList = goalsListFlow.asLiveData() - private val _searchWidgetState: MutableState = - mutableStateOf(value = SearchWidgetState.CLOSED) - val searchWidgetState: State = _searchWidgetState + private val _searchBarState: MutableState = + mutableStateOf(value = SearchBarState.CLOSED) + val searchBarState: State = _searchBarState private val _searchTextState: MutableState = mutableStateOf(value = "") val searchTextState: State = _searchTextState @@ -112,8 +112,8 @@ class HomeViewModel @Inject constructor( ) val showOnboardingTapTargets: State = _showOnboardingTapTargets - fun updateSearchWidgetState(newValue: SearchWidgetState) { - _searchWidgetState.value = newValue + fun updateSearchWidgetState(newValue: SearchBarState) { + _searchBarState.value = newValue } fun updateSearchTextState(newValue: String) { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt index b6d63ab..e9c94a6 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeAppBars.kt @@ -50,7 +50,12 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalView @@ -60,43 +65,48 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.starry.greenstash.R -import com.starry.greenstash.ui.screens.home.SearchWidgetState +import com.starry.greenstash.ui.screens.home.SearchBarState import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.weakHapticFeedback @Composable fun HomeAppBar( + searchBarState: SearchBarState, + searchTextState: String, + consumeBackPress: MutableState, onMenuClicked: () -> Unit, onFilterClicked: () -> Unit, onSearchClicked: () -> Unit, - searchWidgetState: SearchWidgetState, - searchTextState: String, onSearchTextChange: (String) -> Unit, onSearchCloseClicked: () -> Unit, onSearchImeAction: (String) -> Unit, ) { Crossfade( - targetState = searchWidgetState, + targetState = searchBarState, animationSpec = tween(durationMillis = 300), label = "searchbar cross-fade" ) { when (it) { - SearchWidgetState.CLOSED -> { + SearchBarState.CLOSED -> { DefaultAppBar( onMenuClicked = onMenuClicked, onFilterClicked = onFilterClicked, onSearchClicked = onSearchClicked ) + consumeBackPress.value = false } - SearchWidgetState.OPENED -> { + SearchBarState.OPENED -> { SearchAppBar( text = searchTextState, onTextChange = onSearchTextChange, onCloseClicked = onSearchCloseClicked, onSearchClicked = onSearchImeAction ) + // Consume the system back button press when the search bar is open + // So we can close the search bar instead of navigating back. + consumeBackPress.value = true } } } @@ -163,10 +173,12 @@ private fun SearchAppBar( onCloseClicked: () -> Unit, onSearchClicked: (String) -> Unit, ) { + val focusRequester = remember { FocusRequester() } Surface( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(insets = WindowInsets.statusBars), + .windowInsetsPadding(insets = WindowInsets.statusBars) + .focusRequester(focusRequester), color = MaterialTheme.colorScheme.surface ) { OutlinedTextField( @@ -223,5 +235,8 @@ private fun SearchAppBar( ), shape = RoundedCornerShape(24.dp) ) + + // Request focus on the search bar when it is opened + LaunchedEffect(Unit) { focusRequester.requestFocus() } } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt index b6e257c..0a096c0 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt @@ -25,6 +25,7 @@ package com.starry.greenstash.ui.screens.home.composables +import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.keyframes import androidx.compose.animation.fadeIn @@ -106,7 +107,7 @@ import com.starry.greenstash.ui.navigation.Screens import com.starry.greenstash.ui.screens.home.FilterField import com.starry.greenstash.ui.screens.home.FilterSortType import com.starry.greenstash.ui.screens.home.HomeViewModel -import com.starry.greenstash.ui.screens.home.SearchWidgetState +import com.starry.greenstash.ui.screens.home.SearchBarState import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.getActivity import com.starry.greenstash.utils.isScrollingUp @@ -129,13 +130,25 @@ fun HomeScreen(navController: NavController) { val filterSheetState = rememberModalBottomSheetState() val drawerState = rememberDrawerState(DrawerValue.Closed) - val searchWidgetState by viewModel.searchWidgetState + val searchBarState by viewModel.searchBarState val searchTextState by viewModel.searchTextState val lazyListState = rememberLazyListState() val snackBarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() + // If search bar is open, then consume back button press to close it. + val consumeBackPress = remember { mutableStateOf(false) } + BackHandler(enabled = consumeBackPress.value) { + if (viewModel.searchBarState.value == SearchBarState.OPENED) { + if (searchTextState.isNotBlank()) { + viewModel.updateSearchTextState(newValue = "") + } else { + viewModel.updateSearchWidgetState(SearchBarState.CLOSED) + } + } + } + if (showFilterSheet.value) { ModalBottomSheet( onDismissRequest = { @@ -175,17 +188,14 @@ fun HomeScreen(navController: NavController) { snackbarHost = { SnackbarHost(snackBarHostState) }, topBar = { HomeAppBar( - onMenuClicked = { coroutineScope.launch { drawerState.open() } }, - onFilterClicked = { - - showFilterSheet.value = true - - }, - onSearchClicked = { viewModel.updateSearchWidgetState(newValue = SearchWidgetState.OPENED) }, - searchWidgetState = searchWidgetState, + searchBarState = searchBarState, searchTextState = searchTextState, + consumeBackPress = consumeBackPress, + onMenuClicked = { coroutineScope.launch { drawerState.open() } }, + onFilterClicked = { showFilterSheet.value = true }, + onSearchClicked = { viewModel.updateSearchWidgetState(newValue = SearchBarState.OPENED) }, onSearchTextChange = { viewModel.updateSearchTextState(newValue = it) }, - onSearchCloseClicked = { viewModel.updateSearchWidgetState(newValue = SearchWidgetState.CLOSED) }, + onSearchCloseClicked = { viewModel.updateSearchWidgetState(newValue = SearchBarState.CLOSED) }, onSearchImeAction = { println("Meow >~< | $it") }, ) },