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

View File

@ -26,7 +26,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 58 versionCode = 59
versionName = "1.9.4" versionName = "1.9.4"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View File

@ -1,11 +1,14 @@
package eu.kanade.domain package eu.kanade.domain
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.download.interactor.DeleteDownload import eu.kanade.domain.download.interactor.DeleteDownload
import eu.kanade.domain.extension.interactor.GetExtensionLanguages import eu.kanade.domain.extension.interactor.GetExtensionLanguages
import eu.kanade.domain.extension.interactor.GetExtensionSources import eu.kanade.domain.extension.interactor.GetExtensionSources
import eu.kanade.domain.extension.interactor.GetExtensionsByType 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.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
@ -112,6 +115,8 @@ class DomainModule : InjektModule {
addFactory { NetworkToLocalManga(get()) } addFactory { NetworkToLocalManga(get()) }
addFactory { UpdateManga(get(), get()) } addFactory { UpdateManga(get(), get()) }
addFactory { SetMangaCategories(get()) } addFactory { SetMangaCategories(get()) }
addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) }
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) } addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get(), get()) } addFactory { GetApplicationRelease(get(), get()) }
@ -133,7 +138,8 @@ class DomainModule : InjektModule {
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get(), get()) } addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() } 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()) } addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) } addFactory { GetHistory(get()) }

View File

@ -52,7 +52,6 @@ import tachiyomi.domain.manga.interactor.InsertFavoriteEntryAlternative
import tachiyomi.domain.manga.interactor.InsertFlatMetadata import tachiyomi.domain.manga.interactor.InsertFlatMetadata
import tachiyomi.domain.manga.interactor.InsertMergedReference import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.interactor.SetMangaFilteredScanlators
import tachiyomi.domain.manga.interactor.UpdateMergedSettings import tachiyomi.domain.manga.interactor.UpdateMergedSettings
import tachiyomi.domain.manga.repository.CustomMangaRepository import tachiyomi.domain.manga.repository.CustomMangaRepository
import tachiyomi.domain.manga.repository.FavoritesEntryRepository import tachiyomi.domain.manga.repository.FavoritesEntryRepository
@ -88,7 +87,6 @@ class SYDomainModule : InjektModule {
addFactory { GetShowLatest(get()) } addFactory { GetShowLatest(get()) }
addFactory { ToggleExcludeFromDataSaver(get()) } addFactory { ToggleExcludeFromDataSaver(get()) }
addFactory { SetSourceCategories(get()) } addFactory { SetSourceCategories(get()) }
addFactory { SetMangaFilteredScanlators(get()) }
addFactory { GetAllManga(get()) } addFactory { GetAllManga(get()) }
addFactory { GetMangaBySource(get()) } addFactory { GetMangaBySource(get()) }
addFactory { DeleteChapters(get()) } addFactory { DeleteChapters(get()) }

View File

@ -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() }
}
}

View File

@ -2,6 +2,7 @@ package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.chapter.model.copyFromSChapter import eu.kanade.domain.chapter.model.copyFromSChapter
import eu.kanade.domain.chapter.model.toSChapter 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -34,6 +35,7 @@ class SyncChaptersWithSource(
private val updateManga: UpdateManga, private val updateManga: UpdateManga,
private val updateChapter: UpdateChapter, private val updateChapter: UpdateChapter,
private val getChaptersByMangaId: GetChaptersByMangaId, private val getChaptersByMangaId: GetChaptersByMangaId,
private val getExcludedScanlators: GetExcludedScanlators,
) { ) {
/** /**
@ -234,6 +236,10 @@ class SyncChaptersWithSource(
val reAddedUrls = reAdded.map { it.url }.toHashSet() 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
}
} }
} }

View File

@ -3,7 +3,6 @@ package eu.kanade.domain.chapter.model
import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterList import eu.kanade.tachiyomi.ui.manga.ChapterList
import exh.md.utils.MdUtil
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.getChapterSort import tachiyomi.domain.chapter.service.getChapterSort
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -16,7 +15,7 @@ import tachiyomi.source.local.isLocal
*/ */
fun List<Chapter>.applyFilters( fun List<Chapter>.applyFilters(
manga: Manga, manga: Manga,
downloadManager: DownloadManager/* SY --> */, downloadManager: DownloadManager,/* SY --> */
mergedManga: Map<Long, Manga>, /* SY <-- */ mergedManga: Map<Long, Manga>, /* SY <-- */
): List<Chapter> { ): List<Chapter> {
val isLocalManga = manga.isLocal() val isLocalManga = manga.isLocal()
@ -41,14 +40,6 @@ fun List<Chapter>.applyFilters(
downloaded || isLocalManga downloaded || isLocalManga
} }
} }
// SY -->
.filter { chapter ->
manga.filteredScanlators.isNullOrEmpty() ||
MdUtil.getScanlators(chapter.scanlator).any { group ->
manga.filteredScanlators!!.contains(group)
}
}
// SY <--
.sortedWith(getChapterSort(manga)) .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(unreadFilter) { !chapter.read } }
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } } .filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } } .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) } .sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
} }

View File

@ -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() }
}
}

View File

@ -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)
}
}
}

View File

