Use SQLDelight on Updates screen (#7423)

This commit is contained in:
Andreas 2022-07-01 04:18:12 +02:00 committed by GitHub
parent b9e108eb4d
commit 1f10b79ee8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 156 additions and 125 deletions

View file

@ -1,5 +1,6 @@
package eu.kanade.data.manga
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Manga =
@ -24,3 +25,39 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
initialized = initialized,
)
}
val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Pair<Manga, Chapter> =
{ _id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, next_update, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, chapterId, mangaId, chapterUrl, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
Manga(
id = _id,
source = source,
favorite = favorite,
lastUpdate = lastUpdate ?: 0,
dateAdded = dateAdded,
viewerFlags = viewerFlags,
chapterFlags = chapterFlags,
coverLastModified = coverLastModified,
url = url,
title = title,
artist = artist,
author = author,
description = description,
genre = genre,
status = status,
thumbnailUrl = thumbnailUrl,
initialized = initialized,
) to Chapter(
id = chapterId,
mangaId = mangaId,
read = read,
bookmark = bookmark,
lastPageRead = lastPageRead,
dateFetch = dateFetch,
sourceOrder = sourceOrder,
url = chapterUrl,
name = name,
dateUpload = dateUpload,
chapterNumber = chapterNumber,
scanlator = scanlator,
)
}

View file

@ -1,15 +1,11 @@
package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import java.util.Date
interface ChapterQueries : DbProvider {
@ -24,18 +20,6 @@ interface ChapterQueries : DbProvider {
)
.prepare()
fun getRecentChapters(date: Date) = db.get()
.listOfObjects(MangaChapter::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentsQuery())
.args(date.time)
.observesTables(ChapterTable.TABLE)
.build(),
)
.withGetResolver(MangaChapterGetResolver.INSTANCE)
.prepare()
fun getChapter(id: Long) = db.get()
.`object`(Chapter::class.java)
.withQuery(

View file

@ -38,19 +38,6 @@ val libraryQuery =
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID}
"""
/**
* Query to get the recent chapters of manga from the library up to a date.
*/
fun getRecentsQuery() =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, * FROM ${Manga.TABLE} JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1
AND ${Chapter.COL_DATE_UPLOAD} > ?
AND ${Chapter.COL_DATE_FETCH} > ${Manga.COL_DATE_ADDED}
ORDER BY ${Chapter.COL_DATE_UPLOAD} DESC
"""
fun getLastReadMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max

View file

@ -2,16 +2,14 @@ package eu.kanade.tachiyomi.ui.manga.chapter.base
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.model.Page
abstract class BaseChapterItem<T : BaseChapterHolder, H : AbstractHeaderItem<*>>(
val chapter: Chapter,
header: H? = null,
) :
AbstractSectionableItem<T, H?>(header),
Chapter by chapter {
) : AbstractSectionableItem<T, H?>(header) {
private var _status: Download.State = Download.State.NOT_DOWNLOADED
@ -36,12 +34,14 @@ abstract class BaseChapterItem<T : BaseChapterHolder, H : AbstractHeaderItem<*>>
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is BaseChapterItem<*, *>) {
return chapter.id!! == other.chapter.id!!
return chapter.id == other.chapter.id && chapter.read == other.chapter.read
}
return false
}
override fun hashCode(): Int {
return chapter.id!!.hashCode()
var result = chapter.id.hashCode()
result = 31 * result + chapter.read.hashCode()
return result
}
}

View file

