mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Add infinite history and search history (#3827)
* Add infinite history and search history * Cleanup code
This commit is contained in:
		| @@ -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() | ||||
|   | ||||
| @@ -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() = | ||||
|   | ||||
| @@ -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<HistoryItem>(null, controller, true) { | ||||
|     FlexibleAdapter<IFlexible<*>>(null, controller, true) { | ||||
|  | ||||
|     val sourceManager by injectLazy<SourceManager>() | ||||
|  | ||||
|   | ||||
| @@ -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<HistoryItem>) { | ||||
|         adapter?.updateDataSet(mangaHistory) | ||||
|     fun onNextManga(mangaHistory: List<HistoryItem>, 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() } | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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<HistoryController>() { | ||||
|         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<List<HistoryItem>> { | ||||
|     private fun getRecentMangaObservable(limit: Int = 25, offset: Int = 0, search: String = ""): Observable<List<HistoryItem>> { | ||||
|         // 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<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) } | ||||
|                 val byDay = recents | ||||
| @@ -71,6 +79,20 @@ class HistoryPresenter : BasePresenter<HistoryController>() { | ||||
|             .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<HistoryController>() { | ||||
|         } | ||||
|  | ||||
|         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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user