Эх сурвалжийг харах

Use SQLDelight on Updates screen (#7423)

Andreas 2 жил өмнө
parent
commit
1f10b79ee8

+ 37 - 0
app/src/main/java/eu/kanade/data/manga/MangaMapper.kt

@@ -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,
+        )
+    }

+ 0 - 16
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/ChapterQueries.kt

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

+ 0 - 13
app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt

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

+ 6 - 6
app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/base/BaseChapterItem.kt

@@ -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
     }
 }

+ 25 - 26
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesController.kt

@@ -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 }
     }

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt

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

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesItem.kt

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

+ 74 - 60
app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesPresenter.kt

@@ -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)
-        }
+        presenterScope.launchIO {
+            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) }
+            handler
+                .subscribeToList {
+                    mangasQueries.getRecentlyUpdated(after = cal.timeInMillis, mangaChapterMapper)
                 }
-            }
-            .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
+                .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 }
+
+                        // 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)
 
-                // Set unread chapter count for bottom bar badge
-                preferences.unreadUpdatesCount().set(list.count { !it.read })
-            }
+                    _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 {

+ 10 - 0
app/src/main/sqldelight/data/mangas.sq

@@ -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;