@ -11,7 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.SelectableAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download
@ -30,8 +29,10 @@ import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.onAnimationsFinished
import eu.kanade.tachiyomi.widget.ActionModeWithToolbar
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import logcat.LogPriority
import reactivecircus.flowbinding.recyclerview.scrollStateChanges
import reactivecircus.flowbinding.swiperefreshlayout.refreshes
@ -107,6 +108,24 @@ class UpdatesController :
binding.swipeRefresh.isRefreshing = false
}
.launchIn(viewScope)
viewScope.launch {
presenter.updates.collectLatest { updatesItems ->
destroyActionModeIfNeeded()
if (adapter == null) {
adapter = UpdatesAdapter(this@UpdatesController, binding.recycler.context, updatesItems)
binding.recycler.adapter = adapter
adapter!!.fastScroller = binding.fastScroller
} else {
adapter?.updateDataSet(updatesItems)
}
binding.swipeRefresh.isRefreshing = false
binding.fastScroller.isVisible = true
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
}
}
override fun onDestroyView(view: View) {
@ -191,7 +210,7 @@ class UpdatesController :
*/
private fun openChapter(item: UpdatesItem) {
val activity = activity ?: return
val intent = ReaderActivity.newIntent(activity, item.manga, item.chapter)
val intent = ReaderActivity.newIntent(activity, item.manga.id, item.chapter.id)
startActivity(intent)
}
@ -204,26 +223,6 @@ class UpdatesController :
destroyActionModeIfNeeded()
}
/**
* Populate adapter with chapters
* @param chapters list of [Any]
*/
fun onNextRecentChapters(chapters: List<IFlexible<*>>) {
destroyActionModeIfNeeded()
if (adapter == null) {
adapter = UpdatesAdapter(this@UpdatesController, binding.recycler.context, chapters)
binding.recycler.adapter = adapter
adapter!!.fastScroller = binding.fastScroller
} else {
adapter?.updateDataSet(chapters)
}
binding.swipeRefresh.isRefreshing = false
binding.fastScroller.isVisible = true
binding.recycler.onAnimationsFinished {
(activity as? MainActivity)?.ready = true
}
}
override fun onUpdateEmptyView(size: Int) {
if (size > 0) {
binding.emptyView.hide()
@ -317,8 +316,8 @@ class UpdatesController :
}
override fun startDownloadNow(position: Int) {
val chapter = adapter?.getItem(position) as? UpdatesItem ?: return
presenter.startDownloadingNow(chapter)
val item = adapter?.getItem(position) as? UpdatesItem ?: return
presenter.startDownloadingNow(item.chapter)
}
private fun bookmarkChapters(chapters: List<UpdatesItem>, bookmarked: Boolean) {
@ -357,8 +356,8 @@ class UpdatesController :
if (chapters.isEmpty()) return
toolbar.findToolbarItem(R.id.action_download)?.isVisible = chapters.any { !it.isDownloaded }
toolbar.findToolbarItem(R.id.action_delete)?.isVisible = chapters.any { it.isDownloaded }
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.bookmark }
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.bookmark }
toolbar.findToolbarItem(R.id.action_bookmark)?.isVisible = chapters.any { !it.chapter.bookmark }
toolbar.findToolbarItem(R.id.action_remove_bookmark)?.isVisible = chapters.all { it.chapter.bookmark }
toolbar.findToolbarItem(R.id.action_mark_as_read)?.isVisible = chapters.any { !it.chapter.read }
toolbar.findToolbarItem(R.id.action_mark_as_unread)?.isVisible = chapters.all { it.chapter.read }
}

View file

@ -44,12 +44,12 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter)
} else {
binding.mangaTitle.setTextColor(adapter.unreadColor)
binding.chapterTitle.setTextColor(
if (item.bookmark) adapter.bookmarkedColor else adapter.unreadColorSecondary,
if (item.chapter.bookmark) adapter.bookmarkedColor else adapter.unreadColorSecondary,
)
}
// Set bookmark status
binding.bookmarkIcon.isVisible = item.bookmark
binding.bookmarkIcon.isVisible = item.chapter.bookmark
// Set chapter status
binding.download.isVisible = item.manga.source != LocalSource.ID

View file

@ -4,9 +4,9 @@ import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterItem
import eu.kanade.tachiyomi.ui.recent.DateSectionItem

View file

