From 7738b7bc29bda813c3524e9dd305f0a9bb7d3f61 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:51:30 +0800 Subject: [PATCH] Implement title keyword search in history (#840) * Implement title keyword search in history * Don't use Dispatchers.IO --- .../main/kotlin/com/hippo/ehviewer/EhDB.kt | 2 + .../com/hippo/ehviewer/dao/HistoryDao.kt | 4 + .../hippo/ehviewer/dao/LocalFavoritesDao.kt | 2 +- .../com/hippo/ehviewer/ui/main/GalleryList.kt | 2 +- .../ehviewer/ui/screen/DownloadsScreen.kt | 3 - .../ehviewer/ui/screen/FavoritesScreen.kt | 4 - .../hippo/ehviewer/ui/screen/HistoryScreen.kt | 98 ++++++++++++------- .../ehviewer/ui/screen/SearchBarScreen.kt | 3 +- 8 files changed, 70 insertions(+), 48 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt b/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt index 61b1b278d..a3f5e06b4 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt @@ -194,6 +194,8 @@ object EhDB { val historyLazyList get() = db.historyDao().joinListLazy() + fun searchHistory(keyword: String) = db.historyDao().joinListLazy("%$keyword%") + val localFavLazyList get() = db.localFavoritesDao().joinListLazy() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/dao/HistoryDao.kt b/app/src/main/kotlin/com/hippo/ehviewer/dao/HistoryDao.kt index bcee98b5c..8a0999fe8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/dao/HistoryDao.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/dao/HistoryDao.kt @@ -18,6 +18,10 @@ interface HistoryDao { @Query("SELECT * FROM HISTORY JOIN GALLERIES USING(GID) ORDER BY TIME DESC") fun joinListLazy(): PagingSource + @RewriteQueriesToDropUnusedColumns + @Query("SELECT * FROM HISTORY JOIN GALLERIES USING(GID) WHERE TITLE LIKE :title OR TITLE_JPN LIKE :title ORDER BY TIME DESC") + fun joinListLazy(title: String): PagingSource + @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertOrIgnore(historyInfoList: List) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/dao/LocalFavoritesDao.kt b/app/src/main/kotlin/com/hippo/ehviewer/dao/LocalFavoritesDao.kt index 38c7c3ddd..10e114003 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/dao/LocalFavoritesDao.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/dao/LocalFavoritesDao.kt @@ -23,7 +23,7 @@ interface LocalFavoritesDao { fun joinListLazy(): PagingSource @RewriteQueriesToDropUnusedColumns - @Query("SELECT * FROM LOCAL_FAVORITES JOIN GALLERIES USING(GID) WHERE TITLE LIKE :title ORDER BY TIME DESC") + @Query("SELECT * FROM LOCAL_FAVORITES JOIN GALLERIES USING(GID) WHERE TITLE LIKE :title OR TITLE_JPN LIKE :title ORDER BY TIME DESC") fun joinListLazy(title: String): PagingSource @RewriteQueriesToDropUnusedColumns diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryList.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryList.kt index 6c1baf127..780cfb805 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryList.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryList.kt @@ -57,7 +57,7 @@ import com.hippo.ehviewer.util.displayString import kotlinx.coroutines.flow.drop @Stable -private operator fun PaddingValues.plus(r: PaddingValues) = object : PaddingValues { +operator fun PaddingValues.plus(r: PaddingValues) = object : PaddingValues { val l = this@plus override fun calculateBottomPadding() = l.calculateBottomPadding() + r.calculateBottomPadding() override fun calculateLeftPadding(layoutDirection: LayoutDirection) = l.calculateLeftPadding(layoutDirection) + r.calculateLeftPadding(layoutDirection) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt index e46150ae0..c988e5290 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.items -import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.DriveFileMove import androidx.compose.material.icons.automirrored.filled.Sort @@ -131,7 +130,6 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) { var filterState by rememberSaveable { mutableStateOf(DownloadsFilterState(Settings.recentDownloadLabel.value)) } var invalidateKey by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember(filterState.label) { mutableIntStateOf(0) } - val searchFieldState = rememberTextFieldState() var fabExpanded by remember { mutableStateOf(false) } var fabHidden by remember { mutableStateOf(false) } @@ -350,7 +348,6 @@ fun DownloadsScreen(navigator: DestinationsNavigator) = composing(navigator) { SearchBarScreen( title = title, - searchFieldState = searchFieldState, searchFieldHint = hint, onApplySearch = { filterState = filterState.copy(keyword = it) }, onSearchExpanded = { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt index 6f5033477..35751b289 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.DriveFileMove @@ -221,8 +220,6 @@ fun FavouritesScreen(navigator: DestinationsNavigator) { } } - val searchFieldState = rememberTextFieldState() - val refreshState = rememberPullToRefreshState { data.loadState.refresh is LoadState.NotLoading } @@ -235,7 +232,6 @@ fun FavouritesScreen(navigator: DestinationsNavigator) { SearchBarScreen( title = title, - searchFieldState = searchFieldState, searchFieldHint = searchBarHint, onApplySearch = { refresh(FavListUrlBuilder(urlBuilder.favCat, it)) }, onSearchExpanded = { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/HistoryScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/HistoryScreen.kt index 5ebdae90a..7df4640a7 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/HistoryScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/HistoryScreen.kt @@ -2,6 +2,7 @@ package com.hippo.ehviewer.ui.screen import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -9,21 +10,26 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ClearAll -import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.SwipeToDismissBoxValue import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberSwipeToDismissBoxState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +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.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -40,10 +46,10 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.icons.EhIcons import com.hippo.ehviewer.icons.big.History -import com.hippo.ehviewer.ui.LocalNavDrawerState import com.hippo.ehviewer.ui.composing import com.hippo.ehviewer.ui.doGalleryInfoAction import com.hippo.ehviewer.ui.main.GalleryInfoListItem +import com.hippo.ehviewer.ui.main.plus import com.hippo.ehviewer.ui.tools.Deferred import com.hippo.ehviewer.ui.tools.FastScrollLazyColumn import com.hippo.ehviewer.ui.tools.SwipeToDismissBox2 @@ -51,54 +57,70 @@ import com.hippo.ehviewer.ui.tools.rememberInVM import com.hippo.ehviewer.util.FavouriteStatusRouter import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import eu.kanade.tachiyomi.util.lang.launchIO +import kotlin.math.roundToInt import kotlinx.coroutines.delay import kotlinx.coroutines.launch @Destination @Composable fun HistoryScreen(navigator: DestinationsNavigator) = composing(navigator) { - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val title = stringResource(id = R.string.history) + val hint = stringResource(R.string.search_bar_hint, title) + + var searchBarOffsetY by remember { mutableIntStateOf(0) } + var keyword by rememberSaveable { mutableStateOf("") } + + val density = LocalDensity.current val historyData = rememberInVM { Pager(config = PagingConfig(pageSize = 20, jumpThreshold = 40)) { - EhDB.historyLazyList + if (keyword.isNotEmpty()) { + EhDB.searchHistory(keyword) + } else { + EhDB.historyLazyList + } }.flow.cachedIn(viewModelScope) }.collectAsLazyPagingItems() FavouriteStatusRouter.Observe(historyData) - Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - TopAppBar( - title = { Text(text = stringResource(id = R.string.history)) }, - navigationIcon = { - val drawerState = LocalNavDrawerState.current - IconButton(onClick = { launch { drawerState.open() } }) { - Icon(imageVector = Icons.Default.Menu, contentDescription = null) - } - }, - actions = { - IconButton(onClick = { - launchIO { - awaitPermissionOrCancel( - confirmText = R.string.clear_all, - text = { Text(text = stringResource(id = R.string.clear_all_history)) }, - ) - EhDB.clearHistoryInfo() - } - }) { - Icon(imageVector = Icons.Default.ClearAll, contentDescription = null) - } - }, - scrollBehavior = scrollBehavior, - ) + SearchBarScreen( + title = title, + searchFieldHint = hint, + onApplySearch = { + keyword = it + historyData.refresh() + }, + onSearchExpanded = {}, + onSearchHidden = {}, + searchBarOffsetY = { searchBarOffsetY }, + trailingIcon = { + IconButton(onClick = { + launch { + awaitPermissionOrCancel( + confirmText = R.string.clear_all, + text = { Text(text = stringResource(id = R.string.clear_all_history)) }, + ) + EhDB.clearHistoryInfo() + } + }) { + Icon(imageVector = Icons.Default.ClearAll, contentDescription = null) + } }, ) { paddingValues -> + val searchBarConnection = remember { + val topPaddingPx = with(density) { paddingValues.calculateTopPadding().roundToPx() } + object : NestedScrollConnection { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + val dy = -consumed.y + searchBarOffsetY = (searchBarOffsetY - dy).roundToInt().coerceIn(-topPaddingPx, 0) + return Offset.Zero // We never consume it + } + } + } val marginH = dimensionResource(id = R.dimen.gallery_list_margin_h) val cardHeight by collectListThumbSizeAsState() val showPages by Settings.showGalleryPages.collectAsState() FastScrollLazyColumn( - modifier = Modifier.padding(horizontal = marginH).fillMaxSize(), - contentPadding = paddingValues, + modifier = Modifier.nestedScroll(searchBarConnection).fillMaxSize(), + contentPadding = paddingValues + PaddingValues(horizontal = marginH), verticalArrangement = Arrangement.spacedBy(dimensionResource(R.dimen.gallery_list_interval)), ) { items( @@ -111,7 +133,7 @@ fun HistoryScreen(navigator: DestinationsNavigator) = composing(navigator) { val dismissState = rememberSwipeToDismissBoxState( confirmValueChange = { if (it == SwipeToDismissBoxValue.EndToStart) { - launchIO { EhDB.deleteHistoryInfo(info) } + launch { EhDB.deleteHistoryInfo(info) } } true }, @@ -123,7 +145,7 @@ fun HistoryScreen(navigator: DestinationsNavigator) = composing(navigator) { ) { GalleryInfoListItem( onClick = { navigate(info.asDst()) }, - onLongClick = { launchIO { doGalleryInfoAction(info) } }, + onLongClick = { launch { doGalleryInfoAction(info) } }, info = info, showPages = showPages, modifier = Modifier.height(cardHeight), diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt index 965ce8e5a..487c8f4b1 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/SearchBarScreen.kt @@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.forEachTextValue +import androidx.compose.foundation.text.input.rememberTextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -95,7 +96,7 @@ suspend fun SearchDao.suggestions(prefix: String, limit: Int) = @Composable fun SearchBarScreen( title: String? = null, - searchFieldState: TextFieldState, + searchFieldState: TextFieldState = rememberTextFieldState(), searchFieldHint: String? = null, onApplySearch: (String) -> Unit, onSearchExpanded: () -> Unit,