Improve search toolbar UX a little bit (#8102)

* Improve search toolbar UX a little.

* Fix wrong stringResource import.

* Revert `FocusRequester` change in favour of #8093.
This commit is contained in:
Alessandro Jean 2022-09-27 19:05:10 -03:00 committed by GitHub
parent 98a4f6cccb
commit 5a37f2398a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 126 additions and 14 deletions

View File

@ -6,10 +6,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.presentation.browse.components.BrowseSourceSearchToolbar import eu.kanade.presentation.browse.components.BrowseSourceSearchToolbar
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.ui.more.MoreController import eu.kanade.tachiyomi.ui.more.MoreController
@ -39,6 +41,7 @@ fun SourceSearchScreen(
BrowseSourceSearchToolbar( BrowseSourceSearchToolbar(
searchQuery = presenter.searchQuery ?: "", searchQuery = presenter.searchQuery ?: "",
onSearchQueryChanged = { presenter.searchQuery = it }, onSearchQueryChanged = { presenter.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint),
navigateUp = navigateUp, navigateUp = navigateUp,
onResetClick = { presenter.searchQuery = "" }, onResetClick = { presenter.searchQuery = "" },
onSearchClick = { presenter.search() }, onSearchClick = { presenter.search() },

View File

@ -17,6 +17,8 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import eu.kanade.domain.library.model.LibraryDisplayMode import eu.kanade.domain.library.model.LibraryDisplayMode
@ -57,6 +59,7 @@ fun BrowseSourceToolbar(
BrowseSourceSearchToolbar( BrowseSourceSearchToolbar(
searchQuery = state.searchQuery!!, searchQuery = state.searchQuery!!,
onSearchQueryChanged = { state.searchQuery = it }, onSearchQueryChanged = { state.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint),
navigateUp = { state.searchQuery = null }, navigateUp = { state.searchQuery = null },
onResetClick = { state.searchQuery = "" }, onResetClick = { state.searchQuery = "" },
onSearchClick = onSearch, onSearchClick = onSearch,
@ -159,18 +162,25 @@ fun BrowseSourceRegularToolbar(
fun BrowseSourceSearchToolbar( fun BrowseSourceSearchToolbar(
searchQuery: String, searchQuery: String,
onSearchQueryChanged: (String) -> Unit, onSearchQueryChanged: (String) -> Unit,
placeholderText: String?,
navigateUp: () -> Unit, navigateUp: () -> Unit,
onResetClick: () -> Unit, onResetClick: () -> Unit,
onSearchClick: () -> Unit, onSearchClick: () -> Unit,
scrollBehavior: TopAppBarScrollBehavior?, scrollBehavior: TopAppBarScrollBehavior?,
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
SearchToolbar( SearchToolbar(
searchQuery = searchQuery, searchQuery = searchQuery,
onChangeSearchQuery = onSearchQueryChanged, onChangeSearchQuery = onSearchQueryChanged,
placeholderText = placeholderText,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions( keyboardActions = KeyboardActions(
onSearch = { onSearch = {
onSearchClick() onSearchClick()
focusManager.clearFocus()
keyboardController?.hide()
}, },
), ),
onClickCloseSearch = navigateUp, onClickCloseSearch = navigateUp,

View File

@ -1,6 +1,7 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@ -20,6 +21,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
@ -38,8 +40,10 @@ import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@Composable @Composable
@ -63,7 +67,10 @@ fun AppBar(
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
) { ) {
val isActionMode by derivedStateOf { actionModeCounter > 0 } val isActionMode by remember(actionModeCounter) {
derivedStateOf { actionModeCounter > 0 }
}
AppBar( AppBar(
modifier = modifier, modifier = modifier,
titleContent = { titleContent = {
@ -216,6 +223,7 @@ fun AppBarActions(
fun SearchToolbar( fun SearchToolbar(
searchQuery: String, searchQuery: String,
onChangeSearchQuery: (String) -> Unit, onChangeSearchQuery: (String) -> Unit,
placeholderText: String? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default, keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
onClickCloseSearch: () -> Unit, onClickCloseSearch: () -> Unit,
@ -223,8 +231,11 @@ fun SearchToolbar(
incognitoMode: Boolean = false, incognitoMode: Boolean = false,
downloadedOnlyMode: Boolean = false, downloadedOnlyMode: Boolean = false,
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
AppBar( AppBar(
titleContent = { titleContent = {
BasicTextField( BasicTextField(
@ -233,11 +244,40 @@ fun SearchToolbar(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester), .focusRequester(focusRequester),
textStyle = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onBackground), textStyle = MaterialTheme.typography.titleMedium.copy(
color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.Normal,
fontSize = 18.sp,
),
keyboardOptions = keyboardOptions, keyboardOptions = keyboardOptions,
keyboardActions = keyboardActions, keyboardActions = keyboardActions,
singleLine = true, singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
visualTransformation = visualTransformation,
interactionSource = interactionSource,
decorationBox = { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox(
value = searchQuery,
innerTextField = innerTextField,
enabled = true,
singleLine = true,
visualTransformation = visualTransformation,
interactionSource = interactionSource,
placeholder = {
if (!placeholderText.isNullOrEmpty()) {
Text(
text = placeholderText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium.copy(
fontSize = 18.sp,
fontWeight = FontWeight.Normal,
),
)
}
},
)
},
) )
}, },
navigationIcon = Icons.Outlined.ArrowBack, navigationIcon = Icons.Outlined.ArrowBack,

View File

@ -22,6 +22,7 @@ fun TabbedScreen(
tabs: List<TabContent>, tabs: List<TabContent>,
startIndex: Int? = null, startIndex: Int? = null,
searchQuery: String? = null, searchQuery: String? = null,
@StringRes placeholderRes: Int? = null,
onChangeSearchQuery: (String?) -> Unit = {}, onChangeSearchQuery: (String?) -> Unit = {},
incognitoMode: Boolean, incognitoMode: Boolean,
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,
@ -47,6 +48,7 @@ fun TabbedScreen(
} else { } else {
SearchToolbar( SearchToolbar(
searchQuery = searchQuery, searchQuery = searchQuery,
placeholderText = placeholderRes?.let { stringResource(it) },
onChangeSearchQuery = { onChangeSearchQuery = {
onChangeSearchQuery(it) onChangeSearchQuery(it)
}, },

View File

@ -1,5 +1,7 @@
package eu.kanade.presentation.history.components package eu.kanade.presentation.history.components
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.DeleteSweep import androidx.compose.material.icons.outlined.DeleteSweep
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
@ -7,7 +9,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -21,6 +26,9 @@ fun HistoryToolbar(
incognitoMode: Boolean, incognitoMode: Boolean,
downloadedOnlyMode: Boolean, downloadedOnlyMode: Boolean,
) { ) {
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
if (state.searchQuery == null) { if (state.searchQuery == null) {
HistoryRegularToolbar( HistoryRegularToolbar(
onClickSearch = { state.searchQuery = "" }, onClickSearch = { state.searchQuery = "" },
@ -33,10 +41,20 @@ fun HistoryToolbar(
SearchToolbar( SearchToolbar(
searchQuery = state.searchQuery!!, searchQuery = state.searchQuery!!,
onChangeSearchQuery = { state.searchQuery = it }, onChangeSearchQuery = { state.searchQuery = it },
placeholderText = stringResource(R.string.action_search_hint),
onClickCloseSearch = { state.searchQuery = null }, onClickCloseSearch = { state.searchQuery = null },
onClickResetSearch = { state.searchQuery = "" }, onClickResetSearch = { state.searchQuery = "" },
incognitoMode = incognitoMode, incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode, downloadedOnlyMode = downloadedOnlyMode,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
keyboardController?.hide()
},
),
) )
} }
} }

View File

@ -2,6 +2,8 @@ package eu.kanade.presentation.library.components
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
@ -17,7 +19,10 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
@ -48,15 +53,30 @@ fun LibraryToolbar(
onClickSelectAll = onClickSelectAll, onClickSelectAll = onClickSelectAll,
onClickInvertSelection = onClickInvertSelection, onClickInvertSelection = onClickInvertSelection,
) )
state.searchQuery != null -> SearchToolbar( state.searchQuery != null -> {
searchQuery = state.searchQuery!!, val keyboardController = LocalSoftwareKeyboardController.current
onChangeSearchQuery = { state.searchQuery = it }, val focusManager = LocalFocusManager.current
onClickCloseSearch = { state.searchQuery = null },
onClickResetSearch = { state.searchQuery = "" }, SearchToolbar(
scrollBehavior = scrollBehavior, searchQuery = state.searchQuery!!,
incognitoMode = incognitoMode, onChangeSearchQuery = { state.searchQuery = it },
downloadedOnlyMode = downloadedOnlyMode, onClickCloseSearch = { state.searchQuery = null },
) onClickResetSearch = { state.searchQuery = "" },
scrollBehavior = scrollBehavior,
incognitoMode = incognitoMode,
downloadedOnlyMode = downloadedOnlyMode,
placeholderText = stringResource(R.string.action_search_hint),
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
keyboardController?.hide()
},
),
)
}
else -> LibraryRegularToolbar( else -> LibraryRegularToolbar(
title = title, title = title,
hasFilters = state.hasActiveFilters, hasFilters = state.hasActiveFilters,

View File

@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -14,11 +16,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.components.ScrollbarLazyColumn import eu.kanade.presentation.components.ScrollbarLazyColumn
import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.util.horizontalPadding import eu.kanade.presentation.util.horizontalPadding
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.setting.SettingsController import eu.kanade.tachiyomi.ui.setting.SettingsController
import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchHelper import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchHelper
import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchPresenter import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchPresenter
@ -33,6 +40,9 @@ fun SettingsSearchScreen(
val results by presenter.state.collectAsState() val results by presenter.state.collectAsState()
var query by remember { mutableStateOf("") } var query by remember { mutableStateOf("") }
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
SearchToolbar( SearchToolbar(
@ -41,13 +51,20 @@ fun SettingsSearchScreen(
query = it query = it
presenter.searchSettings(it) presenter.searchSettings(it)
}, },
placeholderText = stringResource(R.string.action_search_settings),
onClickCloseSearch = navigateUp, onClickCloseSearch = navigateUp,
onClickResetSearch = { query = "" }, onClickResetSearch = { query = "" },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
keyboardOptions = KeyboardOptions.Default.copy(
imeAction = ImeAction.Search,
),
keyboardActions = KeyboardActions(
onSearch = {
focusManager.clearFocus()
keyboardController?.hide()
},
),
) )
// TODO: search placeholder
// Text(stringResource(R.string.action_search_settings))
}, },
) { contentPadding -> ) { contentPadding ->
ScrollbarLazyColumn( ScrollbarLazyColumn(

View File

@ -45,6 +45,7 @@ class BrowseController : FullComposeController<BrowsePresenter>, RootController
startIndex = 1.takeIf { toExtensions }, startIndex = 1.takeIf { toExtensions },
searchQuery = query, searchQuery = query,
onChangeSearchQuery = { presenter.extensionsPresenter.search(it) }, onChangeSearchQuery = { presenter.extensionsPresenter.search(it) },
placeholderRes = R.string.action_search_hint,
incognitoMode = presenter.isIncognitoMode, incognitoMode = presenter.isIncognitoMode,
downloadedOnlyMode = presenter.isDownloadOnly, downloadedOnlyMode = presenter.isDownloadOnly,
) )

View File

@ -50,6 +50,7 @@
<string name="action_sort_chapter_fetch_date">Chapter fetch date</string> <string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
<string name="action_sort_date_added">Date added</string> <string name="action_sort_date_added">Date added</string>
<string name="action_search">Search</string> <string name="action_search">Search</string>
<string name="action_search_hint">Search…</string>
<string name="action_search_settings">Search settings</string> <string name="action_search_settings">Search settings</string>
<string name="action_global_search">Global search</string> <string name="action_global_search">Global search</string>
<string name="action_select_all">Select all</string> <string name="action_select_all">Select all</string>