@ -33,10 +33,7 @@ val Manga.downloadedFilter: TriState
fun Manga.chaptersFiltered(): Boolean { fun Manga.chaptersFiltered(): Boolean {
return unreadFilter != TriState.DISABLED || return unreadFilter != TriState.DISABLED ||
downloadedFilter != TriState.DISABLED || downloadedFilter != TriState.DISABLED ||
bookmarkedFilter != TriState.DISABLED || bookmarkedFilter != TriState.DISABLED
// SY -->
!filteredScanlators.isNullOrEmpty()
// SY <--
} }
fun Manga.forceDownloaded(): Boolean { fun Manga.forceDownloaded(): Boolean {
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get() return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()

View File

@ -14,6 +14,7 @@ import androidx.compose.material.icons.outlined.PeopleAlt
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton 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.RadioItem
import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TriStateItem import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.theme.active
@Composable @Composable
fun ChapterSettingsDialog( fun ChapterSettingsDialog(
@ -45,13 +47,12 @@ fun ChapterSettingsDialog(
onDownloadFilterChanged: (TriState) -> Unit, onDownloadFilterChanged: (TriState) -> Unit,
onUnreadFilterChanged: (TriState) -> Unit, onUnreadFilterChanged: (TriState) -> Unit,
onBookmarkedFilterChanged: (TriState) -> Unit, onBookmarkedFilterChanged: (TriState) -> Unit,
scanlatorFilterActive: Boolean,
onScanlatorFilterClicked: (() -> Unit),
onSortModeChanged: (Long) -> Unit, onSortModeChanged: (Long) -> Unit,
onDisplayModeChanged: (Long) -> Unit, onDisplayModeChanged: (Long) -> Unit,
onSetAsDefault: (applyToExistingManga: Boolean) -> Unit, onSetAsDefault: (applyToExistingManga: Boolean) -> Unit,
onResetToDefault: () -> Unit, onResetToDefault: () -> Unit,
// SY -->
onClickShowScanlatorSelection: (() -> Unit)?,
// SY <--
) { ) {
var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) } var showSetAsDefaultDialog by rememberSaveable { mutableStateOf(false) }
if (showSetAsDefaultDialog) { if (showSetAsDefaultDialog) {
@ -94,14 +95,14 @@ fun ChapterSettingsDialog(
0 -> { 0 -> {
FilterPage( FilterPage(
downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED, downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED,
onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true }, onDownloadFilterChanged = onDownloadFilterChanged
.takeUnless { manga?.forceDownloaded() == true },
unreadFilter = manga?.unreadFilter ?: TriState.DISABLED, unreadFilter = manga?.unreadFilter ?: TriState.DISABLED,
onUnreadFilterChanged = onUnreadFilterChanged, onUnreadFilterChanged = onUnreadFilterChanged,
bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED, bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED,
onBookmarkedFilterChanged = onBookmarkedFilterChanged, onBookmarkedFilterChanged = onBookmarkedFilterChanged,
// SY --> scanlatorFilterActive = scanlatorFilterActive,
onClickShowScanlatorSelection = onClickShowScanlatorSelection, onScanlatorFilterClicked = onScanlatorFilterClicked,
// SY <--
) )
} }
1 -> { 1 -> {
@ -130,9 +131,8 @@ private fun ColumnScope.FilterPage(
onUnreadFilterChanged: (TriState) -> Unit, onUnreadFilterChanged: (TriState) -> Unit,
bookmarkedFilter: TriState, bookmarkedFilter: TriState,
onBookmarkedFilterChanged: (TriState) -> Unit, onBookmarkedFilterChanged: (TriState) -> Unit,
// SY --> scanlatorFilterActive: Boolean,
onClickShowScanlatorSelection: (() -> Unit)?, onScanlatorFilterClicked: (() -> Unit),
// SY <--
) { ) {
TriStateItem( TriStateItem(
label = stringResource(R.string.label_downloaded), label = stringResource(R.string.label_downloaded),
@ -149,23 +149,20 @@ private fun ColumnScope.FilterPage(
state = bookmarkedFilter, state = bookmarkedFilter,
onClick = onBookmarkedFilterChanged, onClick = onBookmarkedFilterChanged,
) )
// SY --> ScanlatorFilterItem(
if (onClickShowScanlatorSelection != null) { active = scanlatorFilterActive,
SetScanlatorsItem(onClickShowScanlatorSelection) onClick = onScanlatorFilterClicked,
} )
// SY <--
} }
// SY -->
@Composable @Composable
private fun SetScanlatorsItem( fun ScanlatorFilterItem(
onClickShowScanlatorSelection: () -> Unit, active: Boolean,
onClick: () -> Unit,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.clickable( .clickable(onClick = onClick)
onClick = onClickShowScanlatorSelection,
)
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp), .padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@ -174,6 +171,11 @@ private fun SetScanlatorsItem(
Icon( Icon(
imageVector = Icons.Outlined.PeopleAlt, imageVector = Icons.Outlined.PeopleAlt,
contentDescription = null, contentDescription = null,
tint = if (active) {
MaterialTheme.colorScheme.active
} else {
LocalContentColor.current
},
) )
Text( Text(
text = stringResource(R.string.scanlator), text = stringResource(R.string.scanlator),
@ -181,7 +183,6 @@ private fun SetScanlatorsItem(
) )
} }
} }
// SY <--
@Composable @Composable
private fun ColumnScope.SortPage( private fun ColumnScope.SortPage(

View File

@ -48,7 +48,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastMap 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.ChapterDownloadAction
import eu.kanade.presentation.manga.components.ChapterHeader import eu.kanade.presentation.manga.components.ChapterHeader
import eu.kanade.presentation.manga.components.ExpandableMangaDescription import eu.kanade.presentation.manga.components.ExpandableMangaDescription
@ -375,7 +374,7 @@ private fun MangaScreenSmallImpl(
title = state.manga.title, title = state.manga.title,
titleAlphaProvider = { animatedTitleAlpha }, titleAlphaProvider = { animatedTitleAlpha },
backgroundAlphaProvider = { animatedBgAlpha }, backgroundAlphaProvider = { animatedBgAlpha },
hasFilters = state.manga.chaptersFiltered(), hasFilters = state.filterActive,
onBackClicked = internalOnBackPressed, onBackClicked = internalOnBackPressed,
onClickFilter = onFilterClicked, onClickFilter = onFilterClicked,
onClickShare = onShareClicked, onClickShare = onShareClicked,
@ -705,7 +704,7 @@ fun MangaScreenLargeImpl(
title = state.manga.title, title = state.manga.title,
titleAlphaProvider = { if (isAnySelected) 1f else 0f }, titleAlphaProvider = { if (isAnySelected) 1f else 0f },
backgroundAlphaProvider = { 1f }, backgroundAlphaProvider = { 1f },
hasFilters = state.manga.chaptersFiltered(), hasFilters = state.filterActive,
onBackClicked = internalOnBackPressed, onBackClicked = internalOnBackPressed,
onClickFilter = onFilterButtonClicked, onClickFilter = onFilterButtonClicked,
onClickShare = onShareClicked, onClickShare = onShareClicked,

View File

@ -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))
}
}
},
)
}

View File

@ -47,7 +47,6 @@ import tachiyomi.data.DatabaseHandler
import tachiyomi.data.DateColumnAdapter import tachiyomi.data.DateColumnAdapter
import tachiyomi.data.History import tachiyomi.data.History
import tachiyomi.data.Mangas import tachiyomi.data.Mangas
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.StringListColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
@ -126,9 +125,6 @@ class AppModule(val app: Application) : InjektModule {
mangasAdapter = Mangas.Adapter( mangasAdapter = Mangas.Adapter(
genreAdapter = StringListColumnAdapter, genreAdapter = StringListColumnAdapter,
update_strategyAdapter = UpdateStrategyColumnAdapter, update_strategyAdapter = UpdateStrategyColumnAdapter,
// SY -->
filtered_scanlatorsAdapter = StringListAndColumnAdapter,
// SY <--
), ),
) )
} }

View File

@ -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.BackupConst.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupCategory 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.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
@ -252,10 +253,15 @@ class BackupCreator(
// Check if user wants chapter information in backup // Check if user wants chapter information in backup
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) { if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters // Backup all the chapters
val chapters = handler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id, backupChapterMapper) } handler.awaitList {
if (chapters.isNotEmpty()) { chaptersQueries.getChaptersByMangaId(
mangaObject.chapters = chapters mangaId = manga.id,
applyScanlatorFilter = 0, // false
mapper = backupChapterMapper,
)
} }
.takeUnless(List<BackupChapter>::isEmpty)
?.let { mangaObject.chapters = it }
} }
// Check if user wants category information in backup // Check if user wants category information in backup

View File

