Add Filters to Updates screen (#2851)

* Add Filters to Updates screen

Behaves basically like the filters in the library:

- Unread: Show/Don't show unread chapters
- Downloaded: Show/Don't show downloaded chapters
- Started: Show/Don't show chapters that have some progress but aren't
  fully Read
- Bookmarked: Show/Don't show chapters that have been bookmarked

Started behaves differently from its Library counterpart because the
actual manga data is not available at this point in time and I thought
calling getManga for each entry without caching would be a pretty bad
idea.

I have modelled this closely on the filter control flow in the
Library, but I'm sure this can be simplified/adjusted in some way.

* Move most filtering logic to SQL

Unread, Started, and Bookmarked filters are now part of the SQL query.

Download state cannot be filtered in the database so it remains in
Kotlin.

Because the Downloaded filter has to be run in Kotlin, the combine
flow uses the preferences flow twice, once to get the SQL query params
and once for the Kotlin filters (only Downloaded at this time).

* Add "Hide excluded scanlators" to update filters

Based on the work done in #1623 but integrated with the other filters
in this PR. Added the user as a co-author for credit.

Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com>

---------

Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com>
(cherry picked from commit bbe9aa8561360f030072fbc49f79748e71c6535e)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
#	data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt
#	data/src/main/sqldelight/tachiyomi/migrations/9.sqm
#	domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt
This commit is contained in:
MajorTanya
2026-01-17 11:43:40 +01:00
committed by Jobobby04
parent b0d6e16ca3
commit 1f51569a35
16 changed files with 521 additions and 73 deletions
@@ -114,6 +114,21 @@ class AndroidDatabaseHandler(
// SY -->
fun getLibraryQuery(condition: String = "M.favorite = 1") = LibraryQuery(driver, condition)
fun getUpdatesQuery(after: Long, limit: Long) = UpdatesQuery(driver, after, limit)
fun getUpdatesQuery(
after: Long,
limit: Long,
read: Boolean?,
started: Long?,
bookmarked: Boolean?,
hideExcludedScanlators: Long,
) = UpdatesQuery(
driver,
after,
limit,
read,
started,
bookmarked,
hideExcludedScanlators,
)
// SY <--
}
@@ -24,72 +24,113 @@ private val mapper = { cursor: SqlCursor ->
cursor.getLong(12)!!,
cursor.getLong(13)!!,
cursor.getLong(14)!!,
cursor.getString(15),
)
}
class UpdatesQuery(val driver: SqlDriver, val after: Long, val limit: Long) : ExecutableQuery<UpdatesView>(mapper) {
class UpdatesQuery(
val driver: SqlDriver,
val after: Long,
val limit: Long,
val read: Boolean?,
val started: Long?,
val bookmarked: Boolean?,
val hideExcludedScanlators: Long,
) : ExecutableQuery<UpdatesView>(mapper) {
override fun <R> execute(mapper: (SqlCursor) -> QueryResult<R>): QueryResult<R> {
return driver.executeQuery(
null,
"""
SELECT
mangas._id AS mangaId,
mangas.title AS mangaTitle,
chapters._id AS chapterId,
chapters.name AS chapterName,
chapters.scanlator,
chapters.url AS chapterUrl,
chapters.read,
chapters.bookmark,
chapters.last_page_read,
mangas.source,
mangas.favorite,
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch
FROM mangas JOIN chapters
ON mangas._id = chapters.manga_id
WHERE favorite = 1 AND source <> $MERGED_SOURCE_ID
AND date_fetch > date_added
AND dateUpload > :after
UNION
SELECT
mangas._id AS mangaId,
mangas.title AS mangaTitle,
chapters._id AS chapterId,
chapters.name AS chapterName,
chapters.scanlator,
chapters.url AS chapterUrl,
chapters.read,
chapters.bookmark,
chapters.last_page_read,
mangas.source,
mangas.favorite,
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch
FROM mangas
LEFT JOIN (
SELECT merged.manga_id,merged.merge_id
FROM merged
GROUP BY merged.merge_id
) as ME
ON ME.merge_id = mangas._id
JOIN chapters
ON ME.manga_id = chapters.manga_id
WHERE favorite = 1 AND source = $MERGED_SOURCE_ID
AND date_fetch > date_added
AND dateUpload > :after
ORDER BY datefetch DESC
SELECT *
FROM (
-- Normal source
SELECT
mangas._id AS mangaId,
mangas.title AS mangaTitle,
chapters._id AS chapterId,
chapters.name AS chapterName,
chapters.scanlator,
chapters.url AS chapterUrl,
chapters.read,
chapters.bookmark,
chapters.last_page_read,
mangas.source,
mangas.favorite,
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch,
excluded_scanlators.scanlator AS excludedScanlator
FROM mangas
JOIN chapters
ON mangas._id = chapters.manga_id
LEFT JOIN excluded_scanlators
ON mangas._id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
WHERE mangas.source <> $MERGED_SOURCE_ID
AND date_fetch > date_added
UNION ALL
-- Merged source
SELECT
mangas._id AS mangaId,
mangas.title AS mangaTitle,
chapters._id AS chapterId,
chapters.name AS chapterName,
chapters.scanlator,
chapters.url AS chapterUrl,
chapters.read,
chapters.bookmark,
chapters.last_page_read,
mangas.source,
mangas.favorite,
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch,
excluded_scanlators.scanlator AS excludedScanlator
FROM mangas
LEFT JOIN (
SELECT merged.manga_id, merged.merge_id
FROM merged
GROUP BY merged.merge_id
) AS ME
ON ME.merge_id = mangas._id
JOIN chapters
ON ME.manga_id = chapters.manga_id
LEFT JOIN excluded_scanlators
ON ME.merge_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
WHERE mangas.source = $MERGED_SOURCE_ID
AND date_fetch > date_added
) AS combined
WHERE
favorite = 1
AND dateUpload > :after
AND (:read IS NULL OR read = :read)
AND (
:started IS NULL
OR (:started = 1 AND last_page_read > 0 AND read = 0)
OR (:started = 0 AND last_page_read = 0 AND read = 0)
)
AND (:bookmarked IS NULL OR bookmark = :bookmarked)
AND (
excludedScanlator IS NULL OR :hideExcludedScanlators = 0
)
ORDER BY datefetch DESC
LIMIT :limit;
""".trimIndent(),
mapper,
2,
6,
binders = {
bindLong(0, after)
bindLong(1, limit)
var parameterIndex = 0
bindLong(parameterIndex++, after)
bindBoolean(parameterIndex++, read)
bindLong(parameterIndex++, started)
bindBoolean(parameterIndex++, bookmarked)
bindLong(parameterIndex++, hideExcludedScanlators)
bindLong(parameterIndex++, limit)
},
)
}
@@ -2,6 +2,7 @@ package tachiyomi.data.updates
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.core.common.util.lang.toLong
import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.DatabaseHandler
import tachiyomi.domain.manga.model.MangaCover
@@ -28,12 +29,36 @@ class UpdatesRepositoryImpl(
}
}
override fun subscribeAll(after: Long, limit: Long): Flow<List<UpdatesWithRelations>> {
override fun subscribeAll(
after: Long,
limit: Long,
unread: Boolean?,
started: Boolean?,
bookmarked: Boolean?,
hideExcludedScanlators: Boolean,
): Flow<List<UpdatesWithRelations>> {
return databaseHandler.subscribeToList {
updatesViewQueries.getRecentUpdates(after, limit, ::mapUpdatesWithRelations)
updatesViewQueries.getRecentUpdatesWithFilters(
after = after,
limit = limit,
// invert because unread in Kotlin -> read column in SQL
read = unread?.let { !it },
started = started?.toLong(),
bookmarked = bookmarked,
hideExcludedScanlators = hideExcludedScanlators.toLong(),
mapper = ::mapUpdatesWithRelations,
)
}.map {
databaseHandler.awaitListExecutable {
(databaseHandler as AndroidDatabaseHandler).getUpdatesQuery(after, limit)
(databaseHandler as AndroidDatabaseHandler).getUpdatesQuery(
after = after,
limit = limit,
// invert because unread in Kotlin -> read column in SQL
read = unread?.let { !it },
started = started?.toLong(),
bookmarked = bookmarked,
hideExcludedScanlators = hideExcludedScanlators.toLong(),
)
}
.map(::mapUpdatesView)
}
@@ -70,6 +95,7 @@ class UpdatesRepositoryImpl(
coverLastModified: Long,
dateUpload: Long,
dateFetch: Long,
excludedScanlator: String?,
): UpdatesWithRelations = UpdatesWithRelations(
mangaId = mangaId,
// SY -->
@@ -0,0 +1,29 @@
-- Add excluded_scanlators to updatesView
DROP VIEW IF EXISTS updatesView;
CREATE VIEW updatesView AS
SELECT
mangas._id AS mangaId,
mangas.title AS mangaTitle,
chapters._id AS chapterId,
chapters.name AS chapterName,
chapters.scanlator,
chapters.url AS chapterUrl,
chapters.read,
chapters.bookmark,
chapters.last_page_read,
mangas.source,
mangas.favorite,
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch,
excluded_scanlators.scanlator AS excludedScanlator
FROM mangas JOIN chapters
ON mangas._id = chapters.manga_id
LEFT JOIN excluded_scanlators
ON mangas._id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
WHERE favorite = 1
AND date_fetch > date_added
ORDER BY date_fetch DESC;
@@ -14,9 +14,13 @@ SELECT
mangas.thumbnail_url AS thumbnailUrl,
mangas.cover_last_modified AS coverLastModified,
chapters.date_upload AS dateUpload,
chapters.date_fetch AS datefetch
chapters.date_fetch AS datefetch,
excluded_scanlators.scanlator AS excludedScanlator
FROM mangas JOIN chapters
ON mangas._id = chapters.manga_id
LEFT JOIN excluded_scanlators
ON mangas._id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
WHERE favorite = 1
AND date_fetch > date_added
ORDER BY date_fetch DESC;
@@ -27,6 +31,23 @@ FROM updatesView
WHERE dateUpload > :after
LIMIT :limit;
getRecentUpdatesWithFilters:
SELECT *
FROM updatesView
WHERE dateUpload > :after
AND (:read IS NULL OR read = :read)
-- Started means some progress but not finished, Read means finished chapter, thus:
AND (
:started IS NULL
OR (:started = 1 AND last_page_read > 0 AND read = 0)
OR (:started = 0 AND last_page_read = 0 AND read = 0)
)
AND (:bookmarked IS NULL OR bookmark = :bookmarked)
AND (
(excludedScanlator IS NULL OR :hideExcludedScanlators = 0)
)
LIMIT :limit;
getUpdatesByReadStatus:
SELECT *
FROM updatesView