diff --git a/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt index b017a6ba8..83c47b1dd 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/FeedScreen.kt @@ -34,6 +34,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem import eu.kanade.presentation.browse.components.GlobalSearchResultItem import eu.kanade.presentation.browse.components.SourceIcon +import eu.kanade.presentation.components.SourcesSearchBox import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.ui.browse.feed.FeedScreenState @@ -331,7 +332,7 @@ fun RadioSelectorSearchable( onSelectOption: (Int) -> Unit = {}, ) { Column(Modifier.verticalScroll(rememberScrollState())) { - SourcesSearch( + SourcesSearchBox( searchQuery = queryString, onChangeSearchQuery = onChangeSearchQuery ?: {}, modifier = Modifier diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index bfdb9c941..f81183f13 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.SourceIcon +import eu.kanade.presentation.components.AnimatedFloatingSearchBox import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel import eu.kanade.tachiyomi.util.system.copyToClipboard diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index a8040dd1e..498487176 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -1,30 +1,17 @@ package eu.kanade.presentation.browse import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState -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.foundation.verticalScroll 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.PushPin -import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.outlined.PushPin import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon @@ -33,28 +20,15 @@ import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf -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.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.LocalContext -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalSoftwareKeyboardController -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.unit.dp import eu.kanade.domain.source.model.installedExtension import eu.kanade.presentation.browse.components.BaseSourceItem +import eu.kanade.presentation.components.AnimatedFloatingSearchBox import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing @@ -75,10 +49,6 @@ import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.theme.header -import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide -import tachiyomi.presentation.core.util.isScrollingUp -import tachiyomi.presentation.core.util.runOnEnterKeyPressed -import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.source.local.LocalSource import tachiyomi.source.local.isLocal @@ -175,162 +145,6 @@ fun SourcesScreen( } } -// KMK --> -@Composable -fun AnimatedFloatingSearchBox( - listState: LazyListState, - searchQuery: String?, - onChangeSearchQuery: (String?) -> Unit, - modifier: Modifier = Modifier, - placeholderText: String? = null, -) { - AnimatedVisibility( - visible = listState.isScrollingUp(), - enter = expandVertically(), - exit = shrinkVertically(), - modifier = modifier, - ) { - SourcesSearch( - searchQuery = searchQuery, - onChangeSearchQuery = onChangeSearchQuery, - placeholderText = placeholderText, - ) - } -} - -@Composable -fun SourcesSearch( - searchQuery: String?, - onChangeSearchQuery: (String?) -> Unit, - modifier: Modifier = Modifier, - placeholderText: String? = null, -) { - val focusRequester = remember { FocusRequester() } - val keyboardController = LocalSoftwareKeyboardController.current - val focusManager = LocalFocusManager.current - - val searchAndClearFocus: () -> Unit = f@{ - if (searchQuery.isNullOrBlank()) return@f - focusManager.clearFocus() - keyboardController?.hide() - } - val onClickClearSearch: () -> Unit = { - onChangeSearchQuery(null) - focusRequester.requestFocus() - keyboardController?.show() - } - val onClickCloseSearch: () -> Unit = { - onClickClearSearch() - focusManager.clearFocus() - keyboardController?.hide() - } - - var isFocused by remember { mutableStateOf(false) } - - BasicTextField( - value = searchQuery ?: "", - onValueChange = onChangeSearchQuery, - modifier = modifier - .fillMaxWidth() - .height(40.dp) - .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( - isFocused || !searchQuery.isNullOrBlank(), - modifier = Modifier, - onClickCloseSearch, - ) - }, - trailingIcon = { - SearchBoxTrailingIcon( - searchQuery.isNullOrEmpty(), - modifier = Modifier, - onClickClearSearch, - ) - }, - shape = RoundedCornerShape(24.dp), - colors = TextFieldDefaults.colors( - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - cursorColor = MaterialTheme.colorScheme.onBackground, - ), - contentPadding = PaddingValues(12.dp), - ) - }, - ) -} - -@Composable -fun SearchBoxLeadingIcon( - isSearching: Boolean, - @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, - onClickCloseSearch: () -> Unit = {}, -) { - if (isSearching) { - IconButton( - onClick = onClickCloseSearch, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = "Close", - ) - } - } else { - Icon( - imageVector = Icons.Filled.Search, - contentDescription = "Search", - ) - } -} - -@Composable -fun SearchBoxTrailingIcon( - isEmpty: Boolean, - @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, - onClickClearSearch: () -> Unit = {}, -) { - if (!isEmpty) { - IconButton( - onClick = onClickClearSearch, - ) { - Icon( - imageVector = Icons.Filled.Close, - contentDescription = "Clear", - ) - } - } -} -// KMK <-- - @Composable private fun SourceHeader( language: String, diff --git a/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt b/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt new file mode 100644 index 000000000..9c9dfc5bd --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt @@ -0,0 +1,198 @@ +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.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.text.input.ImeAction +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide +import tachiyomi.presentation.core.util.isScrollingUp +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, +) { + AnimatedVisibility( + visible = listState.isScrollingUp(), + enter = expandVertically(), + exit = shrinkVertically(), + modifier = modifier, + ) { + SourcesSearchBox( + searchQuery = searchQuery, + onChangeSearchQuery = onChangeSearchQuery, + placeholderText = placeholderText, + ) + } +} + +@Composable +fun SourcesSearchBox( + searchQuery: String?, + onChangeSearchQuery: (String?) -> Unit, + modifier: Modifier = Modifier, + placeholderText: String? = null, +) { + val focusRequester = remember { FocusRequester() } + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + + val searchAndClearFocus: () -> Unit = f@{ + if (searchQuery.isNullOrBlank()) return@f + focusManager.clearFocus() + keyboardController?.hide() + } + val onClickClearSearch: () -> Unit = { + onChangeSearchQuery(null) + focusRequester.requestFocus() + keyboardController?.show() + } + val onClickCloseSearch: () -> Unit = { + onClickClearSearch() + 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( + isFocused || !searchQuery.isNullOrBlank(), + modifier = Modifier, + onClickCloseSearch, + ) + }, + trailingIcon = { + SearchBoxTrailingIcon( + searchQuery.isNullOrEmpty(), + modifier = Modifier, + onClickClearSearch, + ) + }, + shape = RoundedCornerShape(24.dp), + colors = TextFieldDefaults.colors( + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.onBackground, + ), + contentPadding = PaddingValues(12.dp), + ) + }, + ) +} + +@Composable +fun SearchBoxLeadingIcon( + isSearching: Boolean, + @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, + onClickCloseSearch: () -> Unit = {}, +) { + if (isSearching) { + IconButton( + onClick = onClickCloseSearch, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Close", + ) + } + } else { + Icon( + imageVector = Icons.Filled.Search, + contentDescription = "Search", + ) + } +} + +@Composable +fun SearchBoxTrailingIcon( + isEmpty: Boolean, + @Suppress("UNUSED_PARAMETER") modifier: Modifier = Modifier, + onClickClearSearch: () -> Unit = {}, +) { + if (!isEmpty) { + IconButton( + onClick = onClickClearSearch, + ) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = "Clear", + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt index 9f99914b7..2ead0a30a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt @@ -40,9 +40,9 @@ import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.browse.SourcesSearch import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions +import eu.kanade.presentation.components.SourcesSearchBox import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.databinding.PreMigrationListBinding import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen @@ -207,7 +207,7 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() { ) } // KMK --> - SourcesSearch( + SourcesSearchBox( modifier = Modifier .onSizeChanged { searchBoxHeight = it.height } .background(MaterialTheme.colorScheme.background)