komikku/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt

218 lines
7.9 KiB
Kotlin
Raw Normal View History

2024-10-18 18:16:32 +02:00
package eu.kanade.presentation.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Search
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.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusManager
2024-10-18 18:16:32 +02:00
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
2024-10-18 18:16:32 +02:00
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
2024-10-18 18:16:32 +02:00
import androidx.compose.ui.unit.dp
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
2024-10-18 18:16:32 +02:00
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
import tachiyomi.presentation.core.util.isItemScrollingUp
2024-10-18 18:16:32 +02:00
import tachiyomi.presentation.core.util.runOnEnterKeyPressed
import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable
fun AnimatedFloatingSearchBox(
listState: LazyListState,
searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
modifier: Modifier = Modifier,
placeholderText: String? = null,
focusManager: FocusManager = LocalFocusManager.current,
focusRequester: FocusRequester = remember { FocusRequester() },
keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
2024-10-18 18:16:32 +02:00
) {
AnimatedVisibility(
visible = listState.isItemScrollingUp(),
2024-10-18 18:16:32 +02:00
enter = expandVertically(),
exit = shrinkVertically(),
modifier = modifier,
) {
SourcesSearchBox(
searchQuery = searchQuery,
onChangeSearchQuery = onChangeSearchQuery,
placeholderText = placeholderText,
focusManager = focusManager,
focusRequester = focusRequester,
keyboardController = keyboardController,
2024-10-18 18:16:32 +02:00
)
}
}
@Composable
fun SourcesSearchBox(
searchQuery: String?,
onChangeSearchQuery: (String?) -> Unit,
modifier: Modifier = Modifier,
placeholderText: String? = null,
focusManager: FocusManager = LocalFocusManager.current,
focusRequester: FocusRequester = remember { FocusRequester() },
keyboardController: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
2024-10-18 18:16:32 +02:00
) {
val searchAndClearFocus: () -> Unit = f@{
if (searchQuery.isNullOrBlank()) return@f
focusManager.clearFocus()
keyboardController?.hide()
}
val onClickClearSearch: () -> Unit = {
onChangeSearchQuery("")
2024-10-18 18:16:32 +02:00
focusRequester.requestFocus()
keyboardController?.show()
}
val onClickCloseSearch: () -> Unit = {
onChangeSearchQuery("")
2024-10-18 18:16:32 +02:00
focusManager.clearFocus()
keyboardController?.hide()
}
var isFocused by remember { mutableStateOf(false) }
BasicTextField(
value = searchQuery ?: "",
onValueChange = onChangeSearchQuery,
modifier = modifier
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusChanged { isFocused = it.isFocused }
.runOnEnterKeyPressed(action = searchAndClearFocus)
.clearFocusOnSoftKeyboardHide(),
enabled = true,
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onBackground,
),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = KeyboardActions(onSearch = { searchAndClearFocus() }),
singleLine = true,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground),
decorationBox = { innerTextField ->
TextFieldDefaults.DecorationBox(
value = searchQuery ?: "",
innerTextField = innerTextField,
enabled = true,
singleLine = true,
visualTransformation = VisualTransformation.None,
interactionSource = remember { MutableInteractionSource() },
placeholder = {
Text(
modifier = Modifier.secondaryItemAlpha(),
text = (placeholderText ?: stringResource(MR.strings.action_search_hint)),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium,
)
},
leadingIcon = {
SearchBoxLeadingIcon(
isSearching = isFocused || !searchQuery.isNullOrBlank(),
onClickCloseSearch = onClickCloseSearch,
2024-10-18 18:16:32 +02:00
)
},
trailingIcon = {
SearchBoxTrailingIcon(
isEmpty = searchQuery.isNullOrEmpty(),
onClickClearSearch = onClickClearSearch,
2024-10-18 18:16:32 +02:00
)
},
shape = RoundedCornerShape(24.dp),
colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
errorIndicatorColor = Color.Transparent,
cursorColor = MaterialTheme.colorScheme.onBackground,
),
contentPadding = PaddingValues(MaterialTheme.padding.small),
2024-10-18 18:16:32 +02:00
)
},
)
}
@Composable
fun SearchBoxLeadingIcon(
isSearching: Boolean,
modifier: Modifier = Modifier,
2024-10-18 18:16:32 +02:00
onClickCloseSearch: () -> Unit = {},
) {
if (isSearching) {
IconButton(
modifier = modifier,
2024-10-18 18:16:32 +02:00
onClick = onClickCloseSearch,
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Close",
)
}
} else {
Icon(
modifier = modifier,
2024-10-18 18:16:32 +02:00
imageVector = Icons.Filled.Search,
contentDescription = "Search",
)
}
}
@Composable
fun SearchBoxTrailingIcon(
isEmpty: Boolean,
modifier: Modifier = Modifier,
2024-10-18 18:16:32 +02:00
onClickClearSearch: () -> Unit = {},
) {
if (!isEmpty) {
IconButton(
modifier = modifier,
2024-10-18 18:16:32 +02:00
onClick = onClickClearSearch,
) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = "Clear",
)
}
}
}
@Preview
@Composable
fun PreviewClearFocusOnKeyboardDismissExample() {
SourcesSearchBox(
searchQuery = "Hello World",
onChangeSearchQuery = {},
)
}