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:
@@ -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.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.CalendarMonth
|
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.FlipToBack
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
import androidx.compose.material.icons.outlined.SelectAll
|
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.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
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.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
import tachiyomi.presentation.core.theme.active
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@@ -59,6 +63,8 @@ fun UpdateScreen(
|
|||||||
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
|
||||||
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
|
||||||
onOpenChapter: (UpdatesItem) -> Unit,
|
onOpenChapter: (UpdatesItem) -> Unit,
|
||||||
|
onFilterClicked: () -> Unit,
|
||||||
|
hasActiveFilters: Boolean,
|
||||||
) {
|
) {
|
||||||
BackHandler(enabled = state.selectionMode) {
|
BackHandler(enabled = state.selectionMode) {
|
||||||
onSelectAll(false)
|
onSelectAll(false)
|
||||||
@@ -69,6 +75,8 @@ fun UpdateScreen(
|
|||||||
UpdatesAppBar(
|
UpdatesAppBar(
|
||||||
onCalendarClicked = { onCalendarClicked() },
|
onCalendarClicked = { onCalendarClicked() },
|
||||||
onUpdateLibrary = { onUpdateLibrary() },
|
onUpdateLibrary = { onUpdateLibrary() },
|
||||||
|
onFilterClicked = { onFilterClicked() },
|
||||||
|
hasFilters = hasActiveFilters,
|
||||||
actionModeCounter = state.selected.size,
|
actionModeCounter = state.selected.size,
|
||||||
onSelectAll = { onSelectAll(true) },
|
onSelectAll = { onSelectAll(true) },
|
||||||
onInvertSelection = { onInvertSelection() },
|
onInvertSelection = { onInvertSelection() },
|
||||||
@@ -139,6 +147,8 @@ fun UpdateScreen(
|
|||||||
private fun UpdatesAppBar(
|
private fun UpdatesAppBar(
|
||||||
onCalendarClicked: () -> Unit,
|
onCalendarClicked: () -> Unit,
|
||||||
onUpdateLibrary: () -> Unit,
|
onUpdateLibrary: () -> Unit,
|
||||||
|
onFilterClicked: () -> Unit,
|
||||||
|
hasFilters: Boolean,
|
||||||
// For action mode
|
// For action mode
|
||||||
actionModeCounter: Int,
|
actionModeCounter: Int,
|
||||||
onSelectAll: () -> Unit,
|
onSelectAll: () -> Unit,
|
||||||
@@ -153,6 +163,12 @@ private fun UpdatesAppBar(
|
|||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
persistentListOf(
|
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(
|
AppBar.Action(
|
||||||
title = stringResource(MR.strings.action_view_upcoming),
|
title = stringResource(MR.strings.action_view_upcoming),
|
||||||
icon = Icons.Outlined.CalendarMonth,
|
icon = Icons.Outlined.CalendarMonth,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import tachiyomi.domain.backup.service.BackupPreferences
|
|||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.storage.service.StoragePreferences
|
import tachiyomi.domain.storage.service.StoragePreferences
|
||||||
|
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||||
import uy.kohesive.injekt.api.InjektRegistrar
|
import uy.kohesive.injekt.api.InjektRegistrar
|
||||||
|
|
||||||
class PreferenceModule(val app: Application) : InjektModule {
|
class PreferenceModule(val app: Application) : InjektModule {
|
||||||
@@ -44,6 +45,9 @@ class PreferenceModule(val app: Application) : InjektModule {
|
|||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
LibraryPreferences(get())
|
LibraryPreferences(get())
|
||||||
}
|
}
|
||||||
|
addSingletonFactory {
|
||||||
|
UpdatesPreferences(get())
|
||||||
|
}
|
||||||
addSingletonFactory {
|
addSingletonFactory {
|
||||||
ReaderPreferences(get())
|
ReaderPreferences(get())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.app.Application
|
|||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.util.fastFilter
|
||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import eu.kanade.core.preference.asState
|
import eu.kanade.core.preference.asState
|
||||||
@@ -30,11 +31,16 @@ import kotlinx.coroutines.flow.catch
|
|||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
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.merge
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
|
import tachiyomi.core.common.preference.TriState
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
@@ -43,9 +49,11 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter
|
|||||||
import tachiyomi.domain.chapter.model.ChapterUpdate
|
import tachiyomi.domain.chapter.model.ChapterUpdate
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.domain.manga.interactor.GetManga
|
import tachiyomi.domain.manga.interactor.GetManga
|
||||||
|
import tachiyomi.domain.manga.model.applyFilter
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
import tachiyomi.domain.updates.interactor.GetUpdates
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||||
|
import tachiyomi.domain.updates.service.UpdatesPreferences
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
@@ -60,6 +68,7 @@ class UpdatesScreenModel(
|
|||||||
private val getManga: GetManga = Injekt.get(),
|
private val getManga: GetManga = Injekt.get(),
|
||||||
private val getChapter: GetChapter = Injekt.get(),
|
private val getChapter: GetChapter = Injekt.get(),
|
||||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||||
|
private val updatesPreferences: UpdatesPreferences = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||||
// SY -->
|
// SY -->
|
||||||
readerPreferences: ReaderPreferences = Injekt.get(),
|
readerPreferences: ReaderPreferences = Injekt.get(),
|
||||||
@@ -85,19 +94,35 @@ class UpdatesScreenModel(
|
|||||||
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
||||||
|
|
||||||
combine(
|
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,
|
downloadCache.changes,
|
||||||
downloadManager.queueState,
|
downloadManager.queueState,
|
||||||
) { updates, _, _ -> updates }
|
// needed for Kotlin filters (downloaded)
|
||||||
.catch {
|
getUpdatesItemPreferenceFlow().distinctUntilChanged { old, new ->
|
||||||
logcat(LogPriority.ERROR, it)
|
old.filterDownloaded == new.filterDownloaded
|
||||||
_events.send(Event.InternalError)
|
},
|
||||||
|
) { updates, _, _, itemPreferences ->
|
||||||
|
updates
|
||||||
|
.toUpdateItems()
|
||||||
|
.applyFilters(itemPreferences)
|
||||||
|
.toPersistentList()
|
||||||
}
|
}
|
||||||
.collectLatest { updates ->
|
.collectLatest { updateItems ->
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
items = updates.toUpdateItems(),
|
items = updateItems,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,9 +133,43 @@ class UpdatesScreenModel(
|
|||||||
.catch { logcat(LogPriority.ERROR, it) }
|
.catch { logcat(LogPriority.ERROR, it) }
|
||||||
.collect(this@UpdatesScreenModel::updateDownloadState)
|
.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<UpdatesWithRelations>.toUpdateItems(): PersistentList<UpdatesItem> {
|
private fun List<UpdatesItem>.applyFilters(
|
||||||
|
preferences: ItemPreferences,
|
||||||
|
): List<UpdatesItem> {
|
||||||
|
val filterDownloaded = preferences.filterDownloaded
|
||||||
|
|
||||||
|
val filterFnDownloaded: (UpdatesItem) -> Boolean = {
|
||||||
|
applyFilter(filterDownloaded) {
|
||||||
|
it.downloadStateProvider() == Download.State.DOWNLOADED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fastFilter {
|
||||||
|
filterFnDownloaded(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<UpdatesWithRelations>.toUpdateItems(): List<UpdatesItem> {
|
||||||
return this
|
return this
|
||||||
.map { update ->
|
.map { update ->
|
||||||
val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId)
|
val activeDownload = downloadManager.getQueuedDownloadOrNull(update.chapterId)
|
||||||
@@ -135,7 +194,6 @@ class UpdatesScreenModel(
|
|||||||
selected = update.chapterId in selectedChapterIds,
|
selected = update.chapterId in selectedChapterIds,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.toPersistentList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLibrary(): Boolean {
|
fun updateLibrary(): Boolean {
|
||||||
@@ -373,9 +431,41 @@ class UpdatesScreenModel(
|
|||||||
libraryPreferences.newUpdatesCount().set(0)
|
libraryPreferences.newUpdatesCount().set(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getUpdatesItemPreferenceFlow(): Flow<ItemPreferences> {
|
||||||
|
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
|
@Immutable
|
||||||
data class State(
|
data class State(
|
||||||
val isLoading: Boolean = true,
|
val isLoading: Boolean = true,
|
||||||
|
val hasActiveFilters: Boolean = false,
|
||||||
val items: PersistentList<UpdatesItem> = persistentListOf(),
|
val items: PersistentList<UpdatesItem> = persistentListOf(),
|
||||||
val dialog: Dialog? = null,
|
val dialog: Dialog? = null,
|
||||||
) {
|
) {
|
||||||
@@ -399,6 +489,7 @@ class UpdatesScreenModel(
|
|||||||
|
|
||||||
sealed interface Dialog {
|
sealed interface Dialog {
|
||||||
data class DeleteConfirmation(val toDelete: List<UpdatesItem>) : Dialog
|
data class DeleteConfirmation(val toDelete: List<UpdatesItem>) : Dialog
|
||||||
|
data object FilterSheet : Dialog
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Event {
|
sealed interface Event {
|
||||||
@@ -407,6 +498,14 @@ class UpdatesScreenModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun TriState.toBooleanOrNull(): Boolean? {
|
||||||
|
return when (this) {
|
||||||
|
TriState.DISABLED -> null
|
||||||
|
TriState.ENABLED_IS -> true
|
||||||
|
TriState.ENABLED_NOT -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class UpdatesItem(
|
data class UpdatesItem(
|
||||||
val update: UpdatesWithRelations,
|
val update: UpdatesWithRelations,
|
||||||
|
|||||||
@@ -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<TriState>) {
|
||||||
|
preference(updatesPreferences).getAndSet {
|
||||||
|
it.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ import eu.kanade.core.preference.asState
|
|||||||
import eu.kanade.domain.ui.UiPreferences
|
import eu.kanade.domain.ui.UiPreferences
|
||||||
import eu.kanade.presentation.updates.UpdateScreen
|
import eu.kanade.presentation.updates.UpdateScreen
|
||||||
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog
|
||||||
|
import eu.kanade.presentation.updates.UpdatesFilterDialog
|
||||||
import eu.kanade.presentation.util.Tab
|
import eu.kanade.presentation.util.Tab
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
|
||||||
@@ -70,6 +71,7 @@ data object UpdatesTab : Tab {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
val screenModel = rememberScreenModel { UpdatesScreenModel() }
|
||||||
|
val settingsScreenModel = rememberScreenModel { UpdatesSettingsScreenModel() }
|
||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
UpdateScreen(
|
UpdateScreen(
|
||||||
@@ -93,6 +95,8 @@ data object UpdatesTab : Tab {
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
onCalendarClicked = { navigator.push(UpcomingScreen()) },
|
onCalendarClicked = { navigator.push(UpcomingScreen()) },
|
||||||
|
onFilterClicked = screenModel::showFilterDialog,
|
||||||
|
hasActiveFilters = state.hasActiveFilters,
|
||||||
)
|
)
|
||||||
|
|
||||||
val onDismissDialog = { screenModel.setDialog(null) }
|
val onDismissDialog = { screenModel.setDialog(null) }
|
||||||
@@ -103,6 +107,12 @@ data object UpdatesTab : Tab {
|
|||||||
onConfirm = { screenModel.deleteChapters(dialog.toDelete) },
|
onConfirm = { screenModel.deleteChapters(dialog.toDelete) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is UpdatesScreenModel.Dialog.FilterSheet -> {
|
||||||
|
UpdatesFilterDialog(
|
||||||
|
onDismissRequest = onDismissDialog,
|
||||||
|
screenModel = settingsScreenModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
null -> {}
|
null -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,21 @@ class AndroidDatabaseHandler(
|
|||||||
// SY -->
|
// SY -->
|
||||||
fun getLibraryQuery(condition: String = "M.favorite = 1") = LibraryQuery(driver, condition)
|
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 <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,26 @@ private val mapper = { cursor: SqlCursor ->
|
|||||||
cursor.getLong(12)!!,
|
cursor.getLong(12)!!,
|
||||||
cursor.getLong(13)!!,
|
cursor.getLong(13)!!,
|
||||||
cursor.getLong(14)!!,
|
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> {
|
override fun <R> execute(mapper: (SqlCursor) -> QueryResult<R>): QueryResult<R> {
|
||||||
return driver.executeQuery(
|
return driver.executeQuery(
|
||||||
null,
|
null,
|
||||||
"""
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM (
|
||||||
|
-- Normal source
|
||||||
SELECT
|
SELECT
|
||||||
mangas._id AS mangaId,
|
mangas._id AS mangaId,
|
||||||
mangas.title AS mangaTitle,
|
mangas.title AS mangaTitle,
|
||||||
@@ -47,13 +59,20 @@ class UpdatesQuery(val driver: SqlDriver, val after: Long, val limit: Long) : Ex
|
|||||||
mangas.thumbnail_url AS thumbnailUrl,
|
mangas.thumbnail_url AS thumbnailUrl,
|
||||||
mangas.cover_last_modified AS coverLastModified,
|
mangas.cover_last_modified AS coverLastModified,
|
||||||
chapters.date_upload AS dateUpload,
|
chapters.date_upload AS dateUpload,
|
||||||
chapters.date_fetch AS datefetch
|
chapters.date_fetch AS datefetch,
|
||||||
FROM mangas JOIN chapters
|
excluded_scanlators.scanlator AS excludedScanlator
|
||||||
|
FROM mangas
|
||||||
|
JOIN chapters
|
||||||
ON mangas._id = chapters.manga_id
|
ON mangas._id = chapters.manga_id
|
||||||
WHERE favorite = 1 AND source <> $MERGED_SOURCE_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
|
AND date_fetch > date_added
|
||||||
AND dateUpload > :after
|
|
||||||
UNION
|
UNION ALL
|
||||||
|
|
||||||
|
-- Merged source
|
||||||
SELECT
|
SELECT
|
||||||
mangas._id AS mangaId,
|
mangas._id AS mangaId,
|
||||||
mangas.title AS mangaTitle,
|
mangas.title AS mangaTitle,
|
||||||
@@ -69,27 +88,49 @@ class UpdatesQuery(val driver: SqlDriver, val after: Long, val limit: Long) : Ex
|
|||||||
mangas.thumbnail_url AS thumbnailUrl,
|
mangas.thumbnail_url AS thumbnailUrl,
|
||||||
mangas.cover_last_modified AS coverLastModified,
|
mangas.cover_last_modified AS coverLastModified,
|
||||||
chapters.date_upload AS dateUpload,
|
chapters.date_upload AS dateUpload,
|
||||||
chapters.date_fetch AS datefetch
|
chapters.date_fetch AS datefetch,
|
||||||
|
excluded_scanlators.scanlator AS excludedScanlator
|
||||||
FROM mangas
|
FROM mangas
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT merged.manga_id,merged.merge_id
|
SELECT merged.manga_id, merged.merge_id
|
||||||
FROM merged
|
FROM merged
|
||||||
GROUP BY merged.merge_id
|
GROUP BY merged.merge_id
|
||||||
) as ME
|
) AS ME
|
||||||
ON ME.merge_id = mangas._id
|
ON ME.merge_id = mangas._id
|
||||||
JOIN chapters
|
JOIN chapters
|
||||||
ON ME.manga_id = chapters.manga_id
|
ON ME.manga_id = chapters.manga_id
|
||||||
WHERE favorite = 1 AND source = $MERGED_SOURCE_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
|
AND date_fetch > date_added
|
||||||
|
) AS combined
|
||||||
|
WHERE
|
||||||
|
favorite = 1
|
||||||
AND dateUpload > :after
|
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
|
ORDER BY datefetch DESC
|
||||||
LIMIT :limit;
|
LIMIT :limit;
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
mapper,
|
mapper,
|
||||||
2,
|
6,
|
||||||
binders = {
|
binders = {
|
||||||
bindLong(0, after)
|
var parameterIndex = 0
|
||||||
bindLong(1, limit)
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import tachiyomi.core.common.util.lang.toLong
|
||||||
import tachiyomi.data.AndroidDatabaseHandler
|
import tachiyomi.data.AndroidDatabaseHandler
|
||||||
import tachiyomi.data.DatabaseHandler
|
import tachiyomi.data.DatabaseHandler
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
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 {
|
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 {
|
}.map {
|
||||||
databaseHandler.awaitListExecutable {
|
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)
|
.map(::mapUpdatesView)
|
||||||
}
|
}
|
||||||
@@ -70,6 +95,7 @@ class UpdatesRepositoryImpl(
|
|||||||
coverLastModified: Long,
|
coverLastModified: Long,
|
||||||
dateUpload: Long,
|
dateUpload: Long,
|
||||||
dateFetch: Long,
|
dateFetch: Long,
|
||||||
|
excludedScanlator: String?,
|
||||||
): UpdatesWithRelations = UpdatesWithRelations(
|
): UpdatesWithRelations = UpdatesWithRelations(
|
||||||
mangaId = mangaId,
|
mangaId = mangaId,
|
||||||
// SY -->
|
// 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.thumbnail_url AS thumbnailUrl,
|
||||||
mangas.cover_last_modified AS coverLastModified,
|
mangas.cover_last_modified AS coverLastModified,
|
||||||
chapters.date_upload AS dateUpload,
|
chapters.date_upload AS dateUpload,
|
||||||
chapters.date_fetch AS datefetch
|
chapters.date_fetch AS datefetch,
|
||||||
|
excluded_scanlators.scanlator AS excludedScanlator
|
||||||
FROM mangas JOIN chapters
|
FROM mangas JOIN chapters
|
||||||
ON mangas._id = chapters.manga_id
|
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
|
WHERE favorite = 1
|
||||||
AND date_fetch > date_added
|
AND date_fetch > date_added
|
||||||
ORDER BY date_fetch DESC;
|
ORDER BY date_fetch DESC;
|
||||||
@@ -27,6 +31,23 @@ FROM updatesView
|
|||||||
WHERE dateUpload > :after
|
WHERE dateUpload > :after
|
||||||
LIMIT :limit;
|
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:
|
getUpdatesByReadStatus:
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM updatesView
|
FROM updatesView
|
||||||
|
|||||||
@@ -24,8 +24,21 @@ class GetUpdates(
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subscribe(instant: Instant): Flow<List<UpdatesWithRelations>> {
|
fun subscribe(
|
||||||
return repository.subscribeAll(instant.toEpochMilli(), limit = 500)
|
instant: Instant,
|
||||||
|
unread: Boolean?,
|
||||||
|
started: Boolean?,
|
||||||
|
bookmarked: Boolean?,
|
||||||
|
hideExcludedScanlators: Boolean,
|
||||||
|
): Flow<List<UpdatesWithRelations>> {
|
||||||
|
return repository.subscribeAll(
|
||||||
|
instant.toEpochMilli(),
|
||||||
|
limit = 500,
|
||||||
|
unread = unread,
|
||||||
|
started = started,
|
||||||
|
bookmarked = bookmarked,
|
||||||
|
hideExcludedScanlators = hideExcludedScanlators,
|
||||||
|
)
|
||||||
// SY -->
|
// SY -->
|
||||||
.catchNPE()
|
.catchNPE()
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|||||||
@@ -7,7 +7,14 @@ interface UpdatesRepository {
|
|||||||
|
|
||||||
suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List<UpdatesWithRelations>
|
suspend fun awaitWithRead(read: Boolean, after: Long, limit: Long): List<UpdatesWithRelations>
|
||||||
|
|
||||||
fun subscribeAll(after: Long, limit: Long): Flow<List<UpdatesWithRelations>>
|
fun subscribeAll(
|
||||||
|
after: Long,
|
||||||
|
limit: Long,
|
||||||
|
unread: Boolean?,
|
||||||
|
started: Boolean?,
|
||||||
|
bookmarked: Boolean?,
|
||||||
|
hideExcludedScanlators: Boolean,
|
||||||
|
): Flow<List<UpdatesWithRelations>>
|
||||||
|
|
||||||
fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow<List<UpdatesWithRelations>>
|
fun subscribeWithRead(read: Boolean, after: Long, limit: Long): Flow<List<UpdatesWithRelations>>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -855,6 +855,7 @@
|
|||||||
<string name="updates_last_update_info_just_now">Just now</string>
|
<string name="updates_last_update_info_just_now">Just now</string>
|
||||||
<string name="relative_time_span_never">Never</string>
|
<string name="relative_time_span_never">Never</string>
|
||||||
<string name="action_view_upcoming">View Upcoming Updates</string>
|
<string name="action_view_upcoming">View Upcoming Updates</string>
|
||||||
|
<string name="action_filter_excluded_scanlators">Filter excluded scanlators</string>
|
||||||
|
|
||||||
<!-- Upcoming -->
|
<!-- Upcoming -->
|
||||||
<string name="upcoming_guide">Upcoming Guide</string>
|
<string name="upcoming_guide">Upcoming Guide</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user