@ -27,7 +27,6 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import exh.EXHMigrations import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.util.nullIfBlank import exh.util.nullIfBlank
import exh.util.nullIfEmpty
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.AndroidPreferenceStore
@ -35,7 +34,6 @@ import tachiyomi.core.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Manga_sync import tachiyomi.data.Manga_sync
import tachiyomi.data.Mangas import tachiyomi.data.Mangas
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.data.manga.MangaMapper import tachiyomi.data.manga.MangaMapper
import tachiyomi.data.manga.MergedMangaMapper import tachiyomi.data.manga.MergedMangaMapper
@ -365,9 +363,6 @@ class BackupRestorer(
coverLastModified = manga.coverLastModified, coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded, dateAdded = manga.dateAdded,
mangaId = manga.id, mangaId = manga.id,
// SY -->
filteredScanlators = manga.filteredScanlators?.let(StringListAndColumnAdapter::encode),
// SY <--
updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode), updateStrategy = manga.updateStrategy.let(UpdateStrategyColumnAdapter::encode),
) )
} }
@ -530,9 +525,6 @@ class BackupRestorer(
chapterFlags = manga.chapterFlags, chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified, coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded, dateAdded = manga.dateAdded,
// SY -->
filteredScanlators = manga.filteredScanlators?.nullIfEmpty(),
// SY <--
updateStrategy = manga.updateStrategy, updateStrategy = manga.updateStrategy,
) )
mangasQueries.selectLastInsertedRowId() mangasQueries.selectLastInsertedRowId()

View File

@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -79,7 +78,6 @@ data class BackupManga(
chapterFlags = this@BackupManga.chapterFlags.toLong(), chapterFlags = this@BackupManga.chapterFlags.toLong(),
updateStrategy = this@BackupManga.updateStrategy, updateStrategy = this@BackupManga.updateStrategy,
lastModifiedAt = this@BackupManga.lastModifiedAt, lastModifiedAt = this@BackupManga.lastModifiedAt,
filteredScanlators = this@BackupManga.filtered_scanlators?.let(StringListAndColumnAdapter::decode),
) )
} }
@ -141,7 +139,6 @@ data class BackupManga(
lastModifiedAt = manga.lastModifiedAt, lastModifiedAt = manga.lastModifiedAt,
favoriteModifiedAt = manga.favoriteModifiedAt, favoriteModifiedAt = manga.favoriteModifiedAt,
// SY --> // SY -->
filtered_scanlators = manga.filteredScanlators?.let(StringListAndColumnAdapter::encode),
).also { backupManga -> ).also { backupManga ->
customMangaInfo?.let { customMangaInfo?.let {
backupManga.customTitle = it.title backupManga.customTitle = it.title

View File

@ -602,9 +602,9 @@ class LibraryScreenModel(
// SY --> // SY -->
val mergedManga = getMergedMangaById.await(manga.id).associateBy { it.id } val mergedManga = getMergedMangaById.await(manga.id).associateBy { it.id }
return if (manga.id == MERGED_SOURCE_ID) { return if (manga.id == MERGED_SOURCE_ID) {
getMergedChaptersByMangaId.await(manga.id) getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true)
} else { } else {
getChaptersByMangaId.await(manga.id) getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true)
}.getNextUnread(manga, downloadManager, mergedManga) }.getNextUnread(manga, downloadManager, mergedManga)
// SY <-- // SY <--
} }

View File

