ReImplement history features
This commit is contained in:
parent
35c4d0bf4e
commit
509d7b20b4
@ -22,11 +22,28 @@ interface HistoryQueries : DbProvider {
|
|||||||
* Returns history of recent manga containing last read chapter
|
* Returns history of recent manga containing last read chapter
|
||||||
* @param date recent date range
|
* @param date recent date range
|
||||||
*/
|
*/
|
||||||
fun getRecentManga(date: Date) = db.get()
|
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
|
||||||
.listOfObjects(MangaChapterHistory::class.java)
|
.listOfObjects(MangaChapterHistory::class.java)
|
||||||
.withQuery(
|
.withQuery(
|
||||||
RawQuery.builder()
|
RawQuery.builder()
|
||||||
.query(getRecentMangasQuery())
|
.query(getRecentMangasQuery(offset, search))
|
||||||
|
.args(date.time)
|
||||||
|
.observesTables(HistoryTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns history of recent manga containing last read chapter in 25s
|
||||||
|
* @param date recent date range
|
||||||
|
* @offset offset the db by
|
||||||
|
*/
|
||||||
|
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
|
||||||
|
.listOfObjects(MangaChapterHistory::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getRecentMangasLimitQuery(limit, search))
|
||||||
.args(date.time)
|
.args(date.time)
|
||||||
.observesTables(HistoryTable.TABLE)
|
.observesTables(HistoryTable.TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
@ -76,7 +76,7 @@ fun getRecentsQuery() =
|
|||||||
* and are read after the given time period
|
* and are read after the given time period
|
||||||
* @return return limit is 25
|
* @return return limit is 25
|
||||||
*/
|
*/
|
||||||
fun getRecentMangasQuery() =
|
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
|
||||||
"""
|
"""
|
||||||
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||||
FROM ${Manga.TABLE}
|
FROM ${Manga.TABLE}
|
||||||
@ -91,8 +91,36 @@ fun getRecentMangasQuery() =
|
|||||||
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
|
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}
|
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
|
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||||
LIMIT 25
|
LIMIT 25 OFFSET $offset
|
||||||
|
"""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query to get the recently read chapters of manga from the library up to a date.
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
|
||||||
|
"""
|
||||||
|
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
|
||||||
|
FROM ${Manga.TABLE}
|
||||||
|
JOIN ${Chapter.TABLE}
|
||||||
|
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
|
||||||
|
JOIN ${History.TABLE}
|
||||||
|
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
|
||||||
|
JOIN (
|
||||||
|
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
|
||||||
|
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
|
||||||
|
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}
|
||||||
|
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
|
||||||
|
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
|
||||||
|
LIMIT $limit
|
||||||
"""
|
"""
|
||||||
|
|
||||||
fun getHistoryByMangaId() =
|
fun getHistoryByMangaId() =
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.tachiyomi.ui.recent.history
|
package eu.kanade.tachiyomi.ui.recent.history
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
import java.text.DecimalFormatSymbols
|
import java.text.DecimalFormatSymbols
|
||||||
@ -15,7 +16,7 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
* @constructor creates an instance of the adapter.
|
* @constructor creates an instance of the adapter.
|
||||||
*/
|
*/
|
||||||
class HistoryAdapter(controller: HistoryController) :
|
class HistoryAdapter(controller: HistoryController) :
|
||||||
FlexibleAdapter<HistoryItem>(null, controller, true) {
|
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
||||||
|
|
||||||
val sourceManager by injectLazy<SourceManager>()
|
val sourceManager by injectLazy<SourceManager>()
|
||||||
|
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi.ui.recent.history
|
package eu.kanade.tachiyomi.ui.recent.history
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
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.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.databinding.HistoryControllerBinding
|
import eu.kanade.tachiyomi.databinding.HistoryControllerBinding
|
||||||
@ -13,9 +18,14 @@ import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
import eu.kanade.tachiyomi.ui.base.controller.RootController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
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.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
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.
|
* Fragment that shows recently read manga.
|
||||||
@ -27,6 +37,7 @@ class HistoryController :
|
|||||||
RootController,
|
RootController,
|
||||||
NoToolbarElevationController,
|
NoToolbarElevationController,
|
||||||
FlexibleAdapter.OnUpdateListener,
|
FlexibleAdapter.OnUpdateListener,
|
||||||
|
FlexibleAdapter.EndlessScrollListener,
|
||||||
HistoryAdapter.OnRemoveClickListener,
|
HistoryAdapter.OnRemoveClickListener,
|
||||||
HistoryAdapter.OnResumeClickListener,
|
HistoryAdapter.OnResumeClickListener,
|
||||||
HistoryAdapter.OnItemClickListener,
|
HistoryAdapter.OnItemClickListener,
|
||||||
@ -38,6 +49,12 @@ class HistoryController :
|
|||||||
var adapter: HistoryAdapter? = null
|
var adapter: HistoryAdapter? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endless loading item.
|
||||||
|
*/
|
||||||
|
private var progressItem: ProgressItem? = null
|
||||||
|
private var query = ""
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
return resources?.getString(R.string.label_recent_manga)
|
return resources?.getString(R.string.label_recent_manga)
|
||||||
}
|
}
|
||||||
@ -76,8 +93,20 @@ class HistoryController :
|
|||||||
*
|
*
|
||||||
* @param mangaHistory list of manga history
|
* @param mangaHistory list of manga history
|
||||||
*/
|
*/
|
||||||
fun onNextManga(mangaHistory: List<HistoryItem>) {
|
fun onNextManga(mangaHistory: List<HistoryItem>, cleanBatch: Boolean = false) {
|
||||||
adapter?.updateDataSet(mangaHistory)
|
if (adapter?.itemCount ?: 0 == 0 || cleanBatch) {
|
||||||
|
resetProgressItem()
|
||||||
|
}
|
||||||
|
if (cleanBatch) {
|
||||||
|
adapter?.updateDataSet(mangaHistory)
|
||||||
|
} else {
|
||||||
|
adapter?.onLoadMoreComplete(mangaHistory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAddPageError(error: Throwable) {
|
||||||
|
adapter?.onLoadMoreComplete(null)
|
||||||
|
adapter?.endlessTargetCount = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpdateEmptyView(size: Int) {
|
override fun onUpdateEmptyView(size: Int) {
|
||||||
@ -88,9 +117,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) {
|
override fun onResumeClick(position: Int) {
|
||||||
val activity = activity ?: return
|
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)
|
val nextChapter = presenter.getNextChapter(chapter, manga)
|
||||||
if (nextChapter != null) {
|
if (nextChapter != null) {
|
||||||
@ -102,12 +152,12 @@ class HistoryController :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onRemoveClick(position: Int) {
|
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)
|
RemoveHistoryDialog(this, manga, history).showDialog(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(position: Int) {
|
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())
|
router.pushController(MangaController(manga).withFadeTransaction())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,4 +170,35 @@ class HistoryController :
|
|||||||
presenter.removeFromHistory(history)
|
presenter.removeFromHistory(history)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.recently_read, 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.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
|
||||||
|
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
|
||||||
|
activity?.invalidateOptionsMenu()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,27 +28,63 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
|
|||||||
* Used to connect to database
|
* Used to connect to database
|
||||||
*/
|
*/
|
||||||
val db: DatabaseHelper by injectLazy()
|
val db: DatabaseHelper by injectLazy()
|
||||||
|
var lastCount = 25
|
||||||
|
var lastSearch = ""
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
|
|
||||||
// Used to get a list of recently read manga
|
// Used to get a list of recently read manga
|
||||||
getRecentMangaObservable()
|
updateList()
|
||||||
.subscribeLatestCache(HistoryController::onNextManga)
|
}
|
||||||
|
|
||||||
|
fun requestNext(offset: Int, search: String = "") {
|
||||||
|
lastCount = offset
|
||||||
|
lastSearch = search
|
||||||
|
getRecentMangaObservable((offset), search)
|
||||||
|
.subscribeLatestCache(
|
||||||
|
{ view, mangas ->
|
||||||
|
view.onNextManga(mangas)
|
||||||
|
},
|
||||||
|
HistoryController::onAddPageError
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get recent manga observable
|
* Get recent manga observable
|
||||||
* @return list of history
|
* @return list of history
|
||||||
*/
|
*/
|
||||||
fun getRecentMangaObservable(): Observable<List<HistoryItem>> {
|
fun getRecentMangaObservable(offset: Int = 0, search: String = ""): Observable<List<HistoryItem>> {
|
||||||
// Set date limit for recent manga
|
// Set date limit for recent manga
|
||||||
val cal = Calendar.getInstance().apply {
|
val cal = Calendar.getInstance().apply {
|
||||||
time = Date()
|
time = Date()
|
||||||
add(Calendar.MONTH, -3)
|
add(Calendar.YEAR, -50)
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.getRecentManga(cal.time).asRxObservable()
|
return db.getRecentManga(cal.time, offset, search).asRxObservable()
|
||||||
|
.map { recents ->
|
||||||
|
val map = TreeMap<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) }
|
||||||
|
val byDay = recents
|
||||||
|
.groupByTo(map, { it.history.last_read.toDateKey() })
|
||||||
|
byDay.flatMap { entry ->
|
||||||
|
val dateItem = DateSectionItem(entry.key)
|
||||||
|
entry.value.map { HistoryItem(it, dateItem) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get recent manga observable
|
||||||
|
* @return list of history
|
||||||
|
*/
|
||||||
|
private fun getRecentMangaLimitObservable(offset: Int = 0, search: String = ""): Observable<List<HistoryItem>> {
|
||||||
|
// Set limit for recent manga
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
cal.time = Date()
|
||||||
|
cal.add(Calendar.YEAR, -50)
|
||||||
|
|
||||||
|
return db.getRecentMangaLimit(cal.time, lastCount, search).asRxObservable()
|
||||||
.map { recents ->
|
.map { recents ->
|
||||||
val map = TreeMap<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) }
|
val map = TreeMap<Date, MutableList<MangaChapterHistory>> { d1, d2 -> d2.compareTo(d1) }
|
||||||
val byDay = recents
|
val byDay = recents
|
||||||
@ -71,6 +107,17 @@ class HistoryPresenter : BasePresenter<HistoryController>() {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateList(search: String? = null) {
|
||||||
|
lastSearch = search ?: lastSearch
|
||||||
|
getRecentMangaLimitObservable(lastCount, lastSearch).take(1)
|
||||||
|
.subscribeLatestCache(
|
||||||
|
{ view, mangas ->
|
||||||
|
view.onNextManga(mangas, true)
|
||||||
|
},
|
||||||
|
HistoryController::onAddPageError
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes all chapters belonging to manga from history.
|
* Removes all chapters belonging to manga from history.
|
||||||
* @param mangaId id of manga
|
* @param mangaId id of manga
|
||||||
|
11
app/src/main/res/menu/recently_read.xml
Normal file
11
app/src/main/res/menu/recently_read.xml
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user