Selaa lähdekoodia

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.
Alessandro Jean 2 vuotta sitten
vanhempi
commit
5a37f2398a

+ 3 - 0
app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt

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

+ 10 - 0
app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt

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

+ 42 - 2
app/src/main/java/eu/kanade/presentation/components/AppBar.kt

@@ -1,6 +1,7 @@
 package eu.kanade.presentation.components
 
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.WindowInsets
@@ -20,6 +21,7 @@ import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
 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.res.stringResource
 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.unit.dp
+import androidx.compose.ui.unit.sp
 import eu.kanade.tachiyomi.R
 
 @Composable
@@ -63,7 +67,10 @@ fun AppBar(
 
     scrollBehavior: TopAppBarScrollBehavior? = null,
 ) {
-    val isActionMode by derivedStateOf { actionModeCounter > 0 }
+    val isActionMode by remember(actionModeCounter) {
+        derivedStateOf { actionModeCounter > 0 }
+    }
+
     AppBar(
         modifier = modifier,
         titleContent = {
@@ -216,6 +223,7 @@ fun AppBarActions(
 fun SearchToolbar(
     searchQuery: String,
     onChangeSearchQuery: (String) -> Unit,
+    placeholderText: String? = null,
     keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
     keyboardActions: KeyboardActions = KeyboardActions.Default,
     onClickCloseSearch: () -> Unit,
@@ -223,8 +231,11 @@ fun SearchToolbar(
     incognitoMode: Boolean = false,
     downloadedOnlyMode: Boolean = false,
     scrollBehavior: TopAppBarScrollBehavior? = null,
+    visualTransformation: VisualTransformation = VisualTransformation.None,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
 ) {
     val focusRequester = remember { FocusRequester() }
+
     AppBar(
         titleContent = {
             BasicTextField(
@@ -233,11 +244,40 @@ fun SearchToolbar(
                 modifier = Modifier
                     .fillMaxWidth()
                     .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,
                 keyboardActions = keyboardActions,
                 singleLine = true,
                 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,

+ 2 - 0
app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt

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

+ 18 - 0
app/src/main/java/eu/kanade/presentation/history/components/HistoryToolbar.kt

@@ -1,5 +1,7 @@
 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.outlined.DeleteSweep
 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.TopAppBarScrollBehavior
 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.text.input.ImeAction
 import eu.kanade.presentation.components.AppBar
 import eu.kanade.presentation.components.SearchToolbar
 import eu.kanade.tachiyomi.R
@@ -21,6 +26,9 @@ fun HistoryToolbar(
     incognitoMode: Boolean,
     downloadedOnlyMode: Boolean,
 ) {
+    val keyboardController = LocalSoftwareKeyboardController.current
+    val focusManager = LocalFocusManager.current
+
     if (state.searchQuery == null) {
         HistoryRegularToolbar(
             onClickSearch = { state.searchQuery = "" },
@@ -33,10 +41,20 @@ fun HistoryToolbar(
         SearchToolbar(
             searchQuery = state.searchQuery!!,
             onChangeSearchQuery = { state.searchQuery = it },
+            placeholderText = stringResource(R.string.action_search_hint),
             onClickCloseSearch = { state.searchQuery = null },
             onClickResetSearch = { state.searchQuery = "" },
             incognitoMode = incognitoMode,
             downloadedOnlyMode = downloadedOnlyMode,
+            keyboardOptions = KeyboardOptions.Default.copy(
+                imeAction = ImeAction.Search,
+            ),
+            keyboardActions = KeyboardActions(
+                onSearch = {
+                    focusManager.clearFocus()
+                    keyboardController?.hide()
+                },
+            ),
         )
     }
 }

+ 29 - 9
app/src/main/java/eu/kanade/presentation/library/components/LibraryToolbar.kt

@@ -2,6 +2,8 @@ package eu.kanade.presentation.library.components
 
 import androidx.compose.foundation.isSystemInDarkTheme
 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.outlined.FilterList
 import androidx.compose.material.icons.outlined.FlipToBack
@@ -17,7 +19,10 @@ import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 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.text.style.TextOverflow
 import androidx.compose.ui.unit.sp
 import eu.kanade.presentation.components.AppBar
@@ -48,15 +53,30 @@ fun LibraryToolbar(
         onClickSelectAll = onClickSelectAll,
         onClickInvertSelection = onClickInvertSelection,
     )
-    state.searchQuery != null -> SearchToolbar(
-        searchQuery = state.searchQuery!!,
-        onChangeSearchQuery = { state.searchQuery = it },
-        onClickCloseSearch = { state.searchQuery = null },
-        onClickResetSearch = { state.searchQuery = "" },
-        scrollBehavior = scrollBehavior,
-        incognitoMode = incognitoMode,
-        downloadedOnlyMode = downloadedOnlyMode,
-    )
+    state.searchQuery != null -> {
+        val keyboardController = LocalSoftwareKeyboardController.current
+        val focusManager = LocalFocusManager.current
+
+        SearchToolbar(
+            searchQuery = state.searchQuery!!,
+            onChangeSearchQuery = { state.searchQuery = it },
+            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(
         title = title,
         hasFilters = state.hasActiveFilters,

+ 20 - 3
app/src/main/java/eu/kanade/presentation/more/settings/SettingsSearchScreen.kt

@@ -5,6 +5,8 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 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.Text
 import androidx.compose.runtime.Composable
@@ -14,11 +16,16 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 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 eu.kanade.presentation.components.Scaffold
 import eu.kanade.presentation.components.ScrollbarLazyColumn
 import eu.kanade.presentation.components.SearchToolbar
 import eu.kanade.presentation.util.horizontalPadding
+import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.ui.setting.SettingsController
 import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchHelper
 import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchPresenter
@@ -33,6 +40,9 @@ fun SettingsSearchScreen(
     val results by presenter.state.collectAsState()
     var query by remember { mutableStateOf("") }
 
+    val keyboardController = LocalSoftwareKeyboardController.current
+    val focusManager = LocalFocusManager.current
+
     Scaffold(
         topBar = { scrollBehavior ->
             SearchToolbar(
@@ -41,13 +51,20 @@ fun SettingsSearchScreen(
                     query = it
                     presenter.searchSettings(it)
                 },
+                placeholderText = stringResource(R.string.action_search_settings),
                 onClickCloseSearch = navigateUp,
                 onClickResetSearch = { query = "" },
                 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 ->
         ScrollbarLazyColumn(

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseController.kt

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

+ 1 - 0
i18n/src/main/res/values/strings.xml

@@ -50,6 +50,7 @@
     <string name="action_sort_chapter_fetch_date">Chapter fetch date</string>
     <string name="action_sort_date_added">Date added</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_global_search">Global search</string>
     <string name="action_select_all">Select all</string>