@ -33,7 +33,7 @@ import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.presentation.manga.MangaScreen import eu.kanade.presentation.manga.MangaScreen
import eu.kanade.presentation.manga.components.DeleteChaptersDialog import eu.kanade.presentation.manga.components.DeleteChaptersDialog
import eu.kanade.presentation.manga.components.MangaCoverDialog 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.manga.components.SetIntervalDialog
import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
@ -156,9 +156,17 @@ class MangaScreen(
// SY --> // SY -->
onWebViewClicked = { onWebViewClicked = {
if (successState.mergedData == null) { if (successState.mergedData == null) {
openMangaInWebView(navigator, screenModel.manga, screenModel.source) openMangaInWebView(
navigator,
screenModel.manga,
screenModel.source
)
} else { } else {
openMergedMangaWebview(context, navigator, successState.mergedData) openMergedMangaWebview(
context,
navigator,
successState.mergedData
)
} }
}.takeIf { isHttpSource }, }.takeIf { isHttpSource },
// SY <-- // SY <--
@ -199,9 +207,7 @@ class MangaScreen(
onInvertSelection = screenModel::invertSelection, onInvertSelection = screenModel::invertSelection,
) )
// SY -->
var showScanlatorsDialog by remember { mutableStateOf(false) } var showScanlatorsDialog by remember { mutableStateOf(false) }
// SY <--
val onDismissRequest = { screenModel.dismissDialog() } val onDismissRequest = { screenModel.dismissDialog() }
when (val dialog = successState.dialog) { when (val dialog = successState.dialog) {
@ -240,9 +246,8 @@ class MangaScreen(
onDisplayModeChanged = screenModel::setDisplayMode, onDisplayModeChanged = screenModel::setDisplayMode,
onSetAsDefault = screenModel::setCurrentSettingsAsDefault, onSetAsDefault = screenModel::setCurrentSettingsAsDefault,
onResetToDefault = screenModel::resetToDefaultSettings, onResetToDefault = screenModel::resetToDefaultSettings,
// SY --> scanlatorFilterActive = successState.scanlatorFilterActive,
onClickShowScanlatorSelection = { showScanlatorsDialog = true }.takeIf { successState.scanlators.size > 1 }, onScanlatorFilterClicked = { showScanlatorsDialog = true },
// SY <--
) )
MangaScreenModel.Dialog.TrackSheet -> { MangaScreenModel.Dialog.TrackSheet -> {
NavigatorAdaptiveSheet( NavigatorAdaptiveSheet(
@ -306,16 +311,15 @@ class MangaScreen(
} }
// SY <-- // SY <--
} }
// SY -->
if (showScanlatorsDialog) { if (showScanlatorsDialog) {
SelectScanlatorsDialog( ScanlatorFilterDialog(
availableScanlators = successState.availableScanlators,
excludedScanlators = successState.excludedScanlators,
onDismissRequest = { showScanlatorsDialog = false }, onDismissRequest = { showScanlatorsDialog = false },
availableScanlators = successState.scanlators, onConfirm = screenModel::setExcludedScanlators,
initialSelectedScanlators = successState.manga.filteredScanlators ?: successState.scanlators,
onSelectScanlators = screenModel::setScanlatorFilter,
) )
} }
// SY <--
} }
private fun continueReading(context: Context, unreadChapter: Chapter?) { private fun continueReading(context: Context, unreadChapter: Chapter?) {

View File

@ -11,11 +11,15 @@ import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.core.util.addOrRemove import eu.kanade.core.util.addOrRemove
import eu.kanade.core.util.insertSeparators 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.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource 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.GetPagePreviews
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.PagePreview 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.copyFrom
import eu.kanade.domain.manga.model.downloadedFilter import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.domain.manga.model.toSManga 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.NetworkToLocalManga
import tachiyomi.domain.manga.interactor.SetCustomMangaInfo import tachiyomi.domain.manga.interactor.SetCustomMangaInfo
import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.manga.interactor.SetMangaChapterFlags
import tachiyomi.domain.manga.interactor.SetMangaFilteredScanlators
import tachiyomi.domain.manga.interactor.UpdateMergedSettings import tachiyomi.domain.manga.interactor.UpdateMergedSettings
import tachiyomi.domain.manga.model.CustomMangaInfo import tachiyomi.domain.manga.model.CustomMangaInfo
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@ -141,7 +144,6 @@ class MangaScreenModel(
// SY --> // SY -->
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val setMangaFilteredScanlators: SetMangaFilteredScanlators = Injekt.get(),
private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(), private val getMergedChaptersByMangaId: GetMergedChaptersByMangaId = Injekt.get(),
private val getMergedMangaById: GetMergedMangaById = Injekt.get(), private val getMergedMangaById: GetMergedMangaById = Injekt.get(),
private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(), private val getMergedReferencesById: GetMergedReferencesById = Injekt.get(),
@ -157,6 +159,9 @@ class MangaScreenModel(
private val setCustomMangaInfo: SetCustomMangaInfo = Injekt.get(), private val setCustomMangaInfo: SetCustomMangaInfo = Injekt.get(),
// SY <-- // SY <--
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), 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 setMangaChapterFlags: SetMangaChapterFlags = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val setReadStatus: SetReadStatus = Injekt.get(), private val setReadStatus: SetReadStatus = Injekt.get(),
@ -238,11 +243,11 @@ class MangaScreenModel(
init { init {
screenModelScope.launchIO { screenModelScope.launchIO {
getMangaAndChapters.subscribe(mangaId) getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true)
.distinctUntilChanged() .distinctUntilChanged()
// SY --> // SY -->
.combine( .combine(
getMergedChaptersByMangaId.subscribe(mangaId, true) getMergedChaptersByMangaId.subscribe(mangaId, true, applyScanlatorFilter = true)
.distinctUntilChanged(), .distinctUntilChanged(),
) { (manga, chapters), mergedChapters -> ) { (manga, chapters), mergedChapters ->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
@ -311,19 +316,38 @@ class MangaScreenModel(
// SY --> // SY -->
meta = raiseMetadata(flatMetadata, it.source), meta = raiseMetadata(flatMetadata, it.source),
mergedData = mergedData, mergedData = mergedData,
scanlators = getChapterScanlators(manga, chapters),
// SY <-- // 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() observeDownloads()
screenModelScope.launchIO { screenModelScope.launchIO {
val manga = getMangaAndChapters.awaitManga(mangaId) val manga = getMangaAndChapters.awaitManga(mangaId)
// SY --> // 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) .toChapterListItems(manga, null)
val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references -> val mergedData = getMergedReferencesById.await(mangaId).takeIf { it.isNotEmpty() }?.let { references ->
MergedMangaData( MergedMangaData(
@ -351,6 +375,8 @@ class MangaScreenModel(
source = source, source = source,
isFromSource = isFromSource, isFromSource = isFromSource,
chapters = chapters, chapters = chapters,
availableScanlators = getAvailableScanlators.await(mangaId),
excludedScanlators = getExcludedScanlators.await(mangaId),
isRefreshingData = needRefreshInfo || needRefreshChapter, isRefreshingData = needRefreshInfo || needRefreshChapter,
dialog = null, dialog = null,
// SY --> // SY -->
@ -365,7 +391,6 @@ class MangaScreenModel(
} else { } else {
PagePreviewState.Unused PagePreviewState.Unused
}, },
scanlators = getChapterScanlators(manga, chapters.map { it.chapter }),
alwaysShowReadingProgress = readerPreferences.preserveReadingPosition().get() && manga.isEhBasedManga(), alwaysShowReadingProgress = readerPreferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
// SY <-- // SY <--
) )
@ -424,15 +449,6 @@ class MangaScreenModel(
} }
// SY --> // 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? { private fun raiseMetadata(flatMetadata: FlatMetadata?, source: Source): RaisedSearchMetadata? {
return if (flatMetadata != null) { return if (flatMetadata != null) {
val metaClass = source.getMainSource<MetadataSource<*, *>>()?.metaClass 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. * Sets the active display mode.
* @param mode the mode to set. * @param mode the mode to set.
@ -1535,6 +1542,12 @@ class MangaScreenModel(
updateSuccessState { it.copy(dialog = Dialog.FullCover) } updateSuccessState { it.copy(dialog = Dialog.FullCover) }
} }
fun setExcludedScanlators(excludedScanlators: Set<String>) {
screenModelScope.launchIO {
setExcludedScanlators.await(mangaId, excludedScanlators)
}
}
// SY --> // SY -->
fun showEditMangaInfoDialog() { fun showEditMangaInfoDialog() {
mutableState.update { state -> mutableState.update { state ->
@ -1570,6 +1583,8 @@ class MangaScreenModel(
val source: Source, val source: Source,
val isFromSource: Boolean, val isFromSource: Boolean,
val chapters: List<ChapterList.Item>, val chapters: List<ChapterList.Item>,
val availableScanlators: Set<String>,
val excludedScanlators: Set<String>,
val trackItems: List<TrackItem> = emptyList(), val trackItems: List<TrackItem> = emptyList(),
val isRefreshingData: Boolean = false, val isRefreshingData: Boolean = false,
val dialog: MangaScreenModel.Dialog? = null, val dialog: MangaScreenModel.Dialog? = null,
@ -1581,11 +1596,9 @@ class MangaScreenModel(
val showMergeInOverflow: Boolean, val showMergeInOverflow: Boolean,
val showMergeWithAnother: Boolean, val showMergeWithAnother: Boolean,
val pagePreviewsState: PagePreviewState, val pagePreviewsState: PagePreviewState,
val scanlators: List<String>,
val alwaysShowReadingProgress: Boolean, val alwaysShowReadingProgress: Boolean,
// SY <-- // SY <--
) : State { ) : State {
val processedChapters by lazy { val processedChapters by lazy {
chapters.applyFilters(manga).toList() 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 val trackingAvailable: Boolean
get() = trackItems.isNotEmpty() get() = trackItems.isNotEmpty()
@ -1638,11 +1657,6 @@ class MangaScreenModel(
.filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } } .filter { (chapter) -> applyFilter(unreadFilter) { !chapter.read } }
.filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } } .filter { (chapter) -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
.filter { applyFilter(downloadedFilter) { it.isDownloaded || isLocalManga } } .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) } .sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
} }
} }

View File

@ -181,10 +181,10 @@ class ReaderViewModel @JvmOverloads constructor(
// SY --> // SY -->
val (chapters, mangaMap) = runBlocking { val (chapters, mangaMap) = runBlocking {
if (manga.source == MERGED_SOURCE_ID) { 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 } .associateBy { it.id }
} else { } else {
getChaptersByMangaId.await(manga.id) to null getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to null
} }
} }
fun isChapterDownloaded(chapter: Chapter): Boolean { fun isChapterDownloaded(chapter: Chapter): Boolean {
@ -220,13 +220,7 @@ class ReaderViewModel @JvmOverloads constructor(
) || ) ||
// SY <-- // SY <--
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) || (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) ||
(manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_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 <--
} }
else -> false else -> false
} }

View File

@ -339,9 +339,5 @@ object DebugFunctions {
runBlocking { handler.await { ehQueries.migrateAllNhentaiToOtherLang(NHentai.otherId, sources) } } runBlocking { handler.await { ehQueries.migrateAllNhentaiToOtherLang(NHentai.otherId, sources) } }
} }
fun resetFilteredScanlatorsForAllManga() {
runBlocking { handler.await { ehQueries.resetFilteredScanlatorsForAllManga() } }
}
fun exportProtobufScheme() = ProtoBufSchemaGenerator.generateSchemaText(Backup.serializer().descriptor) fun exportProtobufScheme() = ProtoBufSchemaGenerator.generateSchemaText(Backup.serializer().descriptor)
} }

View File

@ -27,16 +27,3 @@ object UpdateStrategyColumnAdapter : ColumnAdapter<UpdateStrategy, Long> {
override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong() override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
} }
// SY -->
private const val LIST_OF_STRINGS_AND_SEPARATOR = " & "
object StringListAndColumnAdapter : ColumnAdapter<List<String>, String> {
override fun decode(databaseValue: String) =
if (databaseValue.isEmpty()) {
emptyList()
} else {
databaseValue.split(LIST_OF_STRINGS_AND_SEPARATOR)
}
override fun encode(value: List<String>) = value.joinToString(separator = LIST_OF_STRINGS_AND_SEPARATOR)
}
// SY <--

View File

@ -27,7 +27,7 @@ private val mapper = { cursor: SqlCursor ->
chapter_flags = cursor.getLong(15)!!, chapter_flags = cursor.getLong(15)!!,
cover_last_modified = cursor.getLong(16)!!, cover_last_modified = cursor.getLong(16)!!,
date_added = cursor.getLong(17)!!, date_added = cursor.getLong(17)!!,
filtered_scanlators = cursor.getString(18)?.let(StringListAndColumnAdapter::decode), filtered_scanlators = null,
update_strategy = UpdateStrategyColumnAdapter.decode(cursor.getLong(19)!!), update_strategy = UpdateStrategyColumnAdapter.decode(cursor.getLong(19)!!),
calculate_interval = cursor.getLong(20)!!, calculate_interval = cursor.getLong(20)!!,
last_modified_at = cursor.getLong(21)!!, last_modified_at = cursor.getLong(21)!!,
@ -71,8 +71,12 @@ class LibraryQuery(
coalesce(max(chapters.date_fetch), 0) AS fetchedAt, coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount sum(chapters.bookmark) AS bookmarkCount
FROM chapters FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history LEFT JOIN history
ON chapters._id = history.chapter_id ON chapters._id = history.chapter_id
WHERE excluded_scanlators.scanlator IS NULL
GROUP BY chapters.manga_id GROUP BY chapters.manga_id
) AS C ) AS C
ON M._id = C.manga_id ON M._id = C.manga_id
@ -106,10 +110,14 @@ class LibraryQuery(
coalesce(max(chapters.date_fetch), 0) AS fetchedAt, coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount sum(chapters.bookmark) AS bookmarkCount
FROM chapters FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history LEFT JOIN history
ON chapters._id = history.chapter_id ON chapters._id = history.chapter_id
LEFT JOIN merged as ME LEFT JOIN merged as ME
ON ME.manga_id = chapters.manga_id ON ME.manga_id = chapters.manga_id
WHERE excluded_scanlators.scanlator IS NULL
GROUP BY ME.merge_id GROUP BY ME.merge_id
) AS C ) AS C
ON ME.merge_id = C.merge_id ON ME.merge_id = C.merge_id

View File

@ -2,6 +2,7 @@ package tachiyomi.data.chapter
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.util.lang.toLong
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@ -76,8 +77,22 @@ class ChapterRepositoryImpl(
} }
} }
override suspend fun getChapterByMangaId(mangaId: Long): List<Chapter> { override suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean): List<Chapter> {
return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, ChapterMapper::mapChapter) } return handler.awaitList {
chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ChapterMapper::mapChapter)
}
}
override suspend fun getScanlatorsByMangaId(mangaId: Long): List<String> {
return handler.awaitList {
chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() }
}
}
override fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow<List<String>> {
return handler.subscribeToList {
chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() }
}
} }
override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter> { override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter> {
@ -93,12 +108,9 @@ class ChapterRepositoryImpl(
return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, ChapterMapper::mapChapter) } return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, ChapterMapper::mapChapter) }
} }
override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> { override suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean): Flow<List<Chapter>> {
return handler.subscribeToList { return handler.subscribeToList {
chaptersQueries.getChaptersByMangaId( chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ChapterMapper::mapChapter)
mangaId,
ChapterMapper::mapChapter,
)
} }
} }
@ -117,13 +129,13 @@ class ChapterRepositoryImpl(
return handler.awaitList { chaptersQueries.getChapterByUrl(url, ChapterMapper::mapChapter) } return handler.awaitList { chaptersQueries.getChapterByUrl(url, ChapterMapper::mapChapter) }
} }
override suspend fun getMergedChapterByMangaId(mangaId: Long): List<Chapter> { override suspend fun getMergedChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean): List<Chapter> {
return handler.awaitList { chaptersQueries.getMergedChaptersByMangaId(mangaId, ChapterMapper::mapChapter) } return handler.awaitList { chaptersQueries.getMergedChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ChapterMapper::mapChapter) }
} }
override suspend fun getMergedChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> { override suspend fun getMergedChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean): Flow<List<Chapter>> {
return handler.subscribeToList { return handler.subscribeToList {
chaptersQueries.getMergedChaptersByMangaId(mangaId, ChapterMapper::mapChapter) chaptersQueries.getMergedChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ChapterMapper::mapChapter)
} }
} }
// SY <-- // SY <--

