From 9d2adcd512c28872c9e958e9fcdcbccbd11b3b35 Mon Sep 17 00:00:00 2001 From: jobobby04 Date: Sun, 27 Sep 2020 18:17:14 -0400 Subject: [PATCH] Add infinite history and search history (#3827) * Add infinite history and search history * Cleanup code --- .../data/database/queries/HistoryQueries.kt | 7 +- .../data/database/queries/RawQueries.kt | 9 +- .../ui/recent/history/HistoryAdapter.kt | 3 +- .../ui/recent/history/HistoryController.kt | 90 +++++++++++++++++-- .../ui/recent/history/HistoryPresenter.kt | 36 ++++++-- app/src/main/res/menu/history.xml | 11 +++ 6 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/menu/history.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index fa4072dd9..f6a8f3886 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -21,12 +21,15 @@ interface HistoryQueries : DbProvider { /** * Returns history of recent manga containing last read chapter * @param date recent date range + * @param limit the limit of manga to grab + * @param offset offset the db by + * @param search what to search in the db history */ - fun getRecentManga(date: Date) = db.get() + fun getRecentManga(date: Date, limit: Int = 25, offset: Int = 0, search: String = "") = db.get() .listOfObjects(MangaChapterHistory::class.java) .withQuery( RawQuery.builder() - .query(getRecentMangasQuery()) + .query(getRecentMangasQuery(limit, offset, search)) .args(date.time) .observesTables(HistoryTable.TABLE) .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 166447da4..b90288bb6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -49,9 +49,8 @@ fun getRecentsQuery() = * The max_last_read table contains the most recent chapters grouped by manga * The select statement returns all information of chapters that have the same id as the chapter in max_last_read * and are read after the given time period - * @return return limit is 25 */ -fun getRecentMangasQuery() = +fun getRecentMangasQuery(limit: Int = 25, offset: Int = 0, search: String = "") = """ SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.* FROM ${Manga.TABLE} @@ -65,9 +64,11 @@ fun getRecentMangasQuery() = ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID} - WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} + WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? + AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} + AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%' ORDER BY max_last_read.${History.COL_LAST_READ} DESC - LIMIT 25 + LIMIT $limit OFFSET $offset """ fun getHistoryByMangaId() = diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt index d5c19f32d..d85a41a20 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryAdapter.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.recent.history import eu.davidea.flexibleadapter.FlexibleAdapter +import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.source.SourceManager import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat @@ -15,7 +16,7 @@ import java.text.DecimalFormatSymbols * @constructor creates an instance of the adapter. */ class HistoryAdapter(controller: HistoryController) : - FlexibleAdapter(null, controller, true) { + FlexibleAdapter>(null, controller, true) { val sourceManager by injectLazy() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt index 7dfd592c3..e6378176d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt @@ -1,11 +1,15 @@ package eu.kanade.tachiyomi.ui.recent.history import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.widget.SearchView import androidx.recyclerview.widget.LinearLayoutManager import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.HistoryControllerBinding @@ -13,9 +17,14 @@ import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.RootController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction +import eu.kanade.tachiyomi.ui.browse.source.browse.ProgressItem import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import reactivecircus.flowbinding.appcompat.queryTextChanges /** * Fragment that shows recently read manga. @@ -27,6 +36,7 @@ class HistoryController : RootController, NoToolbarElevationController, FlexibleAdapter.OnUpdateListener, + FlexibleAdapter.EndlessScrollListener, HistoryAdapter.OnRemoveClickListener, HistoryAdapter.OnResumeClickListener, HistoryAdapter.OnItemClickListener, @@ -38,6 +48,16 @@ class HistoryController : var adapter: HistoryAdapter? = null private set + /** + * Endless loading item. + */ + private var progressItem: ProgressItem? = null + + /** + * Search query. + */ + private var query = "" + override fun getTitle(): String? { return resources?.getString(R.string.label_recent_manga) } @@ -77,8 +97,23 @@ class HistoryController : * * @param mangaHistory list of manga history */ - fun onNextManga(mangaHistory: List) { - adapter?.updateDataSet(mangaHistory) + fun onNextManga(mangaHistory: List, cleanBatch: Boolean = false) { + if (adapter?.itemCount ?: 0 == 0 || cleanBatch) { + resetProgressItem() + } + if (cleanBatch) { + adapter?.updateDataSet(mangaHistory) + } else { + adapter?.onLoadMoreComplete(mangaHistory) + } + } + + /** + * Safely error if next page load fails + */ + fun onAddPageError(error: Throwable) { + adapter?.onLoadMoreComplete(null) + adapter?.endlessTargetCount = 1 } override fun onUpdateEmptyView(size: Int) { @@ -89,9 +124,30 @@ class HistoryController : } } + /** + * Sets a new progress item and reenables the scroll listener. + */ + private fun resetProgressItem() { + progressItem = ProgressItem() + adapter?.endlessTargetCount = 0 + adapter?.setEndlessScrollListener(this, progressItem!!) + } + + override fun onLoadMore(lastPosition: Int, currentPage: Int) { + val view = view ?: return + if (BackupRestoreService.isRunning(view.context.applicationContext)) { + onAddPageError(Throwable()) + return + } + val adapter = adapter ?: return + presenter.requestNext(adapter.itemCount, query) + } + + override fun noMoreLoad(newItemsSize: Int) {} + override fun onResumeClick(position: Int) { val activity = activity ?: return - val (manga, chapter, _) = adapter?.getItem(position)?.mch ?: return + val (manga, chapter, _) = (adapter?.getItem(position) as? HistoryItem)?.mch ?: return val nextChapter = presenter.getNextChapter(chapter, manga) if (nextChapter != null) { @@ -103,12 +159,12 @@ class HistoryController : } override fun onRemoveClick(position: Int) { - val (manga, _, history) = adapter?.getItem(position)?.mch ?: return + val (manga, _, history) = (adapter?.getItem(position) as? HistoryItem)?.mch ?: return RemoveHistoryDialog(this, manga, history).showDialog(router) } override fun onItemClick(position: Int) { - val manga = adapter?.getItem(position)?.mch?.manga ?: return + val manga = (adapter?.getItem(position) as? HistoryItem)?.mch?.manga ?: return router.pushController(MangaController(manga).withFadeTransaction()) } @@ -121,4 +177,28 @@ class HistoryController : presenter.removeFromHistory(history) } } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.history, menu) + val searchItem = menu.findItem(R.id.action_search) + val searchView = searchItem.actionView as SearchView + searchView.maxWidth = Int.MAX_VALUE + if (query.isNotEmpty()) { + searchItem.expandActionView() + searchView.setQuery(query, true) + searchView.clearFocus() + } + searchView.queryTextChanges() + .filter { router.backstack.lastOrNull()?.controller() == this } + .onEach { + query = it.toString() + presenter.updateList(query) + } + .launchIn(scope) + + // Fixes problem with the overflow icon showing up in lieu of search + searchItem.fixExpand( + onExpand = { invalidateMenuOnExpand() } + ) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt index b75a1dafd..797003995 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt @@ -13,7 +13,6 @@ import rx.Observable import rx.android.schedulers.AndroidSchedulers import uy.kohesive.injekt.injectLazy import java.util.Calendar -import java.util.Comparator import java.util.Date import java.util.TreeMap @@ -33,22 +32,31 @@ class HistoryPresenter : BasePresenter() { super.onCreate(savedState) // Used to get a list of recently read manga - getRecentMangaObservable() - .subscribeLatestCache(HistoryController::onNextManga) + updateList() + } + + fun requestNext(offset: Int, search: String = "") { + getRecentMangaObservable(offset = offset, search = search) + .subscribeLatestCache( + { view, mangas -> + view.onNextManga(mangas) + }, + HistoryController::onAddPageError + ) } /** * Get recent manga observable * @return list of history */ - fun getRecentMangaObservable(): Observable> { + private fun getRecentMangaObservable(limit: Int = 25, offset: Int = 0, search: String = ""): Observable> { // Set date limit for recent manga val cal = Calendar.getInstance().apply { time = Date() - add(Calendar.MONTH, -3) + add(Calendar.YEAR, -50) } - return db.getRecentManga(cal.time).asRxObservable() + return db.getRecentManga(cal.time, limit, offset, search).asRxObservable() .map { recents -> val map = TreeMap> { d1, d2 -> d2.compareTo(d1) } val byDay = recents @@ -71,6 +79,20 @@ class HistoryPresenter : BasePresenter() { .subscribe() } + /** + * Pull a list of history from the db + * @param search a search query to use for filtering + */ + fun updateList(search: String = "") { + getRecentMangaObservable(search = search).take(1) + .subscribeLatestCache( + { view, mangas -> + view.onNextManga(mangas, true) + }, + HistoryController::onAddPageError + ) + } + /** * Removes all chapters belonging to manga from history. * @param mangaId id of manga @@ -103,7 +125,7 @@ class HistoryPresenter : BasePresenter() { } val chapters = db.getChapters(manga).executeAsBlocking() - .sortedWith(Comparator { c1, c2 -> sortFunction(c1, c2) }) + .sortedWith { c1, c2 -> sortFunction(c1, c2) } val currChapterIndex = chapters.indexOfFirst { chapter.id == it.id } return when (manga.sorting) { diff --git a/app/src/main/res/menu/history.xml b/app/src/main/res/menu/history.xml new file mode 100644 index 000000000..b84ac7836 --- /dev/null +++ b/app/src/main/res/menu/history.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file