Adjust SearchToolbar soft keyboard behavior (#9282)

* Show soft keyboard when the text field is composed (a redo)
* Clear focus on text field when soft keyboard is hidden
* Request focus on text field and show soft keyboard
when clear button is clicked
This commit is contained in:
Ivan Iskandar 2023-03-31 20:24:44 +07:00 committed by GitHub
parent 1dd62af188
commit 7a1b599462
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 75 additions and 17 deletions

View file

@ -45,6 +45,7 @@ fun BrowseSourceToolbar(
var selectingDisplayMode by remember { mutableStateOf(false) } var selectingDisplayMode by remember { mutableStateOf(false) }
SearchToolbar( SearchToolbar(
initialShowKeyboard = searchQuery.isNullOrEmpty(),
navigateUp = navigateUp, navigateUp = navigateUp,
titleContent = { AppBarTitle(title) }, titleContent = { AppBarTitle(title) },
searchQuery = searchQuery, searchQuery = searchQuery,

View file

@ -23,7 +23,6 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
@ -45,8 +44,10 @@ 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 androidx.compose.ui.unit.sp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
import tachiyomi.presentation.core.util.runOnEnterKeyPressed import tachiyomi.presentation.core.util.runOnEnterKeyPressed
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import tachiyomi.presentation.core.util.showSoftKeyboard
const val SEARCH_DEBOUNCE_MILLIS = 250L const val SEARCH_DEBOUNCE_MILLIS = 250L
@ -231,9 +232,9 @@ fun SearchToolbar(
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
visualTransformation: VisualTransformation = VisualTransformation.None, visualTransformation: VisualTransformation = VisualTransformation.None,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
initialShowKeyboard: Boolean = true,
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
var searchClickCount by remember { mutableStateOf(0) }
AppBar( AppBar(
titleContent = { titleContent = {
@ -255,7 +256,9 @@ fun SearchToolbar(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.focusRequester(focusRequester) .focusRequester(focusRequester)
.runOnEnterKeyPressed(action = searchAndClearFocus), .runOnEnterKeyPressed(action = searchAndClearFocus)
.showSoftKeyboard(initialShowKeyboard)
.clearFocusOnSoftKeyboardHide(),
textStyle = MaterialTheme.typography.titleMedium.copy( textStyle = MaterialTheme.typography.titleMedium.copy(
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
@ -294,10 +297,7 @@ fun SearchToolbar(
navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch, navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch,
actions = { actions = {
key("search") { key("search") {
val onClick = { val onClick = { onChangeSearchQuery("") }
searchClickCount++
onChangeSearchQuery("")
}
if (!searchEnabled) { if (!searchEnabled) {
// Don't show search action // Don't show search action
@ -306,7 +306,12 @@ fun SearchToolbar(
Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search)) Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search))
} }
} else if (searchQuery.isNotEmpty()) { } else if (searchQuery.isNotEmpty()) {
IconButton(onClick) { IconButton(
onClick = {
onClick()
focusRequester.requestFocus()
},
) {
Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset)) Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset))
} }
} }
@ -317,15 +322,6 @@ fun SearchToolbar(
isActionMode = false, isActionMode = false,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
LaunchedEffect(searchClickCount) {
if (searchQuery == null) return@LaunchedEffect
if (searchClickCount == 0 && searchQuery.isNotEmpty()) return@LaunchedEffect
try {
focusRequester.requestFocus()
} catch (_: Throwable) {
// TextField is gone
}
}
} }
sealed interface AppBar { sealed interface AppBar {

View file

@ -4,14 +4,25 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.isImeVisible
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.platform.LocalFocusManager
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed { fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed {
@ -52,3 +63,53 @@ fun Modifier.runOnEnterKeyPressed(action: () -> Unit): Modifier = this.onPreview
else -> false else -> false
} }
} }
/**
* For TextField on AppBar, this modifier will request focus
* to the element the first time it's composed.
*/
fun Modifier.showSoftKeyboard(show: Boolean): Modifier = if (show) {
composed {
val focusRequester = remember { FocusRequester() }
var openKeyboard by rememberSaveable { mutableStateOf(show) }
LaunchedEffect(focusRequester) {
if (openKeyboard) {
focusRequester.requestFocus()
openKeyboard = false
}
}
Modifier.focusRequester(focusRequester)
}
} else {
this
}
/**
* For TextField, this modifier will clear focus when soft
* keyboard is hidden.
*/
fun Modifier.clearFocusOnSoftKeyboardHide(): Modifier = composed {
var isFocused by remember { mutableStateOf(false) }
var keyboardShowedSinceFocused by remember { mutableStateOf(false) }
if (isFocused) {
val imeVisible = WindowInsets.isImeVisible
val focusManager = LocalFocusManager.current
LaunchedEffect(imeVisible) {
if (imeVisible) {
keyboardShowedSinceFocused = true
} else if (keyboardShowedSinceFocused) {
focusManager.clearFocus()
}
}
}
Modifier.onFocusChanged {
if (isFocused != it.isFocused) {
if (isFocused) {
keyboardShowedSinceFocused = false
}
isFocused = it.isFocused
}
}
}