View File

@ -25,7 +25,10 @@ object MangaMapper {
chapterFlags: Long, chapterFlags: Long,
coverLastModified: Long, coverLastModified: Long,
dateAdded: Long, dateAdded: Long,
filteredScanlators: List<String>?, // SY -->
@Suppress("UNUSED_PARAMETER")
filteredScanlators: String?,
// SY <--
updateStrategy: UpdateStrategy, updateStrategy: UpdateStrategy,
calculateInterval: Long, calculateInterval: Long,
lastModifiedAt: Long, lastModifiedAt: Long,
@ -53,9 +56,6 @@ object MangaMapper {
thumbnailUrl = thumbnailUrl, thumbnailUrl = thumbnailUrl,
updateStrategy = updateStrategy, updateStrategy = updateStrategy,
initialized = initialized, initialized = initialized,
// SY -->
filteredScanlators = filteredScanlators,
// SY <--
lastModifiedAt = lastModifiedAt, lastModifiedAt = lastModifiedAt,
favoriteModifiedAt = favoriteModifiedAt, favoriteModifiedAt = favoriteModifiedAt,
) )
@ -79,7 +79,10 @@ object MangaMapper {
chapterFlags: Long, chapterFlags: Long,
coverLastModified: Long, coverLastModified: Long,
dateAdded: Long, dateAdded: Long,
filteredScanlators: List<String>?, // SY -->
@Suppress("UNUSED_PARAMETER")
filteredScanlators: String?,
// SY <--
updateStrategy: UpdateStrategy, updateStrategy: UpdateStrategy,
calculateInterval: Long, calculateInterval: Long,
lastModifiedAt: Long, lastModifiedAt: Long,
@ -112,7 +115,7 @@ object MangaMapper {
coverLastModified, coverLastModified,
dateAdded, dateAdded,
// SY --> // SY -->
filteredScanlators, null,
// SY <-- // SY <--
updateStrategy, updateStrategy,
calculateInterval, calculateInterval,
@ -150,7 +153,6 @@ object MangaMapper {
thumbnailUrl = libraryView.thumbnail_url, thumbnailUrl = libraryView.thumbnail_url,
updateStrategy = libraryView.update_strategy, updateStrategy = libraryView.update_strategy,
initialized = libraryView.initialized, initialized = libraryView.initialized,
filteredScanlators = libraryView.filtered_scanlators,
fetchInterval = libraryView.calculate_interval.toInt(), fetchInterval = libraryView.calculate_interval.toInt(),
lastModifiedAt = libraryView.last_modified_at, lastModifiedAt = libraryView.last_modified_at,
favoriteModifiedAt = libraryView.favorite_modified_at, favoriteModifiedAt = libraryView.favorite_modified_at,

View File

@ -6,7 +6,6 @@ import logcat.LogPriority
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.data.AndroidDatabaseHandler import tachiyomi.data.AndroidDatabaseHandler
import tachiyomi.data.DatabaseHandler import tachiyomi.data.DatabaseHandler
import tachiyomi.data.StringListAndColumnAdapter
import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.StringListColumnAdapter
import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
@ -119,9 +118,6 @@ class MangaRepositoryImpl(
chapterFlags = manga.chapterFlags, chapterFlags = manga.chapterFlags,
coverLastModified = manga.coverLastModified, coverLastModified = manga.coverLastModified,
dateAdded = manga.dateAdded, dateAdded = manga.dateAdded,
// SY -->
filteredScanlators = manga.filteredScanlators,
// SY <--
updateStrategy = manga.updateStrategy, updateStrategy = manga.updateStrategy,
) )
mangasQueries.selectLastInsertedRowId() mangasQueries.selectLastInsertedRowId()
@ -170,9 +166,6 @@ class MangaRepositoryImpl(
chapterFlags = value.chapterFlags, chapterFlags = value.chapterFlags,
coverLastModified = value.coverLastModified, coverLastModified = value.coverLastModified,
dateAdded = value.dateAdded, dateAdded = value.dateAdded,
// SY -->
filteredScanlators = value.filteredScanlators?.let(StringListAndColumnAdapter::encode),
// SY <--
mangaId = value.id, mangaId = value.id,
updateStrategy = value.updateStrategy?.let(UpdateStrategyColumnAdapter::encode), updateStrategy = value.updateStrategy?.let(UpdateStrategyColumnAdapter::encode),
) )

View File

@ -36,7 +36,19 @@ FROM chapters
WHERE _id = :id; WHERE _id = :id;
getChaptersByMangaId: getChaptersByMangaId:
SELECT * SELECT C.*
FROM chapters C
LEFT JOIN excluded_scanlators ES
ON C.manga_id = ES.manga_id
AND C.scanlator = ES.scanlator
WHERE C.manga_id = :mangaId
AND (
:applyScanlatorFilter = 0
OR ES.scanlator IS NULL
);
getScanlatorsByMangaId:
SELECT scanlator
FROM chapters FROM chapters
WHERE manga_id = :mangaId; WHERE manga_id = :mangaId;
@ -58,12 +70,20 @@ WHERE url = :chapterUrl
AND manga_id = :mangaId; AND manga_id = :mangaId;
getMergedChaptersByMangaId: getMergedChaptersByMangaId:
SELECT chapters.* SELECT C.*
FROM ( FROM chapters C
SELECT manga_id FROM merged WHERE merge_id = ? JOIN (
SELECT manga_id FROM merged WHERE merge_id = :mangaId
) AS M ) AS M
JOIN chapters ON C.manga_id = M.manga_id
ON chapters.manga_id = M.manga_id; LEFT JOIN excluded_scanlators ES
ON C.manga_id = ES.manga_id
AND C.scanlator = ES.scanlator
WHERE C.manga_id = :mangaId
AND (
:applyScanlatorFilter = 0
OR ES.scanlator IS NULL
);
removeChaptersWithIds: removeChaptersWithIds:
DELETE FROM chapters DELETE FROM chapters

View File

@ -9,10 +9,6 @@ WHERE source = :oldId;
getChaptersByMangaIds: getChaptersByMangaIds:
SELECT * FROM chapters WHERE manga_id IN :mangaIds; SELECT * FROM chapters WHERE manga_id IN :mangaIds;
resetFilteredScanlatorsForAllManga:
UPDATE mangas
SET filtered_scanlators = NULL;
migrateAllNhentaiToOtherLang: migrateAllNhentaiToOtherLang:
UPDATE mangas UPDATE mangas
SET source = :nh SET source = :nh

View File

@ -0,0 +1,22 @@
CREATE TABLE excluded_scanlators(
manga_id INTEGER NOT NULL,
scanlator TEXT NOT NULL,
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE
);
CREATE INDEX excluded_scanlators_manga_id_index ON excluded_scanlators(manga_id);
insert:
INSERT INTO excluded_scanlators(manga_id, scanlator)
VALUES (:mangaId, :scanlator);
remove:
DELETE FROM excluded_scanlators
WHERE manga_id = :mangaId
AND scanlator IN :scanlators;
getExcludedScanlatorsByMangaId:
SELECT scanlator
FROM excluded_scanlators
WHERE manga_id = :mangaId;

View File

@ -22,7 +22,7 @@ CREATE TABLE mangas(
chapter_flags INTEGER NOT NULL, chapter_flags INTEGER NOT NULL,
cover_last_modified INTEGER NOT NULL, cover_last_modified INTEGER NOT NULL,
date_added INTEGER NOT NULL, date_added INTEGER NOT NULL,
filtered_scanlators TEXT AS List<String>, filtered_scanlators TEXT,
update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0,
calculate_interval INTEGER DEFAULT 0 NOT NULL, calculate_interval INTEGER DEFAULT 0 NOT NULL,
last_modified_at INTEGER NOT NULL DEFAULT 0, last_modified_at INTEGER NOT NULL DEFAULT 0,
@ -131,8 +131,8 @@ WHERE favorite = 0 AND source IN :sourceIdsAND AND _id NOT IN (
); );
insert: insert:
INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, filtered_scanlators, update_strategy, calculate_interval, last_modified_at) INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at)
VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :filteredScanlators, :updateStrategy, :calculateInterval, strftime('%s', 'now')); VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, strftime('%s', 'now'));
update: update:
UPDATE mangas SET UPDATE mangas SET
@ -153,7 +153,6 @@ UPDATE mangas SET
chapter_flags = coalesce(:chapterFlags, chapter_flags), chapter_flags = coalesce(:chapterFlags, chapter_flags),
cover_last_modified = coalesce(:coverLastModified, cover_last_modified), cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
date_added = coalesce(:dateAdded, date_added), date_added = coalesce(:dateAdded, date_added),
filtered_scanlators = coalesce(:filteredScanlators, filtered_scanlators),
update_strategy = coalesce(:updateStrategy, update_strategy), update_strategy = coalesce(:updateStrategy, update_strategy),
calculate_interval = coalesce(:calculateInterval, calculate_interval) calculate_interval = coalesce(:calculateInterval, calculate_interval)
WHERE _id = :mangaId; WHERE _id = :mangaId;

