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
@@ -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() },
)
}
}
@@ -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
@@ -59,6 +63,8 @@ fun UpdateScreen(
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onOpenChapter: (UpdatesItem) -> Unit,
onFilterClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
BackHandler(enabled = state.selectionMode) {
onSelectAll(false)
@@ -69,6 +75,8 @@ fun UpdateScreen(
UpdatesAppBar(
onCalendarClicked = { onCalendarClicked() },
onUpdateLibrary = { onUpdateLibrary() },
onFilterClicked = { onFilterClicked() },
hasFilters = hasActiveFilters,
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
onInvertSelection = { onInvertSelection() },
@@ -139,6 +147,8 @@ fun UpdateScreen(
private fun UpdatesAppBar(
onCalendarClicked: () -> Unit,
onUpdateLibrary: () -> Unit,
onFilterClicked: () -> Unit,
hasFilters: Boolean,
// For action mode
actionModeCounter: Int,
onSelectAll: () -> Unit,
@@ -153,6 +163,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,