Add infinite history and search history (#3827)

* Add infinite history and search history

* Cleanup code
This commit is contained in:
jobobby04 2020-09-27 18:17:14 -04:00 committed by GitHub
parent fb3756420b
commit 9d2adcd512
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 19 deletions

View File

@ -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()

View File

@ -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() =

View File

@ -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>()

View File

@ -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>) {
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() }
)
}
}

View File

@ -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) {

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_search_24dp"
android:title="@string/action_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconTint="?attr/colorOnPrimary"
app:showAsAction="ifRoom|collapseActionView" />
</menu>