View File

@ -0,0 +1,44 @@
CREATE TABLE excluded_scanlators(
manga_id INTEGER NOT NULL,
scanlator TEXT NOT NULL,
FOREIGN KEY(manga_id) REFERENCES mangas (_id)
ON DELETE CASCADE
);
CREATE INDEX excluded_scanlators_manga_id_index ON excluded_scanlators(manga_id);
DROP VIEW IF EXISTS libraryView;
CREATE VIEW libraryView AS
SELECT
M.*,
coalesce(C.total, 0) AS totalCount,
coalesce(C.readCount, 0) AS readCount,
coalesce(C.latestUpload, 0) AS latestUpload,
coalesce(C.fetchedAt, 0) AS chapterFetchedAt,
coalesce(C.lastRead, 0) AS lastRead,
coalesce(C.bookmarkCount, 0) AS bookmarkCount,
coalesce(MC.category_id, 0) AS category
FROM mangas M
LEFT JOIN(
SELECT
chapters.manga_id,
count(*) AS total,
sum(read) AS readCount,
coalesce(max(chapters.date_upload), 0) AS latestUpload,
coalesce(max(history.last_read), 0) AS lastRead,
coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount
FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history
ON chapters._id = history.chapter_id
WHERE excluded_scanlators.scanlator IS NULL
GROUP BY chapters.manga_id
) AS C
ON M._id = C.manga_id
LEFT JOIN mangas_categories AS MC
ON MC.manga_id = M._id
WHERE M.favorite = 1;

