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) }
SearchToolbar(
initialShowKeyboard = searchQuery.isNullOrEmpty(),
navigateUp = navigateUp,
titleContent = { AppBarTitle(title) },
searchQuery = searchQuery,

View file

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

View file

@ -4,14 +4,25 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
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.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.composed
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.onPreviewKeyEvent
import androidx.compose.ui.platform.LocalFocusManager
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed {
@ -52,3 +63,53 @@ fun Modifier.runOnEnterKeyPressed(action: () -> Unit): Modifier = this.onPreview
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
}
}
}