compose: search: Switch to new AppBarWithSearch API

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2025-08-22 15:46:15 +08:00
parent ef488ff2b4
commit 7ecb142b3e
2 changed files with 58 additions and 66 deletions

View File

@@ -6,7 +6,6 @@
package com.aurora.store.compose.ui.details package com.aurora.store.compose.ui.details
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -17,9 +16,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.VerticalDragHandle
import androidx.compose.material3.adaptive.WindowAdaptiveInfo import androidx.compose.material3.adaptive.WindowAdaptiveInfo
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.material3.adaptive.layout.AdaptStrategy import androidx.compose.material3.adaptive.layout.AdaptStrategy
@@ -34,7 +31,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -137,7 +133,7 @@ fun AppDetailsScreen(
context.startActivity( context.startActivity(
PackageUtil.getLaunchIntent(context, packageName) PackageUtil.getLaunchIntent(context, packageName)
) )
} catch (exception: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
context.toast(context.getString(R.string.unable_to_open)) context.toast(context.getString(R.string.unable_to_open))
} }
}, },
@@ -471,16 +467,6 @@ private fun ScreenContentApp(
} }
} }
} }
},
paneExpansionDragHandle = { state ->
VerticalDragHandle(
modifier = Modifier.paneExpansionDraggable(
state = state,
minTouchTargetSize = LocalMinimumInteractiveComponentSize.current,
interactionSource = remember { MutableInteractionSource() }
),
interactionSource = remember { MutableInteractionSource() }
)
} }
) )
} }

View File

@@ -8,29 +8,32 @@ package com.aurora.store.compose.ui.search
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.DockedSearchBar import androidx.compose.foundation.text.input.clearText
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
import androidx.compose.material3.AppBarWithSearch
import androidx.compose.material3.ExpandedDockedSearchBar
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.AnimatedPane
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.material3.rememberSearchBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -56,6 +59,7 @@ import com.aurora.store.compose.preview.coilPreviewProvider
import com.aurora.store.compose.ui.details.AppDetailsScreen import com.aurora.store.compose.ui.details.AppDetailsScreen
import com.aurora.store.viewmodel.search.SearchViewModel import com.aurora.store.viewmodel.search.SearchViewModel
import kotlinx.coroutines.android.awaitFrame import kotlinx.coroutines.android.awaitFrame
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.random.Random import kotlin.random.Random
@@ -82,18 +86,24 @@ private fun ScreenContent(
onFetchSuggestions: (String) -> Unit = {}, onFetchSuggestions: (String) -> Unit = {},
onSearch: (String) -> Unit = {} onSearch: (String) -> Unit = {}
) { ) {
var currentQuery by rememberSaveable { mutableStateOf("") } val textFieldState = rememberTextFieldState()
var isExpanded by rememberSaveable { mutableStateOf(true) } val searchBarState = rememberSearchBarState()
val scrollBehavior = SearchBarDefaults.enterAlwaysSearchBarScrollBehavior()
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<String>() val scaffoldNavigator = rememberListDetailPaneScaffoldNavigator<String>()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
LaunchedEffect(focusRequester) { LaunchedEffect(key1 = focusRequester) {
awaitFrame() awaitFrame()
focusRequester.requestFocus() focusRequester.requestFocus()
} }
LaunchedEffect(key1 = textFieldState) {
snapshotFlow { textFieldState.text.toString() }
.collectLatest { query -> onFetchSuggestions(query) }
}
fun closeDetailPane() { fun closeDetailPane() {
coroutineScope.launch { coroutineScope.launch {
scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, null) scaffoldNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail, null)
@@ -106,57 +116,54 @@ private fun ScreenContent(
} }
} }
fun onRequestSuggestions(query: String) {
currentQuery = query.trim()
onFetchSuggestions(query.trim())
}
fun onRequestSearch(query: String) { fun onRequestSearch(query: String) {
currentQuery = query.trim() textFieldState.setTextAndPlaceCursorAtEnd(query.trim())
isExpanded = false coroutineScope.launch { searchBarState.animateToCollapsed() }
onSearch(query.trim()) onSearch(textFieldState.text.toString())
} }
@Composable @Composable
fun SearchBar() { fun SearchBar() {
DockedSearchBar( val inputField = @Composable {
expanded = isExpanded, SearchBarDefaults.InputField(
onExpandedChange = { expanded -> isExpanded = expanded }, modifier = Modifier.focusRequester(focusRequester),
inputField = { searchBarState = searchBarState,
SearchBarDefaults.InputField( textFieldState = textFieldState,
modifier = Modifier.focusRequester(focusRequester), onSearch = { query -> onRequestSearch(query) },
query = currentQuery, placeholder = { Text(text = stringResource(R.string.search_hint)) },
onQueryChange = { query -> onRequestSuggestions(query) }, leadingIcon = {
onSearch = { query -> onRequestSearch(query) }, IconButton(onClick = onNavigateUp) {
expanded = isExpanded, Icon(
onExpandedChange = { expanded -> isExpanded = expanded }, painter = painterResource(R.drawable.ic_arrow_back),
leadingIcon = { contentDescription = stringResource(R.string.action_back)
IconButton(onClick = onNavigateUp) { )
}
},
trailingIcon = {
if (textFieldState.text.isNotBlank()) {
IconButton(onClick = { textFieldState.clearText() }) {
Icon( Icon(
painter = painterResource(R.drawable.ic_arrow_back), painter = painterResource(R.drawable.ic_cancel),
contentDescription = stringResource(R.string.action_back) contentDescription = stringResource(R.string.action_clear)
) )
} }
}, }
trailingIcon = { }
if (currentQuery.isNotBlank() && isExpanded) { )
IconButton(onClick = { currentQuery = "" }) { }
Icon(
painter = painterResource(R.drawable.ic_cancel), AppBarWithSearch(
contentDescription = stringResource(R.string.action_clear) scrollBehavior = scrollBehavior,
) state = searchBarState,
} inputField = inputField
} )
},
placeholder = { Text(text = stringResource(R.string.search_hint)) } ExpandedDockedSearchBar(state = searchBarState, inputField = inputField) {
)
}
) {
suggestions.forEach { suggestion -> suggestions.forEach { suggestion ->
SearchSuggestionComposable( SearchSuggestionComposable(
searchSuggestEntry = suggestion, searchSuggestEntry = suggestion,
onClick = { query -> onRequestSearch(query) }, onClick = { query -> onRequestSearch(query) },
onAction = { query -> currentQuery = query.trim() } onAction = { query -> textFieldState.setTextAndPlaceCursorAtEnd(query.trim()) }
) )
} }
} }
@@ -165,9 +172,8 @@ private fun ScreenContent(
@Composable @Composable
fun ListPane() { fun ListPane() {
Scaffold( Scaffold(
topBar = { modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
TopAppBar(title = { SearchBar() }) topBar = { SearchBar() }
}
) { paddingValues -> ) { paddingValues ->
when (results.loadState.refresh) { when (results.loadState.refresh) {
is LoadState.Loading -> ProgressComposable() is LoadState.Loading -> ProgressComposable()
@@ -214,7 +220,7 @@ private fun ScreenContent(
} }
else -> { else -> {
if (currentQuery.isNotBlank() && results.itemCount > 0) { if (textFieldState.text.isNotBlank() && results.itemCount > 0) {
ErrorComposable( ErrorComposable(
icon = R.drawable.ic_round_search, icon = R.drawable.ic_round_search,
message = R.string.select_app_for_details message = R.string.select_app_for_details