View File

@ -19,8 +19,12 @@ LEFT JOIN(
coalesce(max(chapters.date_fetch), 0) AS fetchedAt, coalesce(max(chapters.date_fetch), 0) AS fetchedAt,
sum(chapters.bookmark) AS bookmarkCount sum(chapters.bookmark) AS bookmarkCount
FROM chapters FROM chapters
LEFT JOIN excluded_scanlators
ON chapters.manga_id = excluded_scanlators.manga_id
AND chapters.scanlator = excluded_scanlators.scanlator
LEFT JOIN history LEFT JOIN history
ON chapters._id = history.chapter_id ON chapters._id = history.chapter_id
WHERE excluded_scanlators.scanlator IS NULL
GROUP BY chapters.manga_id GROUP BY chapters.manga_id
) AS C ) AS C
ON M._id = C.manga_id ON M._id = C.manga_id

View File

@ -9,9 +9,9 @@ class GetChaptersByMangaId(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun await(mangaId: Long): List<Chapter> { suspend fun await(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter> {
return try { return try {
chapterRepository.getChapterByMangaId(mangaId) chapterRepository.getChapterByMangaId(mangaId, applyScanlatorFilter)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
emptyList() emptyList()

View File

@ -16,13 +16,21 @@ class GetMergedChaptersByMangaId(
private val getMergedReferencesById: GetMergedReferencesById, private val getMergedReferencesById: GetMergedReferencesById,
) { ) {
suspend fun await(mangaId: Long, dedupe: Boolean = true): List<Chapter> { suspend fun await(
return transformMergedChapters(getMergedReferencesById.await(mangaId), getFromDatabase(mangaId), dedupe) mangaId: Long,
dedupe: Boolean = true,
applyScanlatorFilter: Boolean = false,
): List<Chapter> {
return transformMergedChapters(getMergedReferencesById.await(mangaId), getFromDatabase(mangaId, applyScanlatorFilter), dedupe)
} }
suspend fun subscribe(mangaId: Long, dedupe: Boolean = true): Flow<List<Chapter>> { suspend fun subscribe(
mangaId: Long,
dedupe: Boolean = true,
applyScanlatorFilter: Boolean = false,
): Flow<List<Chapter>> {
return try { return try {
chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId) chapterRepository.getMergedChapterByMangaIdAsFlow(mangaId, applyScanlatorFilter)
.combine(getMergedReferencesById.subscribe(mangaId)) { chapters, references -> .combine(getMergedReferencesById.subscribe(mangaId)) { chapters, references ->
transformMergedChapters(references, chapters, dedupe) transformMergedChapters(references, chapters, dedupe)
} }
@ -32,16 +40,19 @@ class GetMergedChaptersByMangaId(
} }
} }
private suspend fun getFromDatabase(mangaId: Long): List<Chapter> { private suspend fun getFromDatabase(
mangaId: Long,
applyScanlatorFilter: Boolean = false,
): List<Chapter> {
return try { return try {
chapterRepository.getMergedChapterByMangaId(mangaId) chapterRepository.getMergedChapterByMangaId(mangaId, applyScanlatorFilter)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
emptyList() emptyList()
} }
} }
fun transformMergedChapters( private fun transformMergedChapters(
mangaReferences: List<MergedMangaReference>, mangaReferences: List<MergedMangaReference>,
chapterList: List<Chapter>, chapterList: List<Chapter>,
dedupe: Boolean, dedupe: Boolean,

View File

@ -14,21 +14,25 @@ interface ChapterRepository {
suspend fun removeChaptersWithIds(chapterIds: List<Long>) suspend fun removeChaptersWithIds(chapterIds: List<Long>)
suspend fun getChapterByMangaId(mangaId: Long): List<Chapter> suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter>
suspend fun getScanlatorsByMangaId(mangaId: Long): List<String>
fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow<List<String>>
suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter> suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List<Chapter>
suspend fun getChapterById(id: Long): Chapter? suspend fun getChapterById(id: Long): Chapter?
suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean = false): Flow<List<Chapter>>
suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter?
// SY --> // SY -->
suspend fun getChapterByUrl(url: String): List<Chapter> suspend fun getChapterByUrl(url: String): List<Chapter>
suspend fun getMergedChapterByMangaId(mangaId: Long): List<Chapter> suspend fun getMergedChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List<Chapter>
suspend fun getMergedChapterByMangaIdAsFlow(mangaId: Long): Flow<List<Chapter>> suspend fun getMergedChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean = false): Flow<List<Chapter>>
// SY <-- // SY <--
} }

View File

@ -29,7 +29,7 @@ class GetNextChapters(
// SY --> // SY -->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
val chapters = getMergedChaptersByMangaId.await(mangaId) val chapters = getMergedChaptersByMangaId.await(mangaId, applyScanlatorFilter = true)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {
@ -39,7 +39,7 @@ class GetNextChapters(
} }
} }
if (manga.isEhBasedManga()) { if (manga.isEhBasedManga()) {
val chapters = getChaptersByMangaId.await(mangaId) val chapters = getChaptersByMangaId.await(mangaId, applyScanlatorFilter = true)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {
@ -50,7 +50,7 @@ class GetNextChapters(
} }
// SY <-- // SY <--
val chapters = getChaptersByMangaId.await(mangaId) val chapters = getChaptersByMangaId.await(mangaId, applyScanlatorFilter = true)
.sortedWith(getChapterSort(manga, sortDescending = false)) .sortedWith(getChapterSort(manga, sortDescending = false))
return if (onlyUnread) { return if (onlyUnread) {

View File

@ -24,7 +24,7 @@ class FetchInterval(
} else { } else {
window window
} }
val chapters = getChaptersByMangaId.await(manga.id) val chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true)
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval( val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
chapters, chapters,
dateTime.zone, dateTime.zone,

View File

@ -12,10 +12,10 @@ class GetMangaWithChapters(
private val chapterRepository: ChapterRepository, private val chapterRepository: ChapterRepository,
) { ) {
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> { suspend fun subscribe(id: Long, applyScanlatorFilter: Boolean = false): Flow<Pair<Manga, List<Chapter>>> {
return combine( return combine(
mangaRepository.getMangaByIdAsFlow(id), mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id), chapterRepository.getChapterByMangaIdAsFlow(id, applyScanlatorFilter),
) { manga, chapters -> ) { manga, chapters ->
Pair(manga, chapters) Pair(manga, chapters)
} }
@ -25,7 +25,7 @@ class GetMangaWithChapters(
return mangaRepository.getMangaById(id) return mangaRepository.getMangaById(id)
} }
suspend fun awaitChapters(id: Long): List<Chapter> { suspend fun awaitChapters(id: Long, applyScanlatorFilter: Boolean = false): List<Chapter> {
return chapterRepository.getChapterByMangaId(id) return chapterRepository.getChapterByMangaId(id, applyScanlatorFilter)
} }
} }

View File

@ -1,17 +0,0 @@
package tachiyomi.domain.manga.interactor
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
class SetMangaFilteredScanlators(private val mangaRepository: MangaRepository) {
suspend fun awaitSetFilteredScanlators(manga: Manga, filteredScanlators: List<String>): Boolean {
return mangaRepository.update(
MangaUpdate(
id = manga.id,
filteredScanlators = filteredScanlators,
),
)
}
}

View File

@ -31,9 +31,6 @@ data class Manga(
val initialized: Boolean, val initialized: Boolean,
val lastModifiedAt: Long, val lastModifiedAt: Long,
val favoriteModifiedAt: Long?, val favoriteModifiedAt: Long?,
// SY -->
val filteredScanlators: List<String>?,
// SY <--
) : Serializable { ) : Serializable {
// SY --> // SY -->
@ -152,9 +149,6 @@ data class Manga(
initialized = false, initialized = false,
lastModifiedAt = 0L, lastModifiedAt = 0L,
favoriteModifiedAt = null, favoriteModifiedAt = null,
// SY -->
filteredScanlators = null,
// SY <--
) )
// SY --> // SY -->

View File

@ -52,8 +52,5 @@ fun Manga.toMangaUpdate(): MangaUpdate {
thumbnailUrl = thumbnailUrl, thumbnailUrl = thumbnailUrl,
updateStrategy = updateStrategy, updateStrategy = updateStrategy,
initialized = initialized, initialized = initialized,
// SY -->
filteredScanlators = filteredScanlators,
// SY <--
) )
} }

View File

@ -434,7 +434,6 @@
<string name="short_title">Titre court</string> <string name="short_title">Titre court</string>
<string name="cover_image_file_type">Type de fichier d\'image de couverture</string> <string name="cover_image_file_type">Type de fichier d\'image de couverture</string>
<string name="thumbnail_image_file_type">Type de fichier d\'image miniature</string> <string name="thumbnail_image_file_type">Type de fichier d\'image miniature</string>
<string name="scanlator">Scanlator</string>
<string name="url">Url</string> <string name="url">Url</string>
<string name="uploader_capital">Uploader en majuscule</string> <string name="uploader_capital">Uploader en majuscule</string>
<string name="uploader">Uploader</string> <string name="uploader">Uploader</string>

View File

@ -545,7 +545,6 @@
<string name="short_title">Short Title</string> <string name="short_title">Short Title</string>
<string name="cover_image_file_type">Cover image file type</string> <string name="cover_image_file_type">Cover image file type</string>
<string name="thumbnail_image_file_type">Thumbnail image file type</string> <string name="thumbnail_image_file_type">Thumbnail image file type</string>
<string name="scanlator">Scanlator</string>
<string name="url">Url</string> <string name="url">Url</string>
<string name="uploader_capital">Uploader Capitalized</string> <string name="uploader_capital">Uploader Capitalized</string>
<string name="uploader">Uploader</string> <string name="uploader">Uploader</string>

View File

@ -515,7 +515,6 @@
<string name="short_title">Título curto</string> <string name="short_title">Título curto</string>
<string name="cover_image_file_type">Tipo arq. da capa</string> <string name="cover_image_file_type">Tipo arq. da capa</string>
<string name="thumbnail_image_file_type">Tipo arq. miniatura</string> <string name="thumbnail_image_file_type">Tipo arq. miniatura</string>
<string name="scanlator">Scanlator</string>
<string name="url">Url</string> <string name="url">Url</string>
<string name="uploader_capital">Uploader Capitalizado</string> <string name="uploader_capital">Uploader Capitalizado</string>
<string name="uploader">Uploader</string> <string name="uploader">Uploader</string>

View File

@ -616,7 +616,6 @@
<string name="short_title">Краткое название</string> <string name="short_title">Краткое название</string>
<string name="cover_image_file_type">Тип изображения обложки</string> <string name="cover_image_file_type">Тип изображения обложки</string>
<string name="thumbnail_image_file_type">Тип изображения миниатюры</string> <string name="thumbnail_image_file_type">Тип изображения миниатюры</string>
<string name="scanlator">Переводчик</string>
<string name="url">URL-адрес</string> <string name="url">URL-адрес</string>
<string name="uploader_capital">Имя загрузчика с заглавной буквы</string> <string name="uploader_capital">Имя загрузчика с заглавной буквы</string>
<string name="uploader">Имя загрузчика</string> <string name="uploader">Имя загрузчика</string>

View File

@ -582,7 +582,6 @@
<string name="short_title">短标题</string> <string name="short_title">短标题</string>
<string name="cover_image_file_type">转换图像类型</string> <string name="cover_image_file_type">转换图像类型</string>
<string name="thumbnail_image_file_type">缓存图像类型</string> <string name="thumbnail_image_file_type">缓存图像类型</string>
<string name="scanlator">扫译组</string>
<string name="uploader_capital">上传者名字</string> <string name="uploader_capital">上传者名字</string>
<string name="uploader">上传者</string> <string name="uploader">上传者</string>
<string name="rating_string">评分字符串</string> <string name="rating_string">评分字符串</string>

View File

@ -14,6 +14,7 @@
<string name="track">Tracking</string> <string name="track">Tracking</string>
<string name="delete_downloaded">Delete downloaded</string> <string name="delete_downloaded">Delete downloaded</string>
<string name="history">History</string> <string name="history">History</string>
<string name="scanlator">Scanlator</string>
<!-- Screen titles --> <!-- Screen titles -->
<string name="label_more">More</string> <string name="label_more">More</string>
@ -702,6 +703,8 @@
<string name="set_chapter_settings_as_default">Set as default</string> <string name="set_chapter_settings_as_default">Set as default</string>
<string name="no_chapters_error">No chapters found</string> <string name="no_chapters_error">No chapters found</string>
<string name="are_you_sure">Are you sure?</string> <string name="are_you_sure">Are you sure?</string>
<string name="exclude_scanlators">Exclude scanlators</string>
<string name="no_scanlators_found">No scanlators found</string>
<!-- Tracking Screen --> <!-- Tracking Screen -->
<string name="manga_tracking_tab">Tracking</string> <string name="manga_tracking_tab">Tracking</string>

View File

@ -613,7 +613,6 @@
<string name="short_title">Short Title</string> <string name="short_title">Short Title</string>
<string name="cover_image_file_type">Cover image file type</string> <string name="cover_image_file_type">Cover image file type</string>
<string name="thumbnail_image_file_type">Thumbnail image file type</string> <string name="thumbnail_image_file_type">Thumbnail image file type</string>
<string name="scanlator">Scanlator</string>
<string name="url">Url</string> <string name="url">Url</string>
<string name="uploader_capital">Uploader Capitalized</string> <string name="uploader_capital">Uploader Capitalized</string>
<string name="uploader">Uploader</string> <string name="uploader">Uploader</string>