@ -1,17 +1,28 @@
package eu.kanade.tachiyomi.ui.recent.updates
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.data.DatabaseHandler
import eu.kanade.data.manga.mangaChapterMapper
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.recent.DateSectionItem
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.toDateKey
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import logcat.LogPriority
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
@ -25,24 +36,22 @@ import java.util.TreeMap
class UpdatesPresenter : BasePresenter<UpdatesController>() {
val preferences: PreferencesHelper by injectLazy()
private val db: DatabaseHelper by injectLazy()
private val downloadManager: DownloadManager by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val handler: DatabaseHandler by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val relativeTime: Int = preferences.relativeTime().get()
private val dateFormat: DateFormat = preferences.dateFormat()
/**
* List containing chapter and manga information
*/
private var chapters: List<UpdatesItem> = emptyList()
private val _updates: MutableStateFlow<List<UpdatesItem>> = MutableStateFlow(listOf())
val updates: StateFlow<List<UpdatesItem>> = _updates.asStateFlow()
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
getUpdatesObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(UpdatesController::onNextRecentChapters)
downloadManager.queue.getStatusObservable()
.observeOn(Schedulers.io())
@ -72,43 +81,47 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
*
* @return observable containing recent chapters and date
*/
private fun getUpdatesObservable(): Observable<List<UpdatesItem>> {
private fun getUpdatesObservable() {
// Set date limit for recent chapters
val cal = Calendar.getInstance().apply {
time = Date()
add(Calendar.MONTH, -3)
}
return db.getRecentChapters(cal.time).asRxObservable()
// Convert to a list of recent chapters.
.map { mangaChapters ->
val map = TreeMap<Date, MutableList<MangaChapter>> { d1, d2 -> d2.compareTo(d1) }
val byDay = mangaChapters
.groupByTo(map) { it.chapter.date_fetch.toDateKey() }
byDay.flatMap { entry ->
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
entry.value
.sortedWith(compareBy({ it.chapter.date_fetch }, { it.chapter.chapter_number })).asReversed()
.map { UpdatesItem(it.chapter, it.manga, dateItem) }
}
presenterScope.launchIO {
val cal = Calendar.getInstance().apply {
time = Date()
add(Calendar.MONTH, -3)
}
.doOnNext { list ->
list.forEach { item ->
// Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == item.chapter.id }
// If there's an active download, assign it, otherwise ask the manager if
// the chapter is downloaded and assign it to the status.
if (download != null) {
item.download = download
handler
.subscribeToList {
mangasQueries.getRecentlyUpdated(after = cal.timeInMillis, mangaChapterMapper)
}
.map { mangaChapter ->
val map = TreeMap<Date, MutableList<Pair<Manga, Chapter>>> { d1, d2 -> d2.compareTo(d1) }
val byDate = mangaChapter.groupByTo(map) { it.second.dateFetch.toDateKey() }
byDate.flatMap { entry ->
val dateItem = DateSectionItem(entry.key, relativeTime, dateFormat)
entry.value
.sortedWith(compareBy({ it.second.dateFetch }, { it.second.chapterNumber })).asReversed()
.map { UpdatesItem(it.second, it.first, dateItem) }
}
}
setDownloadedChapters(list)
chapters = list
.collectLatest { list ->
list.forEach { item ->
// Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == item.chapter.id }
// Set unread chapter count for bottom bar badge
preferences.unreadUpdatesCount().set(list.count { !it.read })
}
// If there's an active download, assign it, otherwise ask the manager if
// the chapter is downloaded and assign it to the status.
if (download != null) {
item.download = download
}
}
setDownloadedChapters(list)
_updates.value = list
// Set unread chapter count for bottom bar badge
preferences.unreadUpdatesCount().set(list.count { !it.chapter.read })
}
}
}
/**
@ -135,6 +148,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
private fun onDownloadStatusChange(download: Download) {
// Assign the download to the model object.
if (download.status == Download.State.QUEUE) {
val chapters = (view?.adapter?.currentItems ?: emptyList()).filterIsInstance<UpdatesItem>()
val chapter = chapters.find { it.chapter.id == download.chapter.id }
if (chapter != null && chapter.download == null) {
chapter.download = download
@ -153,17 +167,16 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
* @param read read status
*/
fun markChapterRead(items: List<UpdatesItem>, read: Boolean) {
val chapters = items.map { it.chapter }
chapters.forEach {
it.read = read
if (!read) {
it.last_page_read = 0
presenterScope.launchIO {
val toUpdate = items.map {
ChapterUpdate(
read = read,
lastPageRead = if (!read) 0 else null,
id = it.chapter.id,
)
}
updateChapter.awaitAll(toUpdate)
}
Observable.fromCallable { db.updateChaptersProgress(chapters).executeAsBlocking() }
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@ -190,14 +203,15 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
* @param bookmarked bookmark status
*/
fun bookmarkChapters(items: List<UpdatesItem>, bookmarked: Boolean) {
val chapters = items.map { it.chapter }
chapters.forEach {
it.bookmark = bookmarked
presenterScope.launchIO {
val toUpdate = items.map {
ChapterUpdate(
bookmark = bookmarked,
id = it.chapter.id,
)
}
updateChapter.awaitAll(toUpdate)
}
Observable.fromCallable { db.updateChaptersProgress(chapters).executeAsBlocking() }
.subscribeOn(Schedulers.io())
.subscribe()
}
/**
@ -205,7 +219,7 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
* @param items list of recent chapters seleted.
*/
fun downloadChapters(items: List<UpdatesItem>) {
items.forEach { downloadManager.downloadChapters(it.manga, listOf(it.chapter)) }
items.forEach { downloadManager.downloadChapters(it.manga.toDbManga(), listOf(it.chapter.toDbChapter())) }
}
/**
@ -216,9 +230,9 @@ class UpdatesPresenter : BasePresenter<UpdatesController>() {
private fun deleteChaptersInternal(chapterItems: List<UpdatesItem>) {
val itemsByManga = chapterItems.groupBy { it.manga.id }
for ((_, items) in itemsByManga) {
val manga = items.first().manga
val manga = items.first().manga.toDbManga()
val source = sourceManager.get(manga.source) ?: continue
val chapters = items.map { it.chapter }
val chapters = items.map { it.chapter.toDbChapter() }
downloadManager.deleteChapters(chapters, manga, source)
items.forEach {

View file

@ -76,6 +76,16 @@ FROM mangas
WHERE favorite = 0
GROUP BY source;
getRecentlyUpdated:
SELECT *
FROM mangas M
JOIN chapters C
ON M._id = C.manga_id
WHERE M.favorite = 1
AND C.date_upload > :after
AND C.date_fetch > M.date_added
ORDER BY C.date_upload DESC;
deleteMangasNotInLibraryBySourceIds:
DELETE FROM mangas
WHERE favorite = 0 AND source IN :sourceIds;