diff --git a/CHANGELOG.md b/CHANGELOG.md index caf33bc36..4f22772df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Added - Automatically remove downloads on Suwayomi after reading, configurable via extension settings ([@cpiber](https://github.com/cpiber)) ([#2673](https://github.com/mihonapp/mihon/pull/2673)) - Display author & artist name in MAL search results ([@MajorTanya](https://github.com/MajorTanya)) ([#2833](https://github.com/mihonapp/mihon/pull/2833)) +- Add filter options to Updates tab ([@MajorTanya](https://github.com/MajorTanya)) ([#2851](https://github.com/mihonapp/mihon/pull/2851)) ### Improved - Minimize memory usage by reducing in-memory cover cache size ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2266](https://github.com/mihonapp/mihon/pull/2266)) diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesDeleteConfirmationDialog.kt similarity index 100% rename from app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt rename to app/src/main/java/eu/kanade/presentation/updates/UpdatesDeleteConfirmationDialog.kt diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt new file mode 100644 index 000000000..3a5362cbc --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt @@ -0,0 +1,111 @@ +package eu.kanade.presentation.updates + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import eu.kanade.presentation.components.TabbedDialog +import eu.kanade.presentation.components.TabbedDialogPaddings +import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel +import kotlinx.collections.immutable.persistentListOf +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.domain.updates.service.UpdatesPreferences +import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.SettingsItemsPaddings +import tachiyomi.presentation.core.components.TriStateItem +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.util.collectAsState + +@Composable +fun UpdatesFilterDialog( + onDismissRequest: () -> Unit, + screenModel: UpdatesSettingsScreenModel, +) { + TabbedDialog( + onDismissRequest = onDismissRequest, + tabTitles = persistentListOf( + stringResource(MR.strings.action_filter), + ), + ) { + Column( + modifier = Modifier + .padding(vertical = TabbedDialogPaddings.Vertical) + .verticalScroll(rememberScrollState()), + ) { + FilterSheet(screenModel = screenModel) + } + } +} + +@Composable +private fun ColumnScope.FilterSheet( + screenModel: UpdatesSettingsScreenModel, +) { + val filterDownloaded by screenModel.updatesPreferences.filterDownloaded().collectAsState() + TriStateItem( + label = stringResource(MR.strings.label_downloaded), + state = filterDownloaded, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) }, + ) + + val filterUnread by screenModel.updatesPreferences.filterUnread().collectAsState() + TriStateItem( + label = stringResource(MR.strings.action_filter_unread), + state = filterUnread, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnread) }, + ) + + val filterStarted by screenModel.updatesPreferences.filterStarted().collectAsState() + TriStateItem( + label = stringResource(MR.strings.label_started), + state = filterStarted, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) }, + ) + + val filterBookmarked by screenModel.updatesPreferences.filterBookmarked().collectAsState() + TriStateItem( + label = stringResource(MR.strings.action_filter_bookmarked), + state = filterBookmarked, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) }, + ) + + HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small)) + + val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators().collectAsState() + + fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators().getAndSet { !it } + + Row( + modifier = Modifier + .clickable { toggleScanlatorFilter() } + .fillMaxWidth() + .padding(horizontal = SettingsItemsPaddings.Horizontal), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(MR.strings.action_filter_excluded_scanlators), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyMedium, + ) + + Switch( + checked = filterExcludedScanlators, + onCheckedChange = { toggleScanlatorFilter() }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index 941096d84..eed1cf975 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -5,9 +5,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CalendarMonth +import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.SelectAll +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.TopAppBarScrollBehavior @@ -37,6 +40,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.presentation.core.theme.active import java.time.LocalDate import kotlin.time.Duration.Companion.seconds @@ -56,6 +60,8 @@ fun UpdateScreen( onMultiDeleteClicked: (List) -> Unit, onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, onOpenChapter: (UpdatesItem) -> Unit, + onFilterClicked: () -> Unit, + hasActiveFilters: Boolean, ) { BackHandler(enabled = state.selectionMode) { onSelectAll(false) @@ -66,6 +72,8 @@ fun UpdateScreen( UpdatesAppBar( onCalendarClicked = { onCalendarClicked() }, onUpdateLibrary = { onUpdateLibrary() }, + onFilterClicked = { onFilterClicked() }, + hasFilters = hasActiveFilters, actionModeCounter = state.selected.size, onSelectAll = { onSelectAll(true) }, onInvertSelection = { onInvertSelection() }, @@ -133,6 +141,8 @@ fun UpdateScreen( private fun UpdatesAppBar( onCalendarClicked: () -> Unit, onUpdateLibrary: () -> Unit, + onFilterClicked: () -> Unit, + hasFilters: Boolean, // For action mode actionModeCounter: Int, onSelectAll: () -> Unit, @@ -147,6 +157,12 @@ private fun UpdatesAppBar( actions = { AppBarActions( persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.action_filter), + icon = Icons.Outlined.FilterList, + iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current, + onClick = onFilterClicked, + ), AppBar.Action( title = stringResource(MR.strings.action_view_upcoming), icon = Icons.Outlined.CalendarMonth, diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt index c5915fb98..7b01ed48e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt @@ -17,6 +17,7 @@ import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.storage.service.StoragePreferences +import tachiyomi.domain.updates.service.UpdatesPreferences import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addSingletonFactory @@ -46,6 +47,9 @@ class PreferenceModule(val app: Application) : InjektModule { addSingletonFactory { LibraryPreferences(get()) } + addSingletonFactory { + UpdatesPreferences(get()) + } addSingletonFactory { ReaderPreferences(get()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index d77786243..7abba5623 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -4,6 +4,7 @@ import android.app.Application import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue +import androidx.compose.ui.util.fastFilter import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.core.preference.asState @@ -27,11 +28,16 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import logcat.LogPriority +import tachiyomi.core.common.preference.TriState import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.core.common.util.system.logcat @@ -40,9 +46,11 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter import tachiyomi.domain.chapter.model.ChapterUpdate import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetManga +import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.updates.interactor.GetUpdates import tachiyomi.domain.updates.model.UpdatesWithRelations +import tachiyomi.domain.updates.service.UpdatesPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.time.ZonedDateTime @@ -57,6 +65,7 @@ class UpdatesScreenModel( private val getManga: GetManga = Injekt.get(), private val getChapter: GetChapter = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), + private val updatesPreferences: UpdatesPreferences = Injekt.get(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), ) : StateScreenModel(State()) { @@ -75,19 +84,35 @@ class UpdatesScreenModel( val limit = ZonedDateTime.now().minusMonths(3).toInstant() combine( - getUpdates.subscribe(limit).distinctUntilChanged(), + // needed for SQL filters (unread, started, bookmarked, etc) + getUpdatesItemPreferenceFlow() + .distinctUntilChanged() + .flatMapLatest { + getUpdates.subscribe( + limit, + unread = it.filterUnread.toBooleanOrNull(), + started = it.filterStarted.toBooleanOrNull(), + bookmarked = it.filterBookmarked.toBooleanOrNull(), + hideExcludedScanlators = it.filterExcludedScanlators, + ).distinctUntilChanged() + }, downloadCache.changes, downloadManager.queueState, - ) { updates, _, _ -> updates } - .catch { - logcat(LogPriority.ERROR, it) - _events.send(Event.InternalError) - } - .collectLatest { updates -> + // needed for Kotlin filters (downloaded) + getUpdatesItemPreferenceFlow().distinctUntilChanged { old, new -> + old.filterDownloaded == new.filterDownloaded + }, + ) { updates, _, _, itemPreferences -> + updates + .toUpdateItems() + .applyFilters(itemPreferences) + .toPersistentList() + } + .collectLatest { updateItems -> mutableState.update { it.copy( isLoading = false, - items = updates.toUpdateItems(), + items = updateItems, ) } } @@ -98,9 +123,43 @@ class UpdatesScreenModel( .catch { logcat(LogPriority.ERROR, it) } .collect(this@UpdatesScreenModel::updateDownloadState) } + + getUpdatesItemPreferenceFlow() + .map { prefs -> + listOf( + prefs.filterUnread, + prefs.filterDownloaded, + prefs.filterStarted, + prefs.filterBookmarked, + ) + .any { it != TriState.DISABLED } + } + .distinctUntilChanged() + .onEach { + mutableState.update { state -> + state.copy(hasActiveFilters = it) + } + } + .launchIn(screenModelScope) } - private fun List.toUpdateItems(): PersistentList { + private fun List.applyFilters( + preferences: ItemPreferences, + ): List { + val filterDownloaded = preferences.filterDownloaded + + val filterFnDownloaded: (UpdatesItem) -> Boolean = { + applyFilter(filterDownloaded) { + it.downloadStateProvider() == Download.State.DOWNLOADED + } + } + + return fastFilter { + filterFnDownloaded(it) + } + } + + private fun List.toUpdateItems(): List { return this .map { update -> val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId) @@ -123,7 +182,6 @@ class UpdatesScreenModel( selected = update.chapterId in selectedChapterIds, ) } - .toPersistentList() } fun updateLibrary(): Boolean { @@ -361,9 +419,41 @@ class UpdatesScreenModel( libraryPreferences.newUpdatesCount().set(0) } + private fun getUpdatesItemPreferenceFlow(): Flow { + return combine( + updatesPreferences.filterDownloaded().changes(), + updatesPreferences.filterUnread().changes(), + updatesPreferences.filterStarted().changes(), + updatesPreferences.filterBookmarked().changes(), + updatesPreferences.filterExcludedScanlators().changes(), + ) { downloaded, unread, started, bookmarked, excludedScanlators -> + ItemPreferences( + filterDownloaded = downloaded, + filterUnread = unread, + filterStarted = started, + filterBookmarked = bookmarked, + filterExcludedScanlators = excludedScanlators, + ) + } + } + + fun showFilterDialog() { + mutableState.update { it.copy(dialog = Dialog.FilterSheet) } + } + + @Immutable + private data class ItemPreferences( + val filterDownloaded: TriState, + val filterUnread: TriState, + val filterStarted: TriState, + val filterBookmarked: TriState, + val filterExcludedScanlators: Boolean, + ) + @Immutable data class State( val isLoading: Boolean = true, + val hasActiveFilters: Boolean = false, val items: PersistentList = persistentListOf(), val dialog: Dialog? = null, ) { @@ -387,6 +477,7 @@ class UpdatesScreenModel( sealed interface Dialog { data class DeleteConfirmation(val toDelete: List) : Dialog + data object FilterSheet : Dialog } sealed interface Event { @@ -395,6 +486,14 @@ class UpdatesScreenModel( } } +private fun TriState.toBooleanOrNull(): Boolean? { + return when (this) { + TriState.DISABLED -> null + TriState.ENABLED_IS -> true + TriState.ENABLED_NOT -> false + } +} + @Immutable data class UpdatesItem( val update: UpdatesWithRelations, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt new file mode 100644 index 000000000..1e909d484 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.ui.updates + +import cafe.adriel.voyager.core.model.ScreenModel +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.TriState +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.domain.updates.service.UpdatesPreferences +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class UpdatesSettingsScreenModel( + val updatesPreferences: UpdatesPreferences = Injekt.get(), +) : ScreenModel { + + fun toggleFilter(preference: (UpdatesPreferences) -> Preference) { + preference(updatesPreferences).getAndSet { + it.next() + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt index 7233ca818..2d99959d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt @@ -17,6 +17,7 @@ import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.presentation.updates.UpdateScreen import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog +import eu.kanade.presentation.updates.UpdatesFilterDialog import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen @@ -54,6 +55,7 @@ data object UpdatesTab : Tab { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow val screenModel = rememberScreenModel { UpdatesScreenModel() } + val settingsScreenModel = rememberScreenModel { UpdatesSettingsScreenModel() } val state by screenModel.state.collectAsState() UpdateScreen( @@ -74,6 +76,8 @@ data object UpdatesTab : Tab { context.startActivity(intent) }, onCalendarClicked = { navigator.push(UpcomingScreen()) }, + onFilterClicked = screenModel::showFilterDialog, + hasActiveFilters = state.hasActiveFilters, ) val onDismissDialog = { screenModel.setDialog(null) } @@ -84,6 +88,12 @@ data object UpdatesTab : Tab { onConfirm = { screenModel.deleteChapters(dialog.toDelete) }, ) } + is UpdatesScreenModel.Dialog.FilterSheet -> { + UpdatesFilterDialog( + onDismissRequest = onDismissDialog, + screenModel = settingsScreenModel, + ) + } null -> {} } diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index c7bbe7c8b..dc6453919 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -1,6 +1,7 @@ package tachiyomi.data.updates import kotlinx.coroutines.flow.Flow +import tachiyomi.core.common.util.lang.toLong import tachiyomi.data.DatabaseHandler import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.updates.model.UpdatesWithRelations @@ -25,9 +26,25 @@ class UpdatesRepositoryImpl( } } - override fun subscribeAll(after: Long, limit: Long): Flow> { + override fun subscribeAll( + after: Long, + limit: Long, + unread: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> { 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, + ) } } @@ -62,6 +79,7 @@ class UpdatesRepositoryImpl( coverLastModified: Long, dateUpload: Long, dateFetch: Long, + excludedScanlator: String?, ): UpdatesWithRelations = UpdatesWithRelations( mangaId = mangaId, mangaTitle = mangaTitle, diff --git a/data/src/main/sqldelight/tachiyomi/migrations/9.sqm b/data/src/main/sqldelight/tachiyomi/migrations/9.sqm new file mode 100644 index 000000000..526e4cf6c --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/9.sqm @@ -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; diff --git a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq index f18c55401..cfe904cf1 100644 --- a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq @@ -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 diff --git a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt index 361505642..9db97154f 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt @@ -13,8 +13,21 @@ class GetUpdates( return repository.awaitWithRead(read, after, limit = 500) } - fun subscribe(instant: Instant): Flow> { - return repository.subscribeAll(instant.toEpochMilli(), limit = 500) + fun subscribe( + instant: Instant, + unread: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> { + return repository.subscribeAll( + instant.toEpochMilli(), + limit = 500, + unread = unread, + started = started, + bookmarked = bookmarked, + hideExcludedScanlators = hideExcludedScanlators, + ) } fun subscribe(read: Boolean, after: Long): Flow> { diff --git a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt index 3b583ea90..ca738f92d 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt @@ -7,7 +7,14 @@ interface UpdatesRepository { suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List - fun subscribeAll(after: Long, limit: Long): Flow> + fun subscribeAll( + after: Long, + limit: Long, + unread: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow> } diff --git a/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt b/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt new file mode 100644 index 000000000..fa86de29b --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt @@ -0,0 +1,35 @@ +package tachiyomi.domain.updates.service + +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.TriState +import tachiyomi.core.common.preference.getEnum + +class UpdatesPreferences( + private val preferenceStore: PreferenceStore, +) { + + fun filterDownloaded() = preferenceStore.getEnum( + "pref_filter_updates_downloaded", + TriState.DISABLED, + ) + + fun filterUnread() = preferenceStore.getEnum( + "pref_filter_updates_unread", + TriState.DISABLED, + ) + + fun filterStarted() = preferenceStore.getEnum( + "pref_filter_updates_started", + TriState.DISABLED, + ) + + fun filterBookmarked() = preferenceStore.getEnum( + "pref_filter_updates_bookmarked", + TriState.DISABLED, + ) + + fun filterExcludedScanlators() = preferenceStore.getBoolean( + "pref_filter_updates_hide_excluded_scanlators", + false, + ) +} diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 9b5718ee8..ec6847517 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -856,6 +856,7 @@ Just now Never View Upcoming Updates + Filter excluded scanlators Upcoming Guide