From 28db65f1088a8f3db1e8122d5100697b432eeb3a Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 23 Jan 2026 11:25:46 -0300 Subject: [PATCH] Clean up top app bar search bar --- .../main/kotlin/org/fdroid/ui/apps/MyApps.kt | 4 +- .../kotlin/org/fdroid/ui/lists/AppList.kt | 9 +- .../org/fdroid/ui/lists/TopSearchBar.kt | 85 ------------------- .../org/fdroid/ui/search/ExpandedSearch.kt | 55 +----------- .../org/fdroid/ui/search/TopSearchBar.kt | 64 ++++++++++++++ 5 files changed, 74 insertions(+), 143 deletions(-) delete mode 100644 app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt create mode 100644 app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt diff --git a/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt b/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt index d4950cc9d..da1e4f9b4 100644 --- a/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt +++ b/app/src/main/kotlin/org/fdroid/ui/apps/MyApps.kt @@ -44,7 +44,7 @@ import org.fdroid.database.AppListSortOrder.LAST_UPDATED import org.fdroid.download.NetworkState import org.fdroid.install.InstallConfirmationState import org.fdroid.ui.FDroidContent -import org.fdroid.ui.lists.TopSearchBar +import org.fdroid.ui.search.TopSearchBar import org.fdroid.ui.utils.BigLoadingIndicator import org.fdroid.ui.utils.getMyAppsInfo import org.fdroid.ui.utils.myAppsModel @@ -84,7 +84,7 @@ fun MyApps( Scaffold( topBar = { if (searchActive) { - TopSearchBar(onSearch = myAppsInfo::search, onSearchCleared) { + TopSearchBar(onSearch = myAppsInfo::search, onSearchCleared = onSearchCleared) { onBackPressedDispatcher?.onBackPressed() } } else TopAppBar( diff --git a/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt b/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt index 0655c50d8..b46cec95d 100644 --- a/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt +++ b/app/src/main/kotlin/org/fdroid/ui/lists/AppList.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.plus import androidx.compose.foundation.lazy.LazyColumn @@ -52,6 +53,7 @@ import com.viktormykhailiv.compose.hints.rememberHintController import org.fdroid.R import org.fdroid.database.AppListSortOrder import org.fdroid.ui.FDroidContent +import org.fdroid.ui.search.TopSearchBar import org.fdroid.ui.utils.BigLoadingIndicator import org.fdroid.ui.utils.OnboardingCard import org.fdroid.ui.utils.getAppListInfo @@ -95,7 +97,10 @@ fun AppList( topBar = { if (searchActive) { val onSearchCleared = { appListInfo.actions.onSearch("") } - TopSearchBar(onSearch = appListInfo.actions::onSearch, onSearchCleared) { + TopSearchBar( + onSearch = appListInfo.actions::onSearch, + onSearchCleared = onSearchCleared, + ) { searchActive = false onSearchCleared() } @@ -155,7 +160,7 @@ fun AppList( LazyListState() } Column( - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize().imePadding() ) { val apps = appListInfo.model.apps if (apps == null) BigLoadingIndicator() diff --git a/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt b/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt deleted file mode 100644 index 53c4ab7d6..000000000 --- a/app/src/main/kotlin/org/fdroid/ui/lists/TopSearchBar.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.fdroid.ui.lists - -import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Clear -import androidx.compose.material3.AppBarWithSearch -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.rememberSearchBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.res.stringResource -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.debounce -import org.fdroid.R -import org.fdroid.ui.search.AppSearchInputField -import org.fdroid.ui.search.SEARCH_THRESHOLD - -/** - * This is a top app bar that isn't mean to ever expand with results, but for in-list filtering. - * There may still be potential to factor out common code with [AppSearchInputField]. - */ -@Composable -@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) -fun TopSearchBar( - onSearch: (String) -> Unit, - onSearchCleared: () -> Unit, - onHideSearch: () -> Unit, -) { - val searchFieldState = rememberTextFieldState() - val focusRequester = remember { FocusRequester() } - AppBarWithSearch( - state = rememberSearchBarState(), - inputField = { - SearchBarDefaults.InputField( - state = searchFieldState, - leadingIcon = { - IconButton(onClick = onHideSearch) { - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } - }, - trailingIcon = { - if (searchFieldState.text.isNotEmpty()) { - IconButton(onClick = { - searchFieldState.setTextAndPlaceCursorAtEnd("") - onSearchCleared() - }) { - Icon( - imageVector = Icons.Filled.Clear, - contentDescription = stringResource(R.string.clear_search), - ) - } - } - }, - onSearch = onSearch, - expanded = false, - onExpandedChange = {}, - modifier = Modifier.focusRequester(focusRequester) - ) - }, - ) - LaunchedEffect(Unit) { - focusRequester.requestFocus() - snapshotFlow { searchFieldState.text } - .debounce(500) - .collectLatest { - if (it.length >= SEARCH_THRESHOLD || it.isEmpty()) { - onSearch(searchFieldState.text.toString()) - } - } - } -} diff --git a/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt b/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt index 160ac7111..4ff10659b 100644 --- a/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt +++ b/app/src/main/kotlin/org/fdroid/ui/search/ExpandedSearch.kt @@ -5,30 +5,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold import androidx.compose.material3.SearchBarDefaults -import androidx.compose.material3.SearchBarValue -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.rememberSearchBarState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import kotlinx.coroutines.launch -import org.fdroid.R import org.fdroid.ui.FDroidContent import org.fdroid.ui.categories.CategoryItem import org.fdroid.ui.lists.AppListItem @@ -44,41 +27,9 @@ fun ExpandedSearch( onBack: () -> Unit, onSearchCleared: () -> Unit, ) { - val scope = rememberCoroutineScope() - val searchBarState = rememberSearchBarState() - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current Scaffold( topBar = { - TopAppBar( - navigationIcon = { - IconButton(onClick = { - if (searchBarState.currentValue == SearchBarValue.Expanded) { - keyboardController?.hide() - scope.launch { searchBarState.animateToCollapsed() } - } else { - onBack() - } - }) { - Icon( - imageVector = Icons.AutoMirrored.Default.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } - }, - title = { - AppSearchInputField( - searchBarState = searchBarState, - textFieldState = textFieldState, - onSearch = onSearch, - onSearchCleared = { - textFieldState.setTextAndPlaceCursorAtEnd("") - onSearchCleared() - }, - modifier = Modifier.focusRequester(focusRequester) - ) - } - ) + TopSearchBar(textFieldState, onSearch, onSearchCleared, onBack) } ) { paddingValues -> HorizontalDivider( @@ -93,10 +44,6 @@ fun ExpandedSearch( modifier = Modifier ) } - LaunchedEffect(Unit) { - focusRequester.requestFocus() - keyboardController?.show() - } } @Preview diff --git a/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt b/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt new file mode 100644 index 000000000..1e886d84e --- /dev/null +++ b/app/src/main/kotlin/org/fdroid/ui/search/TopSearchBar.kt @@ -0,0 +1,64 @@ +package org.fdroid.ui.search + +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.rememberSearchBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +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.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import kotlinx.coroutines.FlowPreview +import org.fdroid.R + +/** + * This is a top app bar that isn't mean to ever expand with results, but for in-list filtering. + * There may still be potential to factor out common code with [AppSearchInputField]. + */ +@Composable +@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) +fun TopSearchBar( + searchFieldState: TextFieldState = rememberTextFieldState(), + onSearch: suspend (String) -> Unit, + onSearchCleared: () -> Unit, + onHideSearch: () -> Unit, +) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + TopAppBar( + navigationIcon = { + IconButton(onClick = onHideSearch) { + Icon( + imageVector = Icons.AutoMirrored.Default.ArrowBack, + contentDescription = stringResource(R.string.back), + ) + } + }, + title = { + AppSearchInputField( + searchBarState = rememberSearchBarState(), + textFieldState = searchFieldState, + onSearch = onSearch, + onSearchCleared = { + searchFieldState.setTextAndPlaceCursorAtEnd("") + onSearchCleared() + }, + modifier = Modifier.focusRequester(focusRequester) + ) + } + ) + LaunchedEffect(Unit) { + focusRequester.requestFocus() + keyboardController?.show() + } +}