Implement scanlator filter (#8803)

* Implement scanlator filter

* Visual improvement to scanlator filter dialog

* Review changes + Bug fixes

Backup not containing filtered chapters and similar issue fix

* Review Changes + Fix SQL query

* Lint mamma mia

(cherry picked from commit b97aa235480e35b5514b7b1489b9d4413cea66d9)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/presentation/manga/ChapterSettingsDialog.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
#	data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt
#	data/src/main/sqldelight/tachiyomi/migrations/23.sqm
#	data/src/main/sqldelight/tachiyomi/migrations/26.sqm
#	domain/src/main/java/tachiyomi/domain/history/interactor/GetNextChapters.kt
This commit is contained in:
AntsyLich
2023-11-05 21:34:35 +06:00
committed by Jobobby04
parent 94fa45597d
commit c8909961c0
48 changed files with 503 additions and 237 deletions
@@ -1,11 +1,14 @@
package eu.kanade.domain
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetEnabledSources
@@ -112,6 +115,8 @@ class DomainModule : InjektModule {
addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get(), get()) }
addFactory { SetMangaCategories(get()) }
addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) }
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get(), get()) }
@@ -133,7 +138,8 @@ class DomainModule : InjektModule {
addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
addFactory { GetAvailableScanlators(get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) }
@@ -52,7 +52,6 @@ import tachiyomi.domain.manga.interactor.InsertFavoriteEntryAlternative
import tachiyomi.domain.manga.interactor.InsertFlatMetadata
import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.interactor.SetMangaFilteredScanlators
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
import tachiyomi.domain.manga.repository.CustomMangaRepository
import tachiyomi.domain.manga.repository.FavoritesEntryRepository
@@ -88,7 +87,6 @@ class SYDomainModule : InjektModule {
addFactory { GetShowLatest(get()) }
addFactory { ToggleExcludeFromDataSaver(get()) }
addFactory { SetSourceCategories(get()) }
addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetAllManga(get()) }
addFactory { GetMangaBySource(get()) }
addFactory { DeleteChapters(get()) }
@@ -0,0 +1,24 @@
package eu.kanade.domain.chapter.interactor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.domain.chapter.repository.ChapterRepository
class GetAvailableScanlators(
private val repository: ChapterRepository,
) {
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
return mapNotNull { it.ifBlank { null } }.toSet()
}
suspend fun await(mangaId: Long): Set<String> {
return repository.getScanlatorsByMangaId(mangaId)
.cleanupAvailableScanlators()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
.map { it.cleanupAvailableScanlators() }
}
}
@@ -2,6 +2,7 @@ package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.copyFromSChapter
import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.download.DownloadManager
@@ -34,6 +35,7 @@ class SyncChaptersWithSource(
private val updateManga: UpdateManga,
private val updateChapter: UpdateChapter,
private val getChaptersByMangaId: GetChaptersByMangaId,
private val getExcludedScanlators: GetExcludedScanlators,
) {
/**
@@ -234,6 +236,10 @@ class SyncChaptersWithSource(
val reAddedUrls = reAdded.map { it.url }.toHashSet()
return updatedToAdd.filterNot { it.url in reAddedUrls }
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
return updatedToAdd.filterNot {
it.url in reAddedUrls || it.scanlator in excludedScanlators
}
}
}
@@ -3,7 +3,6 @@ package eu.kanade.domain.chapter.model
import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterList
import exh.md.utils.MdUtil
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.manga.model.Manga
@@ -16,7 +15,7 @@ import tachiyomi.source.local.isLocal
*/
fun List<Chapter>.applyFilters(
manga: Manga,
downloadManager: DownloadManager/* SY --> */,
downloadManager: DownloadManager,/* SY --> */
mergedManga: Map<Long, Manga>, /* SY <-- */
): List<Chapter> {
val isLocalManga = manga.isLocal()
@@ -41,14 +40,6 @@ fun List<Chapter>.applyFilters(
downloaded || isLocalManga
}
}
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() ||
MdUtil.getScanlators(chapter.scanlator).any { group ->
manga.filteredScanlators!!.contains(group)
}
}
// SY <--
.sortedWith(getChapterSort(manga))
}
@@ -65,13 +56,5 @@ fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } }
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() ||
MdUtil.getScanlators(chapter.chapter.scanlator).any { group ->
manga.filteredScanlators!!.contains(group)
}
}
// SY <--
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
}
@@ -0,0 +1,24 @@
package eu.kanade.domain.manga.interactor
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
class GetExcludedScanlators(
private val handler: DatabaseHandler,
) {
suspend fun await(mangaId: Long): Set<String> {
return handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
.toSet()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return handler.subscribeToList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
.map { it.toSet() }
}
}
@@ -0,0 +1,22 @@
package eu.kanade.domain.manga.interactor
import tachiyomi.data.DatabaseHandler
class SetExcludedScanlators(
private val handler: DatabaseHandler,
) {
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
handler.await(inTransaction = true) {
val currentExcluded = handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}.toSet()
val toAdd = excludedScanlators.minus(currentExcluded)
for (scanlator in toAdd) {
excluded_scanlatorsQueries.insert(mangaId, scanlator)
}
val toRemove = currentExcluded.minus(excludedScanlators)
excluded_scanlatorsQueries.remove(mangaId, toRemove)
}
}
}
@@ -33,10 +33,7 @@ val Manga.downloadedFilter: TriState
fun Manga.chaptersFiltered(): Boolean {
return unreadFilter != TriState.DISABLED ||
downloadedFilter != TriState.DISABLED ||
bookmarkedFilter != TriState.DISABLED ||
// SY -->
!filteredScanlators.isNullOrEmpty()
// SY <--
bookmarkedFilter != TriState.DISABLED
}
fun Manga.forceDownloaded(): Boolean {
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
@@ -14,6 +14,7 @@ import androidx.compose.material.icons.outlined.PeopleAlt
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -37,6 +38,7 @@ import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.RadioItem
import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.theme.active
@Composable
fun ChapterSettingsDialog(
@@ -45,13 +47,12 @@ fun ChapterSettingsDialog(
onDownloadFilterChanged: (TriState) -> Unit,
onUnreadFilterChanged: (TriState) -> Unit,
onBookmarkedFilterChanged: (TriState) -> Unit,
scanlatorFilterActive: Boolean,
onScanlatorFilterClicked: (() -> Unit),
onSortModeChanged: (Long) -> Unit,
onDisplayModeChanged: (Long) -> Unit,
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
onResetToDefault: () -> Unit,
// SY -->
onClickShowScanlatorSelection: (() -> Unit)?,
// SY <--
) {
var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) }
if (showSetAsDefaultDialog) {
@@ -94,14 +95,14 @@ fun ChapterSettingsDialog(
0 -> {
FilterPage(
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true },
onDownloadFilterChanged = onDownloadFilterChanged
.takeUnless { manga?.forceDownloaded() == true },
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
onUnreadFilterChanged = onUnreadFilterChanged,
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
onBookmarkedFilterChanged = onBookmarkedFilterChanged,
// SY -->
onClickShowScanlatorSelection = onClickShowScanlatorSelection,
// SY <--
scanlatorFilterActive = scanlatorFilterActive,
onScanlatorFilterClicked = onScanlatorFilterClicked,
)
}
1 -> {
@@ -130,9 +131,8 @@ private fun ColumnScope.FilterPage(
onUnreadFilterChanged: (TriState) -> Unit,
bookmarkedFilter: TriState,
onBookmarkedFilterChanged: (TriState) -> Unit,
// SY -->
onClickShowScanlatorSelection: (() -> Unit)?,
// SY <--
scanlatorFilterActive: Boolean,
onScanlatorFilterClicked: (() -> Unit),
) {
TriStateItem(
label = stringResource(R.string.label_downloaded),
@@ -149,23 +149,20 @@ private fun ColumnScope.FilterPage(
state = bookmarkedFilter,
onClick = onBookmarkedFilterChanged,
)
// SY -->
if (onClickShowScanlatorSelection != null) {
SetScanlatorsItem(onClickShowScanlatorSelection)
}
// SY <--
ScanlatorFilterItem(
active = scanlatorFilterActive,
onClick = onScanlatorFilterClicked,
)
}
// SY -->
@Composable
private fun SetScanlatorsItem(
onClickShowScanlatorSelection: () -> Unit,
fun ScanlatorFilterItem(
active: Boolean,
onClick: () -> Unit,
) {
Row(
modifier = Modifier
.clickable(
onClick = onClickShowScanlatorSelection,
)
.clickable(onClick = onClick)
.fillMaxWidth()
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -174,6 +171,11 @@ private fun SetScanlatorsItem(
Icon(
imageVector = Icons.Outlined.PeopleAlt,
contentDescription = null,
tint = if (active) {
MaterialTheme.colorScheme.active
} else {
LocalContentColor.current
},
)
Text(
text = stringResource(R.string.scanlator),
@@ -181,7 +183,6 @@ private fun SetScanlatorsItem(
)
}
}
// SY <--
@Composable
private fun ColumnScope.SortPage(
@@ -48,7 +48,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastMap
import eu.kanade.domain.manga.model.chaptersFiltered
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.manga.components.ChapterHeader
import eu.kanade.presentation.manga.components.ExpandableMangaDescription
@@ -375,7 +374,7 @@ private fun MangaScreenSmallImpl(
title = state.manga.title,
titleAlphaProvider = { animatedTitleAlpha },
backgroundAlphaProvider = { animatedBgAlpha },
hasFilters = state.manga.chaptersFiltered(),
hasFilters = state.filterActive,
onBackClicked = internalOnBackPressed,
onClickFilter = onFilterClicked,
onClickShare = onShareClicked,
@@ -705,7 +704,7 @@ fun MangaScreenLargeImpl(
title = state.manga.title,
titleAlphaProvider = { if (isAnySelected) 1f else 0f },
backgroundAlphaProvider = { 1f },
hasFilters = state.manga.chaptersFiltered(),
hasFilters = state.filterActive,
onBackClicked = internalOnBackPressed,
onClickFilter = onFilterButtonClicked,
onClickShare = onShareClicked,
@@ -0,0 +1,134 @@
package eu.kanade.presentation.manga.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank
import androidx.compose.material.icons.rounded.DisabledByDefault
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties
import eu.kanade.tachiyomi.R
import tachiyomi.presentation.core.components.material.TextButton
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.util.isScrolledToEnd
import tachiyomi.presentation.core.util.isScrolledToStart
@Composable
fun ScanlatorFilterDialog(
availableScanlators: Set<String>,
excludedScanlators: Set<String>,
onDismissRequest: () -> Unit,
onConfirm: (Set<String>) -> Unit,
) {
val sortedAvailableScanlators = remember(availableScanlators) {
availableScanlators.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it })
}
val mutableExcludedScanlators = remember(excludedScanlators) { excludedScanlators.toMutableStateList() }
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(text = stringResource(R.string.exclude_scanlators)) },
text = textFunc@{
if (sortedAvailableScanlators.isEmpty()) {
Text(text = stringResource(R.string.no_scanlators_found))
return@textFunc
}
Box {
val state = rememberLazyListState()
LazyColumn(state = state) {
sortedAvailableScanlators.forEach { scanlator ->
item {
val isExcluded = mutableExcludedScanlators.contains(scanlator)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
if (isExcluded) {
mutableExcludedScanlators.remove(scanlator)
} else {
mutableExcludedScanlators.add(scanlator)
}
}
.minimumInteractiveComponentSize()
.clip(MaterialTheme.shapes.small)
.fillMaxWidth()
.padding(horizontal = MaterialTheme.padding.small),
) {
Icon(
imageVector = if (isExcluded) {
Icons.Rounded.DisabledByDefault
} else {
Icons.Rounded.CheckBoxOutlineBlank
},
tint = if (isExcluded) {
MaterialTheme.colorScheme.primary
} else {
LocalContentColor.current
},
contentDescription = null,
)
Text(
text = scanlator,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(start = 24.dp),
)
}
}
}
}
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
}
},
properties = DialogProperties(
usePlatformDefaultWidth = true,
),
confirmButton = {
FlowRow {
if (sortedAvailableScanlators.isEmpty()) {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel))
}
return@FlowRow
}
TextButton(onClick = mutableExcludedScanlators::clear) {
Text(text = stringResource(R.string.action_reset))
}
Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(R.string.action_cancel))
}
TextButton(
onClick = {
onConfirm(mutableExcludedScanlators.toSet())
onDismissRequest()
},
) {
Text(text = stringResource(R.string.action_ok))
}
}
},
)
}
@@ -47,7 +47,6 @@ import tachiyomi.data.DatabaseHandler
import tachiyomi.data.DateColumnAdapter
import tachiyomi.data.History
import tachiyomi.data.Mangas
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.data.StringListColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.UnsortedPreferences
@@ -126,9 +125,6 @@ class AppModule(val app: Application) : InjektModule {
mangasAdapter = Mangas.Adapter(
genreAdapter = StringListColumnAdapter,
update_strategyAdapter = UpdateStrategyColumnAdapter,
// SY -->
filtered_scanlatorsAdapter = StringListAndColumnAdapter,
// SY <--
),
)
}
@@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga
@@ -252,10 +253,15 @@ class BackupCreator(
// Check if user wants chapter information in backup
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters
val chapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id, backupChapterMapper) }
if (chapters.isNotEmpty()) {
mangaObject.chapters = chapters
handler.awaitList {
chaptersQueries.getChaptersByMangaId(
mangaId = manga.id,
applyScanlatorFilter = 0, // false
mapper = backupChapterMapper,
)
}
.takeUnless(List<BackupChapter>::isEmpty)
?.let { mangaObject.chapters = it }
}
// Check if user wants category information in backup
@@ -27,7 +27,6 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID
import exh.util.nullIfBlank
import exh.util.nullIfEmpty
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import tachiyomi.core.preference.AndroidPreferenceStore
@@ -35,7 +34,6 @@ import tachiyomi.core.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Manga_sync
import tachiyomi.data.Mangas
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.data.manga.MangaMapper
import tachiyomi.data.manga.MergedMangaMapper
@@ -365,9 +363,6 @@ class BackupRestorer(
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
mangaId = manga.id,
// SY -->
filteredScanlators = manga.filteredScanlators?.let(StringListAndColumnAdapter::encode),
// SY <--
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
)
}
@@ -530,9 +525,6 @@ class BackupRestorer(
chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded,
// SY -->
filteredScanlators = manga.filteredScanlators?.nullIfEmpty(),
// SY <--
updateStrategy = manga.updateStrategy,
)
mangasQueries.selectLastInsertedRowId()
@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga
@@ -79,7 +78,6 @@ data class BackupManga(
chapterFlags = this@BackupManga.chapterFlags.toLong(),
updateStrategy = this@BackupManga.updateStrategy,
lastModifiedAt = this@BackupManga.lastModifiedAt,
filteredScanlators = this@BackupManga.filtered_scanlators?.let(StringListAndColumnAdapter::decode),
)
}
@@ -141,7 +139,6 @@ data class BackupManga(
lastModifiedAt = manga.lastModifiedAt,
favoriteModifiedAt = manga.favoriteModifiedAt,
// SY -->
filtered_scanlators = manga.filteredScanlators?.let(StringListAndColumnAdapter::encode),
).also { backupManga ->
customMangaInfo?.let {
backupManga.customTitle = it.title
@@ -602,9 +602,9 @@ class LibraryScreenModel(
// SY -->
val mergedManga = getMergedMangaById.await(manga.id).associateBy { it.id }
return if (manga.id == MERGED_SOURCE_ID) {
getMergedChaptersByMangaId.await(manga.id)
getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true)
} else {
getChaptersByMangaId.await(manga.id)
getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true)
}.getNextUnread(manga, downloadManager, mergedManga)
// SY <--
}
@@ -33,7 +33,7 @@ import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.manga.MangaScreen
import eu.kanade.presentation.manga.components.DeleteChaptersDialog
import eu.kanade.presentation.manga.components.MangaCoverDialog
import eu.kanade.presentation.manga.components.SelectScanlatorsDialog
import eu.kanade.presentation.manga.components.ScanlatorFilterDialog
import eu.kanade.presentation.manga.components.SetIntervalDialog
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen
@@ -156,9 +156,17 @@ class MangaScreen(
// SY -->
onWebViewClicked = {
if (successState.mergedData == null) {
openMangaInWebView(navigator, screenModel.manga, screenModel.source)
openMangaInWebView(
navigator,
screenModel.manga,
screenModel.source
)
} else {
openMergedMangaWebview(context, navigator, successState.mergedData)
openMergedMangaWebview(
context,
navigator,
successState.mergedData
)
}
}.takeIf { isHttpSource },
// SY <--
@@ -199,9 +207,7 @@ class MangaScreen(
onInvertSelection = screenModel::invertSelection,
)
// SY -->
var showScanlatorsDialog by remember { mutableStateOf(false) }
// SY <--
val onDismissRequest = { screenModel.dismissDialog() }
when (val dialog = successState.dialog) {
@@ -240,9 +246,8 @@ class MangaScreen(
onDisplayModeChanged = screenModel::setDisplayMode,
onSetAsDefault = screenModel::setCurrentSettingsAsDefault,
onResetToDefault = screenModel::resetToDefaultSettings,
// SY -->
onClickShowScanlatorSelection = { showScanlatorsDialog = true }.takeIf { successState.scanlators.size > 1 },
// SY <--
scanlatorFilterActive = successState.scanlatorFilterActive,
onScanlatorFilterClicked = { showScanlatorsDialog = true },
)
MangaScreenModel.Dialog.TrackSheet -> {
NavigatorAdaptiveSheet(
@@ -306,16 +311,15 @@ class MangaScreen(
}
// SY <--
}
// SY -->
if (showScanlatorsDialog) {
SelectScanlatorsDialog(
ScanlatorFilterDialog(
availableScanlators = successState.availableScanlators,
excludedScanlators = successState.excludedScanlators,
onDismissRequest = { showScanlatorsDialog = false },
availableScanlators = successState.scanlators,
initialSelectedScanlators = successState.manga.filteredScanlators ?: successState.scanlators,
onSelectScanlators = screenModel::setScanlatorFilter,
onConfirm = screenModel::setExcludedScanlators,
)
}
// SY <--
}
private fun continueReading(context: Context, unreadChapter: Chapter?) {
@@ -11,11 +11,15 @@ import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState
import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
import eu.kanade.domain.manga.interactor.GetPagePreviews
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.PagePreview
import eu.kanade.domain.manga.model.chaptersFiltered
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.toSManga
@@ -106,7 +110,6 @@ import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.SetMangaFilteredScanlators
import tachiyomi.domain.manga.interactor.UpdateMergedSettings
import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga
@@ -141,7 +144,6 @@ class MangaScreenModel(
// SY -->
private val sourceManager: SourceManager = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val setMangaFilteredScanlators: SetMangaFilteredScanlators = Injekt.get(),
private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
@@ -157,6 +159,9 @@ class MangaScreenModel(
private val setCustomMangaInfo: SetCustomMangaInfo = Injekt.get(),
// SY <--
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getAvailableScanlators: GetAvailableScanlators = Injekt.get(),
private val getExcludedScanlators: GetExcludedScanlators = Injekt.get(),
private val setExcludedScanlators: SetExcludedScanlators = Injekt.get(),
private val setMangaChapterFlags: SetMangaChapterFlags = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val setReadStatus: SetReadStatus = Injekt.get(),
@@ -238,11 +243,11 @@ class MangaScreenModel(
init {
screenModelScope.launchIO {
getMangaAndChapters.subscribe(mangaId)
getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true)
.distinctUntilChanged()
// SY -->
.combine(
getMergedChaptersByMangaId.subscribe(mangaId, true)
getMergedChaptersByMangaId.subscribe(mangaId, true, applyScanlatorFilter = true)
.distinctUntilChanged(),
) { (manga, chapters), mergedChapters ->
if (manga.source == MERGED_SOURCE_ID) {
@@ -311,19 +316,38 @@ class MangaScreenModel(
// SY -->
meta = raiseMetadata(flatMetadata, it.source),
mergedData = mergedData,
scanlators = getChapterScanlators(manga, chapters),
// SY <--
)
}
}
}
screenModelScope.launchIO {
getExcludedScanlators.subscribe(mangaId)
.distinctUntilChanged()
.collectLatest { excludedScanlators ->
updateSuccessState {
it.copy(excludedScanlators = excludedScanlators)
}
}
}
screenModelScope.launchIO {
getAvailableScanlators.subscribe(mangaId)
.distinctUntilChanged()
.collectLatest { availableScanlators ->
updateSuccessState {
it.copy(availableScanlators = availableScanlators)
}
}
}
observeDownloads()
screenModelScope.launchIO {
val manga = getMangaAndChapters.awaitManga(mangaId)
// SY -->
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChaptersByMangaId.await(mangaId) else getMangaAndChapters.awaitChapters(mangaId))
val chapters = (if (manga.source == MERGED_SOURCE_ID) getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) else getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true))
.toChapterListItems(manga, null)
val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references ->
MergedMangaData(
@@ -351,6 +375,8 @@ class MangaScreenModel(
source = source,
isFromSource = isFromSource,
chapters = chapters,
availableScanlators = getAvailableScanlators.await(mangaId),
excludedScanlators = getExcludedScanlators.await(mangaId),
isRefreshingData = needRefreshInfo || needRefreshChapter,
dialog = null,
// SY -->
@@ -365,7 +391,6 @@ class MangaScreenModel(
} else {
PagePreviewState.Unused
},
scanlators = getChapterScanlators(manga, chapters.map { it.chapter }),
alwaysShowReadingProgress = readerPreferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
// SY <--
)
@@ -424,15 +449,6 @@ class MangaScreenModel(
}
// SY -->
private fun getChapterScanlators(manga: Manga, chapters: List<Chapter>): List<String> {
return if (manga.isEhBasedManga()) {
emptyList()
} else {
chapters.flatMap { MdUtil.getScanlators(it.scanlator) }
.distinct()
}
}
private fun raiseMetadata(flatMetadata: FlatMetadata?, source: Source): RaisedSearchMetadata? {
return if (flatMetadata != null) {
val metaClass = source.getMainSource<MetadataSource<*, *>>()?.metaClass
@@ -1299,15 +1315,6 @@ class MangaScreenModel(
}
}
// SY -->
fun setScanlatorFilter(filteredScanlators: List<String>) {
val manga = manga ?: return
screenModelScope.launchIO {
setMangaFilteredScanlators.awaitSetFilteredScanlators(manga, filteredScanlators)
}
}
// SY <--
/**
* Sets the active display mode.
* @param mode the mode to set.
@@ -1535,6 +1542,12 @@ class MangaScreenModel(
updateSuccessState { it.copy(dialog = Dialog.FullCover) }
}
fun setExcludedScanlators(excludedScanlators: Set<String>) {
screenModelScope.launchIO {
setExcludedScanlators.await(mangaId, excludedScanlators)
}
}
// SY -->
fun showEditMangaInfoDialog() {
mutableState.update { state ->
@@ -1570,6 +1583,8 @@ class MangaScreenModel(
val source: Source,
val isFromSource: Boolean,
val chapters: List<ChapterList.Item>,
val availableScanlators: Set<String>,
val excludedScanlators: Set<String>,
val trackItems: List<TrackItem> = emptyList(),
val isRefreshingData: Boolean = false,
val dialog: MangaScreenModel.Dialog? = null,
@@ -1581,11 +1596,9 @@ class MangaScreenModel(
val showMergeInOverflow: Boolean,
val showMergeWithAnother: Boolean,
val pagePreviewsState: PagePreviewState,
val scanlators: List<String>,
val alwaysShowReadingProgress: Boolean,
// SY <--
) : State {
val processedChapters by lazy {
chapters.applyFilters(manga).toList()
}
@@ -1617,6 +1630,12 @@ class MangaScreenModel(
}
}
val scanlatorFilterActive: Boolean
get() = excludedScanlators.intersect(availableScanlators).isNotEmpty()
val filterActive: Boolean
get() = scanlatorFilterActive || manga.chaptersFiltered()
val trackingAvailable: Boolean
get() = trackItems.isNotEmpty()
@@ -1638,11 +1657,6 @@ class MangaScreenModel(
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } }
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() || MdUtil.getScanlators(chapter.chapter.scanlator).any { group -> manga.filteredScanlators!!.contains(group) }
}
// SY <--
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
}
}
@@ -181,10 +181,10 @@ class ReaderViewModel @JvmOverloads constructor(
// SY -->
val (chapters, mangaMap) = runBlocking {
if (manga.source == MERGED_SOURCE_ID) {
getMergedChaptersByMangaId.await(manga.id) to getMergedMangaById.await(manga.id)
getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to getMergedMangaById.await(manga.id)
.associateBy { it.id }
} else {
getChaptersByMangaId.await(manga.id) to null
getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to null
}
}
fun isChapterDownloaded(chapter: Chapter): Boolean {
@@ -220,13 +220,7 @@ class ReaderViewModel @JvmOverloads constructor(
) ||
// SY <--
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark) ||
// SY -->
(
manga.filteredScanlators != null && MdUtil.getScanlators(it.scanlator)
.none { group -> manga.filteredScanlators!!.contains(group) }
)
// SY <--
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark)
}
else -> false
}