diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dde03bae5..c82161ddd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ android { defaultConfig { applicationId = "xyz.jmir.tachiyomi.mi" - versionCode = 108 + versionCode = 109 versionName = "0.14.7" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index fe461a676..1e23f9e64 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -4,6 +4,8 @@ import eu.kanade.domain.download.anime.interactor.DeleteEpisodeDownload import eu.kanade.domain.download.manga.interactor.DeleteChapterDownload import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags import eu.kanade.domain.entries.anime.interactor.UpdateAnime +import eu.kanade.domain.entries.manga.interactor.GetExcludedScanlators +import eu.kanade.domain.entries.manga.interactor.SetExcludedScanlators import eu.kanade.domain.entries.manga.interactor.SetMangaViewerFlags import eu.kanade.domain.entries.manga.interactor.UpdateManga import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionLanguages @@ -12,6 +14,7 @@ import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionsByType import eu.kanade.domain.extension.manga.interactor.GetExtensionSources import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionsByType +import eu.kanade.domain.items.chapter.interactor.GetAvailableScanlators import eu.kanade.domain.items.chapter.interactor.SetReadStatus import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.items.episode.interactor.SetSeenStatus @@ -224,6 +227,8 @@ class DomainModule : InjektModule { addFactory { NetworkToLocalManga(get()) } addFactory { UpdateManga(get(), get()) } addFactory { SetMangaCategories(get()) } + addFactory { GetExcludedScanlators(get()) } + addFactory { SetExcludedScanlators(get()) } addSingletonFactory { ReleaseServiceImpl(get(), get()) } addFactory { GetApplicationRelease(get(), get()) } @@ -264,7 +269,8 @@ class DomainModule : InjektModule { addFactory { UpdateChapter(get()) } addFactory { SetReadStatus(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 { AnimeHistoryRepositoryImpl(get()) } addFactory { GetAnimeHistory(get()) } diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/GetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/GetExcludedScanlators.kt new file mode 100644 index 000000000..2039190ed --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/GetExcludedScanlators.kt @@ -0,0 +1,24 @@ +package eu.kanade.domain.entries.manga.interactor + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import tachiyomi.data.handlers.manga.MangaDatabaseHandler + +class GetExcludedScanlators( + private val handler: MangaDatabaseHandler, +) { + + suspend fun await(mangaId: Long): Set { + return handler.awaitList { + excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId) + } + .toSet() + } + + fun subscribe(mangaId: Long): Flow> { + return handler.subscribeToList { + excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId) + } + .map { it.toSet() } + } +} diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetExcludedScanlators.kt b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetExcludedScanlators.kt new file mode 100644 index 000000000..7285f67ba --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetExcludedScanlators.kt @@ -0,0 +1,22 @@ +package eu.kanade.domain.entries.manga.interactor + +import tachiyomi.data.handlers.manga.MangaDatabaseHandler + +class SetExcludedScanlators( + private val handler: MangaDatabaseHandler, +) { + + suspend fun await(mangaId: Long, excludedScanlators: Set) { + 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) + } + } +} diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetMangaViewerFlags.kt b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetMangaViewerFlags.kt index 399638589..939e1af97 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetMangaViewerFlags.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/SetMangaViewerFlags.kt @@ -1,7 +1,7 @@ package eu.kanade.domain.entries.manga.interactor -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.domain.entries.manga.model.MangaUpdate import tachiyomi.domain.entries.manga.repository.MangaRepository @@ -14,7 +14,7 @@ class SetMangaViewerFlags( mangaRepository.updateManga( MangaUpdate( id = id, - viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()), + viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()), ), ) } @@ -24,7 +24,7 @@ class SetMangaViewerFlags( mangaRepository.updateManga( MangaUpdate( id = id, - viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()), + viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()), ), ) } diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt index 28c8fb9f1..6bc467d65 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt @@ -3,8 +3,8 @@ package eu.kanade.domain.entries.manga.model import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus import tachiyomi.core.preference.TriState @@ -14,11 +14,11 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get // TODO: move these into the domain model -val Manga.readingModeType: Long - get() = viewerFlags and ReadingModeType.MASK.toLong() +val Manga.readingMode: Long + get() = viewerFlags and ReadingMode.MASK.toLong() -val Manga.orientationType: Long - get() = viewerFlags and OrientationType.MASK.toLong() +val Manga.readerOrientation: Long + get() = viewerFlags and ReaderOrientation.MASK.toLong() val Manga.downloadedFilter: TriState get() { diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/GetAvailableScanlators.kt b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/GetAvailableScanlators.kt new file mode 100644 index 000000000..80621758e --- /dev/null +++ b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/GetAvailableScanlators.kt @@ -0,0 +1,24 @@ +package eu.kanade.domain.items.chapter.interactor + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import tachiyomi.domain.items.chapter.repository.ChapterRepository + +class GetAvailableScanlators( + private val repository: ChapterRepository, +) { + + private fun List.cleanupAvailableScanlators(): Set { + return mapNotNull { it.ifBlank { null } }.toSet() + } + + suspend fun await(mangaId: Long): Set { + return repository.getScanlatorsByMangaId(mangaId) + .cleanupAvailableScanlators() + } + + fun subscribe(mangaId: Long): Flow> { + return repository.getScanlatorsByMangaIdAsFlow(mangaId) + .map { it.cleanupAvailableScanlators() } + } +} diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt index 928792694..575d7a3a1 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt @@ -1,5 +1,6 @@ package eu.kanade.domain.items.chapter.interactor +import eu.kanade.domain.entries.manga.interactor.GetExcludedScanlators import eu.kanade.domain.entries.manga.interactor.UpdateManga import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.domain.items.chapter.model.copyFromSChapter @@ -33,6 +34,7 @@ class SyncChaptersWithSource( private val updateManga: UpdateManga, private val updateChapter: UpdateChapter, private val getChaptersByMangaId: GetChaptersByMangaId, + private val getExcludedScanlators: GetExcludedScanlators, ) { /** @@ -218,6 +220,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 + } } } diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt b/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt index fa8adf108..c92d7471f 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt @@ -22,7 +22,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter { url = sChapter.url, dateUpload = sChapter.date_upload, chapterNumber = sChapter.chapter_number.toDouble(), - scanlator = sChapter.scanlator?.ifBlank { null }, + scanlator = sChapter.scanlator?.ifBlank { null }?.trim(), ) } diff --git a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt b/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt index fad8fd650..def969a39 100644 --- a/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/EmptyScreen.kt @@ -5,13 +5,13 @@ import androidx.compose.material.icons.outlined.HelpOutline import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction -import tachiyomi.presentation.core.util.ThemePreviews -@ThemePreviews +@PreviewLightDark @Composable private fun NoActionPreview() { TachiyomiTheme { @@ -23,7 +23,7 @@ private fun NoActionPreview() { } } -@ThemePreviews +@PreviewLightDark @Composable private fun WithActionPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt index 537f7fd1e..98a4e96d5 100644 --- a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt @@ -14,13 +14,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.CrashLogUtil import kotlinx.coroutines.launch import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.screens.InfoScreen -import tachiyomi.presentation.core.util.ThemePreviews @Composable fun CrashScreen( @@ -63,7 +63,7 @@ fun CrashScreen( } } -@ThemePreviews +@PreviewLightDark @Composable private fun CrashScreenPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index 5f39776bb..97dc07593 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -5,11 +5,9 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues @@ -29,9 +27,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -51,7 +47,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny @@ -69,6 +64,7 @@ import eu.kanade.presentation.entries.anime.components.AnimeEpisodeListItem import eu.kanade.presentation.entries.anime.components.AnimeInfoBox import eu.kanade.presentation.entries.anime.components.EpisodeDownloadAction import eu.kanade.presentation.entries.anime.components.ExpandableAnimeDescription +import eu.kanade.presentation.entries.anime.components.MissingEpisodeCountListItem import eu.kanade.presentation.entries.anime.components.NextEpisodeAiringListItem import eu.kanade.presentation.util.formatEpisodeNumber import eu.kanade.tachiyomi.R @@ -95,7 +91,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrollingUp -import tachiyomi.presentation.core.util.secondaryItemAlpha import java.text.DateFormat import java.util.Date import java.util.concurrent.TimeUnit @@ -915,25 +910,7 @@ private fun LazyListScope.sharedEpisodeItems( when (item) { is EpisodeList.MissingCount -> { - Row( - modifier = Modifier.padding( - horizontal = MaterialTheme.padding.medium, - vertical = MaterialTheme.padding.small, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), - ) { - HorizontalDivider(modifier = Modifier.weight(1f)) - Text( - text = pluralStringResource( - id = R.plurals.missing_items, - count = item.count, - item.count, - ), - modifier = Modifier.secondaryItemAlpha(), - ) - HorizontalDivider(modifier = Modifier.weight(1f)) - } + MissingEpisodeCountListItem(count = item.count) } is EpisodeList.Item -> { AnimeEpisodeListItem( diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/MissingEpisodeCountListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/MissingEpisodeCountListItem.kt new file mode 100644 index 000000000..12c3bf909 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/MissingEpisodeCountListItem.kt @@ -0,0 +1,52 @@ +package eu.kanade.presentation.entries.anime.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.util.secondaryItemAlpha + +@Composable +fun MissingEpisodeCountListItem( + count: Int, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ) + .secondaryItemAlpha(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), + ) { + HorizontalDivider(modifier = Modifier.weight(1f)) + Text( + text = pluralStringResource(id = R.plurals.missing_items, count = count, count), + style = MaterialTheme.typography.labelMedium, + ) + HorizontalDivider(modifier = Modifier.weight(1f)) + } +} + +@PreviewLightDark +@Composable +private fun Preview() { + TachiyomiTheme { + Surface { + MissingEpisodeCountListItem(count = 42) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt index 9b0ba3321..98189c53f 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/ChapterSettingsDialog.kt @@ -1,12 +1,20 @@ package eu.kanade.presentation.entries.manga +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +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 import androidx.compose.runtime.Composable @@ -14,6 +22,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -28,6 +37,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( @@ -36,6 +46,8 @@ 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, @@ -88,6 +100,8 @@ fun ChapterSettingsDialog( onUnreadFilterChanged = onUnreadFilterChanged, bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED, onBookmarkedFilterChanged = onBookmarkedFilterChanged, + scanlatorFilterActive = scanlatorFilterActive, + onScanlatorFilterClicked = onScanlatorFilterClicked, ) } 1 -> { @@ -116,6 +130,8 @@ private fun FilterPage( onUnreadFilterChanged: (TriState) -> Unit, bookmarkedFilter: TriState, onBookmarkedFilterChanged: (TriState) -> Unit, + scanlatorFilterActive: Boolean, + onScanlatorFilterClicked: (() -> Unit), ) { TriStateItem( label = stringResource(R.string.label_downloaded), @@ -132,6 +148,39 @@ private fun FilterPage( state = bookmarkedFilter, onClick = onBookmarkedFilterChanged, ) + ScanlatorFilterItem( + active = scanlatorFilterActive, + onClick = onScanlatorFilterClicked, + ) +} + +@Composable +fun ScanlatorFilterItem( + active: Boolean, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .padding(horizontal = TabbedDialogPaddings.Horizontal, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp), + ) { + Icon( + imageVector = Icons.Outlined.PeopleAlt, + contentDescription = null, + tint = if (active) { + MaterialTheme.colorScheme.active + } else { + LocalContentColor.current + }, + ) + Text( + text = stringResource(R.string.scanlator), + style = MaterialTheme.typography.bodyMedium, + ) + } } @Composable diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt index 7db2f6a21..85acde186 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt @@ -5,11 +5,9 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.asPaddingValues @@ -28,9 +26,7 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text @@ -48,14 +44,12 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.pluralStringResource 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 cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.entries.manga.model.chaptersFiltered import eu.kanade.presentation.entries.DownloadAction import eu.kanade.presentation.entries.EntryBottomActionMenu import eu.kanade.presentation.entries.EntryScreenItem @@ -66,6 +60,7 @@ import eu.kanade.presentation.entries.manga.components.ExpandableMangaDescriptio import eu.kanade.presentation.entries.manga.components.MangaActionRow import eu.kanade.presentation.entries.manga.components.MangaChapterListItem import eu.kanade.presentation.entries.manga.components.MangaInfoBox +import eu.kanade.presentation.entries.manga.components.MissingChapterCountListItem import eu.kanade.presentation.util.formatChapterNumber import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload @@ -89,7 +84,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrollingUp -import tachiyomi.presentation.core.util.secondaryItemAlpha import java.text.DateFormat import java.util.Date @@ -328,7 +322,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, @@ -586,7 +580,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, @@ -818,25 +812,7 @@ private fun LazyListScope.sharedChapterItems( when (item) { is ChapterList.MissingCount -> { - Row( - modifier = Modifier.padding( - horizontal = MaterialTheme.padding.medium, - vertical = MaterialTheme.padding.small, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), - ) { - HorizontalDivider(modifier = Modifier.weight(1f)) - Text( - text = pluralStringResource( - id = R.plurals.missing_items, - count = item.count, - item.count, - ), - modifier = Modifier.secondaryItemAlpha(), - ) - HorizontalDivider(modifier = Modifier.weight(1f)) - } + MissingChapterCountListItem(count = item.count) } is ChapterList.Item -> { MangaChapterListItem( diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MissingChapterCountListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MissingChapterCountListItem.kt new file mode 100644 index 000000000..e35831c33 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MissingChapterCountListItem.kt @@ -0,0 +1,52 @@ +package eu.kanade.presentation.entries.manga.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.util.secondaryItemAlpha + +@Composable +fun MissingChapterCountListItem( + count: Int, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .padding( + horizontal = MaterialTheme.padding.medium, + vertical = MaterialTheme.padding.small, + ) + .secondaryItemAlpha(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), + ) { + HorizontalDivider(modifier = Modifier.weight(1f)) + Text( + text = pluralStringResource(id = R.plurals.missing_items, count = count, count), + style = MaterialTheme.typography.labelMedium, + ) + HorizontalDivider(modifier = Modifier.weight(1f)) + } +} + +@PreviewLightDark +@Composable +private fun Preview() { + TachiyomiTheme { + Surface { + MissingChapterCountListItem(count = 42) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt new file mode 100644 index 000000000..71a58c7cf --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt @@ -0,0 +1,132 @@ +package eu.kanade.presentation.entries.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.TextButton +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +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.padding +import tachiyomi.presentation.core.util.isScrolledToEnd +import tachiyomi.presentation.core.util.isScrolledToStart + +@Composable +fun ScanlatorFilterDialog( + availableScanlators: Set, + excludedScanlators: Set, + onDismissRequest: () -> Unit, + onConfirm: (Set) -> 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 = { + if (sortedAvailableScanlators.isEmpty()) { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(R.string.action_cancel)) + } + } else { + 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)) + } + } + } + }, + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt index 8d9abbd7d..ce51547bf 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt @@ -11,11 +11,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.LabeledCheckbox -import tachiyomi.presentation.core.util.ThemePreviews import kotlin.random.Random @Composable @@ -94,7 +94,7 @@ fun HistoryDeleteAllDialog( ) } -@ThemePreviews +@PreviewLightDark @Composable private fun HistoryDeleteDialogPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryItem.kt b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryItem.kt index c87d74bc5..086f79c96 100644 --- a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryItem.kt +++ b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryItem.kt @@ -11,6 +11,7 @@ import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.presentation.entries.ItemCover @@ -28,7 +30,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.toTimestampString import tachiyomi.domain.history.anime.model.AnimeHistoryWithRelations import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews private val HistoryItemHeight = 96.dp @@ -94,18 +95,20 @@ fun AnimeHistoryItem( } } -@ThemePreviews +@PreviewLightDark @Composable private fun HistoryItemPreviews( @PreviewParameter(AnimeHistoryWithRelationsProvider::class) historyWithRelations: AnimeHistoryWithRelations, ) { TachiyomiTheme { - AnimeHistoryItem( - history = historyWithRelations, - onClickCover = {}, - onClickResume = {}, - onClickDelete = {}, - ) + Surface { + AnimeHistoryItem( + history = historyWithRelations, + onClickCover = {}, + onClickResume = {}, + onClickDelete = {}, + ) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt index 46d880ff5..d41c3f24d 100644 --- a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.theme.TachiyomiTheme @@ -17,7 +18,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import tachiyomi.presentation.core.util.ThemePreviews import java.util.Date @Composable @@ -70,7 +70,7 @@ sealed interface AnimeHistoryUiModel { data class Item(val item: AnimeHistoryWithRelations) : AnimeHistoryUiModel } -@ThemePreviews +@PreviewLightDark @Composable internal fun HistoryScreenPreviews( @PreviewParameter(AnimeHistoryScreenModelStateProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryItem.kt b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryItem.kt index 21147168d..f6a2eb9f0 100644 --- a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryItem.kt +++ b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryItem.kt @@ -11,6 +11,7 @@ import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -19,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.presentation.entries.ItemCover @@ -28,7 +30,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.lang.toTimestampString import tachiyomi.domain.history.manga.model.MangaHistoryWithRelations import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews private val HISTORY_ITEM_HEIGHT = 96.dp @@ -94,18 +95,20 @@ fun MangaHistoryItem( } } -@ThemePreviews +@PreviewLightDark @Composable internal fun HistoryItemPreviews( @PreviewParameter(MangaHistoryWithRelationsProvider::class) historyWithRelations: MangaHistoryWithRelations, ) { TachiyomiTheme { - MangaHistoryItem( - history = historyWithRelations, - onClickCover = {}, - onClickResume = {}, - onClickDelete = {}, - ) + Surface { + MangaHistoryItem( + history = historyWithRelations, + onClickCover = {}, + onClickResume = {}, + onClickDelete = {}, + ) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt index eaf6edbfa..d816be7d6 100644 --- a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.theme.TachiyomiTheme @@ -17,7 +18,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import tachiyomi.presentation.core.util.ThemePreviews import uy.kohesive.injekt.api.get import java.util.Date @@ -71,7 +71,7 @@ sealed interface MangaHistoryUiModel { data class Item(val item: MangaHistoryWithRelations) : MangaHistoryUiModel } -@ThemePreviews +@PreviewLightDark @Composable internal fun HistoryScreenPreviews( @PreviewParameter(MangaHistoryScreenModelStateProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/library/LibraryBadges.kt b/app/src/main/java/eu/kanade/presentation/library/LibraryBadges.kt index 78e44ec5f..5dc54e5ba 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibraryBadges.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibraryBadges.kt @@ -5,9 +5,9 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Folder import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme import tachiyomi.presentation.core.components.Badge -import tachiyomi.presentation.core.util.ThemePreviews @Composable fun DownloadsBadge(count: Long) { @@ -47,7 +47,7 @@ fun LanguageBadge( } } -@ThemePreviews +@PreviewLightDark @Composable private fun BadgePreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt index f4a0bf20f..d6dc9f94c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/NewUpdateScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.tooling.preview.PreviewLightDark import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.material3.Material3RichText @@ -24,7 +25,6 @@ import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.screens.InfoScreen -import tachiyomi.presentation.core.util.ThemePreviews @Composable fun NewUpdateScreen( @@ -67,7 +67,7 @@ fun NewUpdateScreen( } } -@ThemePreviews +@PreviewLightDark @Composable private fun NewUpdateScreenPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index a5ab3d4bb..3c27cb4a3 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -10,9 +10,9 @@ import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import eu.kanade.presentation.more.settings.Preference import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -32,7 +32,7 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = readerPref.defaultReadingMode(), title = stringResource(R.string.pref_viewer_type), - entries = ReadingModeType.entries.drop(1) + entries = ReadingMode.entries.drop(1) .associate { it.flagValue to stringResource(it.stringRes) }, ), Preference.PreferenceItem.ListPreference( @@ -88,7 +88,7 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.ListPreference( pref = readerPreferences.defaultOrientationType(), title = stringResource(R.string.pref_rotation_type), - entries = OrientationType.entries.drop(1) + entries = ReaderOrientation.entries.drop(1) .associate { it.flagValue to stringResource(it.stringRes) }, ), Preference.PreferenceItem.ListPreference( diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt index 28b538215..f51b8b364 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import eu.kanade.domain.ui.model.AppTheme import eu.kanade.presentation.entries.ItemCover @@ -46,7 +47,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.secondaryItemAlpha @Composable @@ -249,15 +249,17 @@ fun AppThemePreviewItem( } } -@ThemePreviews +@PreviewLightDark @Composable private fun AppThemesListPreview() { var appTheme by remember { mutableStateOf(AppTheme.DEFAULT) } TachiyomiTheme { - AppThemesList( - currentTheme = appTheme, - amoled = false, - onItemClick = { appTheme = it }, - ) + Surface { + AppThemesList( + currentTheme = appTheme, + amoled = false, + onItemClick = { appTheme = it }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt index 1183414e2..2dbb54be0 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/InfoWidget.kt @@ -12,10 +12,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.secondaryItemAlpha @Composable @@ -40,7 +40,7 @@ internal fun InfoWidget(text: String) { } } -@ThemePreviews +@PreviewLightDark @Composable private fun InfoWidgetPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/SwitchPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/SwitchPreferenceWidget.kt index 7de5c68d3..bc026d3ba 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/SwitchPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/SwitchPreferenceWidget.kt @@ -9,8 +9,8 @@ import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme -import tachiyomi.presentation.core.util.ThemePreviews @Composable fun SwitchPreferenceWidget( @@ -37,7 +37,7 @@ fun SwitchPreferenceWidget( ) } -@ThemePreviews +@PreviewLightDark @Composable private fun SwitchPreferenceWidgetPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt index bd8ac4593..05bd7f85d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TextPreferenceWidget.kt @@ -12,8 +12,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.PreviewLightDark import eu.kanade.presentation.theme.TachiyomiTheme -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.secondaryItemAlpha @Composable @@ -59,7 +59,7 @@ fun TextPreferenceWidget( ) } -@ThemePreviews +@PreviewLightDark @Composable private fun TextPreferenceWidgetPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt index 3aefdbffd..b485526ef 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.common.io.Files.append @@ -45,7 +46,6 @@ import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import tachiyomi.domain.items.service.calculateChapterGap -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.secondaryItemAlpha @Composable @@ -317,7 +317,7 @@ private val FakeChapterLongTitle = previewChapter( chapterNumber = 1f, ) -@ThemePreviews +@PreviewLightDark @Composable private fun TransitionTextPreview() { TachiyomiTheme { @@ -334,7 +334,7 @@ private fun TransitionTextPreview() { } } -@ThemePreviews +@PreviewLightDark @Composable private fun TransitionTextLongTitlePreview() { TachiyomiTheme { @@ -351,7 +351,7 @@ private fun TransitionTextLongTitlePreview() { } } -@ThemePreviews +@PreviewLightDark @Composable private fun TransitionTextWithGapPreview() { TachiyomiTheme { @@ -368,7 +368,7 @@ private fun TransitionTextWithGapPreview() { } } -@ThemePreviews +@PreviewLightDark @Composable private fun TransitionTextNoNextPreview() { TachiyomiTheme { @@ -382,7 +382,7 @@ private fun TransitionTextNoNextPreview() { } } -@ThemePreviews +@PreviewLightDark @Composable private fun TransitionTextNoPreviousPreview() { TachiyomiTheme { diff --git a/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt similarity index 51% rename from app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt rename to app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt index 62df9264b..d5b5ef15e 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/OrientationModeSelectDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/OrientationSelectDialog.kt @@ -1,46 +1,48 @@ package eu.kanade.presentation.reader -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.unit.dp -import eu.kanade.domain.entries.manga.model.orientationType +import androidx.compose.ui.tooling.preview.PreviewLightDark +import eu.kanade.domain.entries.manga.model.readerOrientation import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.presentation.reader.components.ModeSelectionDialog import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import tachiyomi.presentation.core.components.SettingsIconGrid import tachiyomi.presentation.core.components.material.IconToggleButton -import tachiyomi.presentation.core.util.ThemePreviews -private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it } +private val ReaderOrientationsWithoutDefault = ReaderOrientation.entries - ReaderOrientation.DEFAULT @Composable -fun OrientationModeSelectDialog( +fun OrientationSelectDialog( onDismissRequest: () -> Unit, screenModel: ReaderSettingsScreenModel, onChange: (Int) -> Unit, ) { val manga by screenModel.mangaFlow.collectAsState() - val orientationType = remember(manga) { - OrientationType.fromPreference( - manga?.orientationType?.toInt(), + val orientation = remember(manga) { + ReaderOrientation.fromPreference( + manga?.readerOrientation?.toInt(), ) } AdaptiveSheet(onDismissRequest = onDismissRequest) { DialogContent( - orientationType = orientationType, + orientation = orientation, onChangeOrientation = { screenModel.onChangeOrientation(it) onChange(it.stringRes) @@ -52,16 +54,25 @@ fun OrientationModeSelectDialog( @Composable private fun DialogContent( - orientationType: OrientationType, - onChangeOrientation: (OrientationType) -> Unit, + orientation: ReaderOrientation, + onChangeOrientation: (ReaderOrientation) -> Unit, ) { - Box(modifier = Modifier.padding(vertical = 16.dp)) { + var selected by remember { mutableStateOf(orientation) } + + ModeSelectionDialog( + onUseDefault = { + onChangeOrientation( + ReaderOrientation.DEFAULT, + ) + }.takeIf { orientation != ReaderOrientation.DEFAULT }, + onApply = { onChangeOrientation(selected) }, + ) { SettingsIconGrid(R.string.rotation_type) { - items(OrientationType.entries) { mode -> + items(ReaderOrientationsWithoutDefault) { mode -> IconToggleButton( - checked = mode == orientationType, + checked = mode == selected, onCheckedChange = { - onChangeOrientation(mode) + selected = (mode) }, modifier = Modifier.fillMaxWidth(), imageVector = ImageVector.vectorResource(mode.iconRes), @@ -72,13 +83,22 @@ private fun DialogContent( } } -@ThemePreviews +@PreviewLightDark @Composable private fun DialogContentPreview() { TachiyomiTheme { - DialogContent( - orientationType = OrientationType.DEFAULT, - onChangeOrientation = {}, - ) + Surface { + Column { + DialogContent( + orientation = ReaderOrientation.DEFAULT, + onChangeOrientation = {}, + ) + + DialogContent( + orientation = ReaderOrientation.FREE, + onChangeOrientation = {}, + ) + } + } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt b/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt index 31319744b..48998d165 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.reader import androidx.compose.foundation.layout.Box import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -9,9 +10,9 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.sp import eu.kanade.presentation.theme.TachiyomiTheme -import tachiyomi.presentation.core.util.ThemePreviews @Composable fun PageIndicatorText( @@ -48,10 +49,12 @@ fun PageIndicatorText( } } -@ThemePreviews +@PreviewLightDark @Composable private fun PageIndicatorTextPreview() { TachiyomiTheme { - PageIndicatorText(currentPage = 10, totalPages = 69) + Surface { + PageIndicatorText(currentPage = 10, totalPages = 69) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt index 9b0699210..956e1c849 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ReadingModeSelectDialog.kt @@ -1,30 +1,31 @@ package eu.kanade.presentation.reader -import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.items -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import eu.kanade.domain.entries.manga.model.readingModeType +import androidx.compose.ui.tooling.preview.PreviewLightDark +import eu.kanade.domain.entries.manga.model.readingMode import eu.kanade.presentation.components.AdaptiveSheet +import eu.kanade.presentation.reader.components.ModeSelectionDialog import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.presentation.core.components.SettingsIconGrid import tachiyomi.presentation.core.components.material.IconToggleButton -import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews -private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it } +private val ReadingModesWithoutDefault = ReadingMode.entries - ReadingMode.DEFAULT @Composable fun ReadingModeSelectDialog( @@ -34,8 +35,8 @@ fun ReadingModeSelectDialog( ) { val manga by screenModel.mangaFlow.collectAsState() val readingMode = remember(manga) { - ReadingModeType.fromPreference( - manga?.readingModeType?.toInt(), + ReadingMode.fromPreference( + manga?.readingMode?.toInt(), ) } @@ -53,16 +54,21 @@ fun ReadingModeSelectDialog( @Composable private fun DialogContent( - readingMode: ReadingModeType, - onChangeReadingMode: (ReadingModeType) -> Unit, + readingMode: ReadingMode, + onChangeReadingMode: (ReadingMode) -> Unit, ) { - Box(modifier = Modifier.padding(vertical = MaterialTheme.padding.medium)) { + var selected by remember { mutableStateOf(readingMode) } + + ModeSelectionDialog( + onUseDefault = { onChangeReadingMode(ReadingMode.DEFAULT) }.takeIf { readingMode != ReadingMode.DEFAULT }, + onApply = { onChangeReadingMode(selected) }, + ) { SettingsIconGrid(R.string.pref_category_reading_mode) { - items(ReadingModeType.entries) { mode -> + items(ReadingModesWithoutDefault) { mode -> IconToggleButton( - checked = mode == readingMode, + checked = mode == selected, onCheckedChange = { - onChangeReadingMode(mode) + selected = (mode) }, modifier = Modifier.fillMaxWidth(), imageVector = ImageVector.vectorResource(mode.iconRes), @@ -73,13 +79,22 @@ private fun DialogContent( } } -@ThemePreviews +@PreviewLightDark @Composable private fun DialogContentPreview() { TachiyomiTheme { - DialogContent( - readingMode = ReadingModeType.DEFAULT, - onChangeReadingMode = {}, - ) + Surface { + Column { + DialogContent( + readingMode = ReadingMode.DEFAULT, + onChangeReadingMode = {}, + ) + + DialogContent( + readingMode = ReadingMode.LEFT_TO_RIGHT, + onChangeReadingMode = {}, + ) + } + } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt b/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt index b16ef8566..726095a53 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/appbars/BottomReaderBar.kt @@ -17,16 +17,16 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode @Composable fun BottomReaderBar( backgroundColor: Color, - readingMode: ReadingModeType, + readingMode: ReadingMode, onClickReadingMode: () -> Unit, - orientationMode: OrientationType, - onClickOrientationMode: () -> Unit, + orientation: ReaderOrientation, + onClickOrientation: () -> Unit, cropEnabled: Boolean, onClickCropBorder: () -> Unit, onClickSettings: () -> Unit, @@ -46,19 +46,17 @@ fun BottomReaderBar( ) } - IconButton(onClick = onClickCropBorder) { + IconButton(onClick = onClickOrientation) { Icon( - painter = painterResource( - if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp, - ), - contentDescription = stringResource(R.string.pref_crop_borders), + painter = painterResource(orientation.iconRes), + contentDescription = stringResource(R.string.rotation_type), ) } - IconButton(onClick = onClickOrientationMode) { + IconButton(onClick = onClickCropBorder) { Icon( - painter = painterResource(orientationMode.iconRes), - contentDescription = stringResource(R.string.pref_rotation_type), + painter = painterResource(if (cropEnabled) R.drawable.ic_crop_24dp else R.drawable.ic_crop_off_24dp), + contentDescription = stringResource(R.string.pref_crop_borders), ) } diff --git a/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt b/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt index 7b360b04d..aff4c8c3f 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt @@ -23,9 +23,10 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions +import eu.kanade.presentation.reader.components.ChapterNavigator import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.viewer.Viewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer @@ -54,10 +55,10 @@ fun ReaderAppBars( totalPages: Int, onSliderValueChange: (Int) -> Unit, - readingMode: ReadingModeType, + readingMode: ReadingMode, onClickReadingMode: () -> Unit, - orientationMode: OrientationType, - onClickOrientationMode: () -> Unit, + orientation: ReaderOrientation, + onClickOrientation: () -> Unit, cropEnabled: Boolean, onClickCropBorder: () -> Unit, onClickSettings: () -> Unit, @@ -155,8 +156,8 @@ fun ReaderAppBars( backgroundColor = backgroundColor, readingMode = readingMode, onClickReadingMode = onClickReadingMode, - orientationMode = orientationMode, - onClickOrientationMode = onClickOrientationMode, + orientation = orientation, + onClickOrientation = onClickOrientation, cropEnabled = cropEnabled, onClickCropBorder = onClickCropBorder, onClickSettings = onClickSettings, diff --git a/app/src/main/java/eu/kanade/presentation/reader/appbars/ChapterNavigator.kt b/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt similarity index 99% rename from app/src/main/java/eu/kanade/presentation/reader/appbars/ChapterNavigator.kt rename to app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt index 30eb213f2..e5f897a38 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/appbars/ChapterNavigator.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/components/ChapterNavigator.kt @@ -1,4 +1,4 @@ -package eu.kanade.presentation.reader.appbars +package eu.kanade.presentation.reader.components import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource diff --git a/app/src/main/java/eu/kanade/presentation/reader/components/ModeSelectionDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/components/ModeSelectionDialog.kt new file mode 100644 index 000000000..045155b69 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/reader/components/ModeSelectionDialog.kt @@ -0,0 +1,89 @@ +package eu.kanade.presentation.reader.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.unit.dp +import eu.kanade.presentation.theme.TachiyomiTheme +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.SettingsItemsPaddings + +@Composable +fun ModeSelectionDialog( + onApply: () -> Unit, + onUseDefault: (() -> Unit)? = null, + content: @Composable () -> Unit, +) { + Box(modifier = Modifier.padding(vertical = 16.dp)) { + Column { + content() + + Row( + modifier = Modifier.padding( + horizontal = SettingsItemsPaddings.Horizontal, + ), + ) { + onUseDefault?.let { + OutlinedButton(onClick = it) { + Text(text = stringResource(R.string.action_revert_to_default)) + } + } + + Spacer(modifier = Modifier.weight(1f)) + + FilledTonalButton( + onClick = onApply, + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Check, + contentDescription = null, + ) + Text(text = stringResource(R.string.action_apply)) + } + } + } + } + } +} + +@PreviewLightDark +@Composable +private fun Preview() { + TachiyomiTheme { + Surface { + Column { + ModeSelectionDialog( + onApply = {}, + onUseDefault = {}, + ) { + Text("Dummy content") + } + + ModeSelectionDialog( + onApply = {}, + ) { + Text("Dummy content without default") + } + } + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt index de81bb174..21f3820ec 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt @@ -8,13 +8,13 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.res.stringResource -import eu.kanade.domain.entries.manga.model.orientationType -import eu.kanade.domain.entries.manga.model.readingModeType +import eu.kanade.domain.entries.manga.model.readerOrientation +import eu.kanade.domain.entries.manga.model.readingMode import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem @@ -23,41 +23,37 @@ import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.util.collectAsState import java.text.NumberFormat -private val readingModeOptions = ReadingModeType.entries.map { it.stringRes to it } -private val orientationTypeOptions = OrientationType.entries.map { it.stringRes to it } -private val tappingInvertModeOptions = ReaderPreferences.TappingInvertMode.entries.map { it.titleResId to it } - @Composable internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel) { HeadingItem(R.string.pref_category_for_this_series) val manga by screenModel.mangaFlow.collectAsState() val readingMode = remember(manga) { - ReadingModeType.fromPreference( - manga?.readingModeType?.toInt(), + ReadingMode.fromPreference( + manga?.readingMode?.toInt(), ) } SettingsChipRow(R.string.pref_category_reading_mode) { - readingModeOptions.map { (stringRes, it) -> + ReadingMode.entries.map { FilterChip( selected = it == readingMode, onClick = { screenModel.onChangeReadingMode(it) }, - label = { Text(stringResource(stringRes)) }, + label = { Text(stringResource(it.stringRes)) }, ) } } - val orientationType = remember(manga) { - OrientationType.fromPreference( - manga?.orientationType?.toInt(), + val orientation = remember(manga) { + ReaderOrientation.fromPreference( + manga?.readerOrientation?.toInt(), ) } SettingsChipRow(R.string.rotation_type) { - orientationTypeOptions.map { (stringRes, it) -> + ReaderOrientation.entries.map { FilterChip( - selected = it == orientationType, + selected = it == orientation, onClick = { screenModel.onChangeOrientation(it) }, - label = { Text(stringResource(stringRes)) }, + label = { Text(stringResource(it.stringRes)) }, ) } } @@ -217,11 +213,11 @@ private fun ColumnScope.TapZonesItems( if (selected != 5) { SettingsChipRow(R.string.pref_read_with_tapping_inverted) { - tappingInvertModeOptions.map { (stringRes, mode) -> + ReaderPreferences.TappingInvertMode.entries.map { FilterChip( - selected = mode == invertMode, - onClick = { onSelectInvertMode(mode) }, - label = { Text(stringResource(stringRes)) }, + selected = it == invertMode, + onClick = { onSelectInvertMode(it) }, + label = { Text(stringResource(it.titleResId)) }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index 2bc17f6c2..98b749312 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -21,6 +21,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.SelectableDates +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDatePickerState @@ -31,6 +32,7 @@ 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.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R @@ -39,7 +41,6 @@ import tachiyomi.presentation.core.components.WheelNumberPicker import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.material.AlertDialogContent import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrolledToStart @@ -238,26 +239,28 @@ fun BaseSelector( ) } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackStatusSelectorPreviews() { TachiyomiTheme { - TrackStatusSelector( - selection = 1, - onSelectionChange = {}, - selections = mapOf( - // Anilist values - 1 to R.string.reading, - 2 to R.string.plan_to_read, - 3 to R.string.completed, - 4 to R.string.on_hold, - 5 to R.string.dropped, - 6 to R.string.repeating, - 7 to R.string.watching, - 8 to R.string.plan_to_watch, - ), - onConfirm = {}, - onDismissRequest = {}, - ) + Surface { + TrackStatusSelector( + selection = 1, + onSelectionChange = {}, + selections = mapOf( + // Anilist values + 1 to R.string.reading, + 2 to R.string.plan_to_read, + 3 to R.string.completed, + 4 to R.string.on_hold, + 5 to R.string.dropped, + 6 to R.string.repeating, + 7 to R.string.watching, + 8 to R.string.plan_to_watch, + ), + onConfirm = {}, + onDismissRequest = {}, + ) + } } } diff --git a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt index 91c6a158e..765c03e13 100644 --- a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.domain.track.anime.model.toDbTrack @@ -43,7 +44,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem import eu.kanade.tachiyomi.util.system.copyToClipboard -import tachiyomi.presentation.core.util.ThemePreviews import java.text.DateFormat private const val UnsetStatusTextAlpha = 0.5F @@ -250,7 +250,7 @@ private fun TrackInfoItemEmpty( } } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackInfoDialogHomePreviews( @PreviewParameter(AnimeTrackInfoDialogHomePreviewProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackerSearch.kt b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackerSearch.kt index 9b43b3eb0..00b27868a 100644 --- a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackerSearch.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.toLowerCase +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.presentation.theme.TachiyomiTheme @@ -52,7 +53,6 @@ import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.runOnEnterKeyPressed @@ -201,7 +201,7 @@ fun AnimeTrackerSearch( } } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackerSearchPreviews( @PreviewParameter(AnimeTrackerSearchPreviewProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/track/components/TrackLogoIcon.kt b/app/src/main/java/eu/kanade/presentation/track/components/TrackLogoIcon.kt index 63b7c6d02..835cce95c 100644 --- a/app/src/main/java/eu/kanade/presentation/track/components/TrackLogoIcon.kt +++ b/app/src/main/java/eu/kanade/presentation/track/components/TrackLogoIcon.kt @@ -11,11 +11,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.data.track.Tracker -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.clickableNoIndication @Composable @@ -43,7 +43,7 @@ fun TrackLogoIcon( } } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackLogoIconPreviews( @PreviewParameter(TrackLogoIconPreviewProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt index c154f90a1..84f889305 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt @@ -44,6 +44,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.domain.track.manga.model.toDbTrack @@ -54,7 +55,6 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.ui.entries.manga.track.MangaTrackItem import eu.kanade.tachiyomi.util.system.copyToClipboard -import tachiyomi.presentation.core.util.ThemePreviews import java.text.DateFormat private const val UnsetStatusTextAlpha = 0.5F @@ -322,7 +322,7 @@ fun TrackInfoItemMenu( } } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackInfoDialogHomePreviews( @PreviewParameter(MangaTrackInfoDialogHomePreviewProvider::class) diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackerSearch.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackerSearch.kt index c0a48fa3b..61548517e 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackerSearch.kt @@ -38,6 +38,7 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable @@ -57,6 +58,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.toLowerCase +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import eu.kanade.presentation.entries.ItemCover @@ -68,7 +70,6 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.runOnEnterKeyPressed import tachiyomi.presentation.core.util.secondaryItemAlpha @@ -326,11 +327,15 @@ fun SearchResultItemDetails( } } -@ThemePreviews +@PreviewLightDark @Composable private fun TrackerSearchPreviews( @PreviewParameter(MangaTrackerSearchPreviewProvider::class) content: @Composable () -> Unit, ) { - TachiyomiTheme { content() } + TachiyomiTheme { + Surface { + content() + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 5c2927c59..af719ac98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast @@ -190,12 +190,12 @@ object Migrations { if (oldVersion < 60) { // Migrate Rotation and Viewer values to default values for viewer_flags val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) { - 1 -> OrientationType.FREE.flagValue - 2 -> OrientationType.PORTRAIT.flagValue - 3 -> OrientationType.LANDSCAPE.flagValue - 4 -> OrientationType.LOCKED_PORTRAIT.flagValue - 5 -> OrientationType.LOCKED_LANDSCAPE.flagValue - else -> OrientationType.FREE.flagValue + 1 -> ReaderOrientation.FREE.flagValue + 2 -> ReaderOrientation.PORTRAIT.flagValue + 3 -> ReaderOrientation.LANDSCAPE.flagValue + 4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue + 5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue + else -> ReaderOrientation.FREE.flagValue } // Reading mode flag and prefValue is the same value diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt index 0b6db6b35..bf0daf0ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.backup.models.BackupAnime import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupChapter import eu.kanade.tachiyomi.data.backup.models.BackupExtension import eu.kanade.tachiyomi.data.backup.models.BackupExtensionPreferences import eu.kanade.tachiyomi.data.backup.models.BackupHistory @@ -240,15 +241,15 @@ class BackupCreator( // Check if user wants chapter information in backup if (options and BackupConst.BACKUP_CHAPTER_MASK == BackupConst.BACKUP_CHAPTER) { // Backup all the chapters - val chapters = mangaHandler.awaitList { + mangaHandler.awaitList { chaptersQueries.getChaptersByMangaId( - manga.id, - backupChapterMapper, + mangaId = manga.id, + applyScanlatorFilter = 0, // false + mapper = backupChapterMapper, ) } - if (chapters.isNotEmpty()) { - mangaObject.chapters = chapters - } + .takeUnless(List::isEmpty) + ?.let { mangaObject.chapters = it } } // Check if user wants category information in backup diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 203df506f..181a6c6f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.model.UpdateStrategy -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import tachiyomi.domain.entries.manga.model.Manga @@ -89,7 +89,7 @@ data class BackupManga( favorite = manga.favorite, source = manga.source, dateAdded = manga.dateAdded, - viewer = (manga.viewerFlags.toInt() and ReadingModeType.MASK), + viewer = (manga.viewerFlags.toInt() and ReadingMode.MASK), viewer_flags = manga.viewerFlags.toInt(), chapterFlags = manga.chapterFlags.toInt(), updateStrategy = manga.updateStrategy, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt index 6052044f3..31ba58063 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.updater import android.content.Context -import android.os.Build import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid import tachiyomi.core.util.lang.withIOContext @@ -13,10 +12,10 @@ class AppUpdateChecker { private val getApplicationRelease: GetApplicationRelease by injectLazy() suspend fun checkForUpdate(context: Context, forceCheck: Boolean = false): GetApplicationRelease.Result { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - return GetApplicationRelease.Result.OsTooOld - } // Disabling app update checks for older Android versions that we're going to drop support for + // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + // return GetApplicationRelease.Result.OsTooOld + // } return withIOContext { val result = getApplicationRelease.await( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt index 4ee3bbaac..6ba1b9648 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt @@ -9,8 +9,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext @@ -31,6 +33,7 @@ import eu.kanade.presentation.entries.manga.ChapterSettingsDialog import eu.kanade.presentation.entries.manga.DuplicateMangaDialog import eu.kanade.presentation.entries.manga.MangaScreen import eu.kanade.presentation.entries.manga.components.MangaCoverDialog +import eu.kanade.presentation.entries.manga.components.ScanlatorFilterDialog import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.isTabletUi @@ -153,6 +156,8 @@ class MangaScreen( onInvertSelection = screenModel::invertSelection, ) + var showScanlatorsDialog by remember { mutableStateOf(false) } + val onDismissRequest = { screenModel.dismissDialog() if (screenModel.autoOpenTrack && screenModel.isFromChangeCategory) { @@ -197,6 +202,8 @@ class MangaScreen( onDisplayModeChanged = screenModel::setDisplayMode, onSetAsDefault = screenModel::setCurrentSettingsAsDefault, onResetToDefault = screenModel::resetToDefaultSettings, + scanlatorFilterActive = successState.scanlatorFilterActive, + onScanlatorFilterClicked = { showScanlatorsDialog = true }, ) MangaScreenModel.Dialog.TrackSheet -> { NavigatorAdaptiveSheet( @@ -245,6 +252,15 @@ class MangaScreen( ) } } + + if (showScanlatorsDialog) { + ScanlatorFilterDialog( + availableScanlators = successState.availableScanlators, + excludedScanlators = successState.excludedScanlators, + onDismissRequest = { showScanlatorsDialog = false }, + onConfirm = screenModel::setExcludedScanlators, + ) + } } private fun continueReading(context: Context, unreadChapter: Chapter?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index c65d1c5f4..cdc54646f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -11,9 +11,13 @@ 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.entries.manga.interactor.GetExcludedScanlators +import eu.kanade.domain.entries.manga.interactor.SetExcludedScanlators import eu.kanade.domain.entries.manga.interactor.UpdateManga +import eu.kanade.domain.entries.manga.model.chaptersFiltered import eu.kanade.domain.entries.manga.model.downloadedFilter import eu.kanade.domain.entries.manga.model.toSManga +import eu.kanade.domain.items.chapter.interactor.GetAvailableScanlators import eu.kanade.domain.items.chapter.interactor.SetReadStatus import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.track.manga.interactor.AddMangaTracks @@ -95,6 +99,9 @@ class MangaScreenModel( private val downloadCache: MangaDownloadCache = Injekt.get(), private val getMangaAndChapters: GetMangaWithChapters = 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 setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val setReadStatus: SetReadStatus = Injekt.get(), @@ -162,7 +169,7 @@ class MangaScreenModel( init { screenModelScope.launchIO { combine( - getMangaAndChapters.subscribe(mangaId).distinctUntilChanged(), + getMangaAndChapters.subscribe(mangaId, applyScanlatorFilter = true).distinctUntilChanged(), downloadCache.changes, downloadManager.queueState, ) { mangaAndChapters, _, _ -> mangaAndChapters } @@ -176,11 +183,31 @@ class MangaScreenModel( } } + 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) - val chapters = getMangaAndChapters.awaitChapters(mangaId) + val chapters = getMangaAndChapters.awaitChapters(mangaId, applyScanlatorFilter = true) .toChapterListItems(manga) if (!manga.favorite) { @@ -197,6 +224,8 @@ class MangaScreenModel( source = Injekt.get().getOrStub(manga.source), isFromSource = isFromSource, chapters = chapters, + availableScanlators = getAvailableScanlators.await(mangaId), + excludedScanlators = getExcludedScanlators.await(mangaId), isRefreshingData = needRefreshInfo || needRefreshChapter, dialog = null, ) @@ -1028,6 +1057,12 @@ class MangaScreenModel( updateSuccessState { it.copy(dialog = Dialog.FullCover) } } + fun setExcludedScanlators(excludedScanlators: Set) { + screenModelScope.launchIO { + setExcludedScanlators.await(mangaId, excludedScanlators) + } + } + sealed interface State { @Immutable data object Loading : State @@ -1038,12 +1073,13 @@ class MangaScreenModel( val source: MangaSource, val isFromSource: Boolean, val chapters: List, + val availableScanlators: Set, + val excludedScanlators: Set, val trackItems: List = emptyList(), val isRefreshingData: Boolean = false, val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, ) : State { - val processedChapters by lazy { chapters.applyFilters(manga).toList() } @@ -1075,6 +1111,12 @@ class MangaScreenModel( } } + val scanlatorFilterActive: Boolean + get() = excludedScanlators.intersect(availableScanlators).isNotEmpty() + + val filterActive: Boolean + get() = scanlatorFilterActive || manga.chaptersFiltered() + val trackingAvailable: Boolean get() = trackItems.isNotEmpty() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt index a904f008f..c16842f87 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt @@ -416,7 +416,7 @@ class MangaLibraryScreenModel( } suspend fun getNextUnreadChapter(manga: Manga): Chapter? { - return getChaptersByMangaId.await(manga.id).getNextUnread(manga, downloadManager) + return getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true).getNextUnread(manga, downloadManager) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index aa080ec60..58e446f93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -43,7 +43,7 @@ import dev.chrisbanes.insetter.applyInsetter import eu.kanade.domain.base.BasePreferences import eu.kanade.presentation.reader.BrightnessOverlay import eu.kanade.presentation.reader.DisplayRefreshHost -import eu.kanade.presentation.reader.OrientationModeSelectDialog +import eu.kanade.presentation.reader.OrientationSelectDialog import eu.kanade.presentation.reader.PageIndicatorText import eu.kanade.presentation.reader.ReaderPageActionsDialog import eu.kanade.presentation.reader.ReadingModeSelectDialog @@ -63,10 +63,10 @@ import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Success import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.system.hasDisplayCutout @@ -338,7 +338,7 @@ class ReaderActivity : BaseActivity() { val cropBorderPaged by readerPreferences.cropBorders().collectAsState() val cropBorderWebtoon by readerPreferences.cropBordersWebtoon().collectAsState() - val isPagerType = ReadingModeType.isPagerType(viewModel.getMangaReadingMode()) + val isPagerType = ReadingMode.isPagerType(viewModel.getMangaReadingMode()) val cropEnabled = if (isPagerType) cropBorderPaged else cropBorderWebtoon ReaderAppBars( @@ -367,14 +367,14 @@ class ReaderActivity : BaseActivity() { moveToPageIndex(it) }, - readingMode = ReadingModeType.fromPreference( + readingMode = ReadingMode.fromPreference( viewModel.getMangaReadingMode(resolveDefault = false), ), onClickReadingMode = viewModel::openReadingModeSelectDialog, - orientationMode = OrientationType.fromPreference( - viewModel.getMangaOrientationType(resolveDefault = false), + orientation = ReaderOrientation.fromPreference( + viewModel.getMangaOrientation(resolveDefault = false), ), - onClickOrientationMode = viewModel::openOrientationModeSelectDialog, + onClickOrientation = viewModel::openOrientationModeSelectDialog, cropEnabled = cropEnabled, onClickCropBorder = { val enabled = viewModel.toggleCropBorders() @@ -432,7 +432,7 @@ class ReaderActivity : BaseActivity() { ) } is ReaderViewModel.Dialog.OrientationModeSelect -> { - OrientationModeSelectDialog( + OrientationSelectDialog( onDismissRequest = onDismissRequest, screenModel = settingsScreenModel, onChange = { stringRes -> @@ -489,15 +489,15 @@ class ReaderActivity : BaseActivity() { */ private fun setManga(manga: Manga) { val prevViewer = viewModel.state.value.viewer - val newViewer = ReadingModeType.toViewer(viewModel.getMangaReadingMode(), this) + val newViewer = ReadingMode.toViewer(viewModel.getMangaReadingMode(), this) if (window.sharedElementEnterTransition is MaterialContainerTransform) { // Wait until transition is complete to avoid crash on API 26 window.sharedElementEnterTransition.doOnEnd { - setOrientation(viewModel.getMangaOrientationType()) + setOrientation(viewModel.getMangaOrientation()) } } else { - setOrientation(viewModel.getMangaOrientationType()) + setOrientation(viewModel.getMangaOrientation()) } // Destroy previous viewer if there was one @@ -550,7 +550,7 @@ class ReaderActivity : BaseActivity() { private fun showReadingModeToast(mode: Int) { try { readingModeToast?.cancel() - readingModeToast = toast(ReadingModeType.fromPreference(mode).stringRes) + readingModeToast = toast(ReadingMode.fromPreference(mode).stringRes) } catch (e: ArrayIndexOutOfBoundsException) { logcat(LogPriority.ERROR) { "Unknown reading mode: $mode" } } @@ -728,7 +728,7 @@ class ReaderActivity : BaseActivity() { * Forces the user preferred [orientation] on the activity. */ private fun setOrientation(orientation: Int) { - val newOrientation = OrientationType.fromPreference(orientation) + val newOrientation = ReaderOrientation.fromPreference(orientation) if (newOrientation.flag != requestedOrientation) { requestedOrientation = newOrientation.flag } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index b50bddcb4..04c3528ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -9,8 +9,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.entries.manga.interactor.SetMangaViewerFlags -import eu.kanade.domain.entries.manga.model.orientationType -import eu.kanade.domain.entries.manga.model.readingModeType +import eu.kanade.domain.entries.manga.model.readerOrientation +import eu.kanade.domain.entries.manga.model.readingMode import eu.kanade.domain.items.chapter.model.toDbChapter import eu.kanade.domain.track.manga.interactor.TrackChapter import eu.kanade.domain.track.service.TrackPreferences @@ -29,9 +29,9 @@ import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.viewer.Viewer import eu.kanade.tachiyomi.util.chapter.filterDownloadedChapters import eu.kanade.tachiyomi.util.chapter.removeDuplicates @@ -147,7 +147,7 @@ class ReaderViewModel @JvmOverloads constructor( */ private val chapterList by lazy { val manga = manga!! - val chapters = runBlocking { getChaptersByMangaId.await(manga.id) } + val chapters = runBlocking { getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) } val selectedChapter = chapters.find { it.id == chapterId } ?: error("Requested chapter of id $chapterId not found in chapter list") @@ -642,22 +642,22 @@ class ReaderViewModel @JvmOverloads constructor( */ fun getMangaReadingMode(resolveDefault: Boolean = true): Int { val default = readerPreferences.defaultReadingMode().get() - val readingMode = ReadingModeType.fromPreference(manga?.readingModeType?.toInt()) + val readingMode = ReadingMode.fromPreference(manga?.readingMode?.toInt()) return when { - resolveDefault && readingMode == ReadingModeType.DEFAULT -> default - else -> manga?.readingModeType?.toInt() ?: default + resolveDefault && readingMode == ReadingMode.DEFAULT -> default + else -> manga?.readingMode?.toInt() ?: default } } /** * Updates the viewer position for the open manga. */ - fun setMangaReadingMode(readingModeType: ReadingModeType) { + fun setMangaReadingMode(readingMode: ReadingMode) { val manga = manga ?: return runBlocking(Dispatchers.IO) { setMangaViewerFlags.awaitSetReadingMode( manga.id, - readingModeType.flagValue.toLong(), + readingMode.flagValue.toLong(), ) val currChapters = state.value.viewerChapters if (currChapters != null) { @@ -679,22 +679,22 @@ class ReaderViewModel @JvmOverloads constructor( /** * Returns the orientation type used by this manga or the default one. */ - fun getMangaOrientationType(resolveDefault: Boolean = true): Int { + fun getMangaOrientation(resolveDefault: Boolean = true): Int { val default = readerPreferences.defaultOrientationType().get() - val orientation = OrientationType.fromPreference(manga?.orientationType?.toInt()) + val orientation = ReaderOrientation.fromPreference(manga?.readerOrientation?.toInt()) return when { - resolveDefault && orientation == OrientationType.DEFAULT -> default - else -> manga?.orientationType?.toInt() ?: default + resolveDefault && orientation == ReaderOrientation.DEFAULT -> default + else -> manga?.readerOrientation?.toInt() ?: default } } /** * Updates the orientation type for the open manga. */ - fun setMangaOrientationType(rotationType: OrientationType) { + fun setMangaOrientationType(orientation: ReaderOrientation) { val manga = manga ?: return viewModelScope.launchIO { - setMangaViewerFlags.awaitSetOrientation(manga.id, rotationType.flagValue.toLong()) + setMangaViewerFlags.awaitSetOrientation(manga.id, orientation.flagValue.toLong()) val currChapters = state.value.viewerChapters if (currChapters != null) { // Save current page @@ -707,14 +707,14 @@ class ReaderViewModel @JvmOverloads constructor( viewerChapters = currChapters, ) } - eventChannel.send(Event.SetOrientation(getMangaOrientationType())) + eventChannel.send(Event.SetOrientation(getMangaOrientation())) eventChannel.send(Event.ReloadViewerChapters) } } } fun toggleCropBorders(): Boolean { - val isPagerType = ReadingModeType.isPagerType(getMangaReadingMode()) + val isPagerType = ReadingMode.isPagerType(getMangaReadingMode()) return if (isPagerType) { readerPreferences.cropBorders().toggle() } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt similarity index 91% rename from app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt index c2edb39e9..c1d2e9b2d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/OrientationType.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderOrientation.kt @@ -5,7 +5,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import eu.kanade.tachiyomi.R -enum class OrientationType( +enum class ReaderOrientation( val flag: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int, @@ -58,6 +58,6 @@ enum class OrientationType( companion object { const val MASK = 0x00000038 - fun fromPreference(preference: Int?): OrientationType = entries.find { it.flagValue == preference } ?: DEFAULT + fun fromPreference(preference: Int?): ReaderOrientation = entries.find { it.flagValue == preference } ?: DEFAULT } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index c3775caf7..754ed0ef1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -32,12 +32,12 @@ class ReaderPreferences( fun defaultReadingMode() = preferenceStore.getInt( "pref_default_reading_mode_key", - ReadingModeType.RIGHT_TO_LEFT.flagValue, + ReadingMode.RIGHT_TO_LEFT.flagValue, ) fun defaultOrientationType() = preferenceStore.getInt( "pref_default_orientation_type_key", - OrientationType.FREE.flagValue, + ReaderOrientation.FREE.flagValue, ) fun webtoonDoubleTapZoomEnabled() = preferenceStore.getBoolean( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt index 5014ba204..5f107c388 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsScreenModel.kt @@ -14,8 +14,8 @@ import uy.kohesive.injekt.api.get class ReaderSettingsScreenModel( readerState: StateFlow, val hasDisplayCutout: Boolean, - val onChangeReadingMode: (ReadingModeType) -> Unit, - val onChangeOrientation: (OrientationType) -> Unit, + val onChangeReadingMode: (ReadingMode) -> Unit, + val onChangeOrientation: (ReaderOrientation) -> Unit, val preferences: ReaderPreferences = Injekt.get(), ) : ScreenModel { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingMode.kt similarity index 79% rename from app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingMode.kt index fd0b3a78f..963aa830b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingModeType.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReadingMode.kt @@ -10,10 +10,12 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer -enum class ReadingModeType( +enum class ReadingMode( @StringRes val stringRes: Int, @DrawableRes val iconRes: Int, val flagValue: Int, + val direction: Direction? = null, + val type: ViewerType? = null, ) { DEFAULT(R.string.label_default, R.drawable.ic_reader_default_24dp, 0x00000000), LEFT_TO_RIGHT(R.string.left_to_right_viewer, R.drawable.ic_reader_ltr_24dp, 0x00000001), @@ -30,11 +32,11 @@ enum class ReadingModeType( companion object { const val MASK = 0x00000007 - fun fromPreference(preference: Int?): ReadingModeType = entries.find { it.flagValue == preference } ?: DEFAULT + fun fromPreference(preference: Int?): ReadingMode = entries.find { it.flagValue == preference } ?: DEFAULT fun isPagerType(preference: Int): Boolean { val mode = fromPreference(preference) - return mode == LEFT_TO_RIGHT || mode == RIGHT_TO_LEFT || mode == VERTICAL + return mode.type is ViewerType.Pager } fun toViewer(preference: Int?, activity: ReaderActivity): Viewer { @@ -50,4 +52,14 @@ enum class ReadingModeType( } } } + + sealed interface Direction { + data object Horizontal : Direction + data object Vertical : Direction + } + + sealed interface ViewerType { + data object Pager : ViewerType + data object Webtoon : ViewerType + } } diff --git a/data/src/main/java/tachiyomi/data/items/chapter/ChapterRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/items/chapter/ChapterRepositoryImpl.kt index 40c45a454..c4c4d8372 100644 --- a/data/src/main/java/tachiyomi/data/items/chapter/ChapterRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/items/chapter/ChapterRepositoryImpl.kt @@ -2,6 +2,7 @@ package tachiyomi.data.items.chapter import kotlinx.coroutines.flow.Flow import logcat.LogPriority +import tachiyomi.core.util.lang.toLong import tachiyomi.core.util.system.logcat import tachiyomi.data.handlers.manga.MangaDatabaseHandler import tachiyomi.domain.items.chapter.model.Chapter @@ -76,8 +77,22 @@ class ChapterRepositoryImpl( } } - override suspend fun getChapterByMangaId(mangaId: Long): List { - return handler.awaitList { chaptersQueries.getChaptersByMangaId(mangaId, ::mapChapter) } + override suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean): List { + return handler.awaitList { + chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) + } + } + + override suspend fun getScanlatorsByMangaId(mangaId: Long): List { + return handler.awaitList { + chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() } + } + } + + override fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow> { + return handler.subscribeToList { + chaptersQueries.getScanlatorsByMangaId(mangaId) { it.orEmpty() } + } } override suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List { @@ -93,12 +108,9 @@ class ChapterRepositoryImpl( return handler.awaitOneOrNull { chaptersQueries.getChapterById(id, ::mapChapter) } } - override suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow> { + override suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean): Flow> { return handler.subscribeToList { - chaptersQueries.getChaptersByMangaId( - mangaId, - ::mapChapter, - ) + chaptersQueries.getChaptersByMangaId(mangaId, applyScanlatorFilter.toLong(), ::mapChapter) } } diff --git a/data/src/main/sqldelight/data/chapters.sq b/data/src/main/sqldelight/data/chapters.sq index 9a56423aa..ab8397588 100644 --- a/data/src/main/sqldelight/data/chapters.sq +++ b/data/src/main/sqldelight/data/chapters.sq @@ -36,7 +36,19 @@ FROM chapters WHERE _id = :id; 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 WHERE manga_id = :mangaId; diff --git a/data/src/main/sqldelight/data/excluded_scanlators.sq b/data/src/main/sqldelight/data/excluded_scanlators.sq new file mode 100644 index 000000000..66ebeea96 --- /dev/null +++ b/data/src/main/sqldelight/data/excluded_scanlators.sq @@ -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; \ No newline at end of file diff --git a/data/src/main/sqldelight/migrations/27.sqm b/data/src/main/sqldelight/migrations/27.sqm new file mode 100644 index 000000000..7e6c0adcb --- /dev/null +++ b/data/src/main/sqldelight/migrations/27.sqm @@ -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; \ No newline at end of file diff --git a/data/src/main/sqldelight/migrations/28.sqm b/data/src/main/sqldelight/migrations/28.sqm new file mode 100644 index 000000000..3b2c12c8f --- /dev/null +++ b/data/src/main/sqldelight/migrations/28.sqm @@ -0,0 +1,3 @@ +UPDATE chapters +SET scanlator = trim(scanlator) +WHERE scanlator IS NOT NULL; \ No newline at end of file diff --git a/data/src/main/sqldelight/view/libraryView.sq b/data/src/main/sqldelight/view/libraryView.sq index 4b1468872..0a5d28543 100644 --- a/data/src/main/sqldelight/view/libraryView.sq +++ b/data/src/main/sqldelight/view/libraryView.sq @@ -19,8 +19,12 @@ LEFT JOIN( 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 diff --git a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/GetMangaWithChapters.kt b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/GetMangaWithChapters.kt index b4add22eb..4512c3a1c 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/GetMangaWithChapters.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/GetMangaWithChapters.kt @@ -12,10 +12,10 @@ class GetMangaWithChapters( private val chapterRepository: ChapterRepository, ) { - suspend fun subscribe(id: Long): Flow>> { + suspend fun subscribe(id: Long, applyScanlatorFilter: Boolean = false): Flow>> { return combine( mangaRepository.getMangaByIdAsFlow(id), - chapterRepository.getChapterByMangaIdAsFlow(id), + chapterRepository.getChapterByMangaIdAsFlow(id, applyScanlatorFilter), ) { manga, chapters -> Pair(manga, chapters) } @@ -25,7 +25,7 @@ class GetMangaWithChapters( return mangaRepository.getMangaById(id) } - suspend fun awaitChapters(id: Long): List { - return chapterRepository.getChapterByMangaId(id) + suspend fun awaitChapters(id: Long, applyScanlatorFilter: Boolean = false): List { + return chapterRepository.getChapterByMangaId(id, applyScanlatorFilter) } } diff --git a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt index 5a3f7fd68..9e2670e83 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/manga/interactor/MangaFetchInterval.kt @@ -24,7 +24,7 @@ class MangaFetchInterval( } else { window } - val chapters = getChaptersByMangaId.await(manga.id) + val chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval( chapters, dateTime.zone, diff --git a/domain/src/main/java/tachiyomi/domain/history/manga/interactor/GetNextChapters.kt b/domain/src/main/java/tachiyomi/domain/history/manga/interactor/GetNextChapters.kt index a507fd4be..5aae88186 100644 --- a/domain/src/main/java/tachiyomi/domain/history/manga/interactor/GetNextChapters.kt +++ b/domain/src/main/java/tachiyomi/domain/history/manga/interactor/GetNextChapters.kt @@ -20,7 +20,7 @@ class GetNextChapters( suspend fun await(mangaId: Long, onlyUnread: Boolean = true): List { val manga = getManga.await(mangaId) ?: return emptyList() - val chapters = getChaptersByMangaId.await(mangaId) + val chapters = getChaptersByMangaId.await(mangaId, applyScanlatorFilter = true) .sortedWith(getChapterSort(manga, sortDescending = false)) return if (onlyUnread) { diff --git a/domain/src/main/java/tachiyomi/domain/items/chapter/interactor/GetChaptersByMangaId.kt b/domain/src/main/java/tachiyomi/domain/items/chapter/interactor/GetChaptersByMangaId.kt index bc0b3d5c5..89ffb1690 100644 --- a/domain/src/main/java/tachiyomi/domain/items/chapter/interactor/GetChaptersByMangaId.kt +++ b/domain/src/main/java/tachiyomi/domain/items/chapter/interactor/GetChaptersByMangaId.kt @@ -9,9 +9,9 @@ class GetChaptersByMangaId( private val chapterRepository: ChapterRepository, ) { - suspend fun await(mangaId: Long): List { + suspend fun await(mangaId: Long, applyScanlatorFilter: Boolean = false): List { return try { - chapterRepository.getChapterByMangaId(mangaId) + chapterRepository.getChapterByMangaId(mangaId, applyScanlatorFilter) } catch (e: Exception) { logcat(LogPriority.ERROR, e) emptyList() diff --git a/domain/src/main/java/tachiyomi/domain/items/chapter/repository/ChapterRepository.kt b/domain/src/main/java/tachiyomi/domain/items/chapter/repository/ChapterRepository.kt index 9d43b3a99..d09495134 100644 --- a/domain/src/main/java/tachiyomi/domain/items/chapter/repository/ChapterRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/items/chapter/repository/ChapterRepository.kt @@ -14,13 +14,17 @@ interface ChapterRepository { suspend fun removeChaptersWithIds(chapterIds: List) - suspend fun getChapterByMangaId(mangaId: Long): List + suspend fun getChapterByMangaId(mangaId: Long, applyScanlatorFilter: Boolean = false): List + + suspend fun getScanlatorsByMangaId(mangaId: Long): List + + fun getScanlatorsByMangaIdAsFlow(mangaId: Long): Flow> suspend fun getBookmarkedChaptersByMangaId(mangaId: Long): List suspend fun getChapterById(id: Long): Chapter? - suspend fun getChapterByMangaIdAsFlow(mangaId: Long): Flow> + suspend fun getChapterByMangaIdAsFlow(mangaId: Long, applyScanlatorFilter: Boolean = false): Flow> suspend fun getChapterByUrlAndMangaId(url: String, mangaId: Long): Chapter? } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b7de463fd..0c0e3af22 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ shizuku_version = "12.2.0" sqlite = "2.4.0" sqldelight = "2.0.0" leakcanary = "2.12" -voyager = "1.0.0-rc08" +voyager = "1.0.0-rc09" richtext = "0.17.0" [libraries] @@ -81,7 +81,7 @@ sqldelight-android-paging = { module = "app.cash.sqldelight:androidx-paging3-ext sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" } -junit = "org.junit.jupiter:junit-jupiter:5.10.0" +junit = "org.junit.jupiter:junit-jupiter:5.10.1" kotest-assertions = "io.kotest:kotest-assertions-core:5.8.0" mockk = "io.mockk:mockk:1.13.8" diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 6135bfad5..d811110a3 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -14,6 +14,7 @@ Tracking Delete downloaded History + Scanlator More @@ -126,6 +127,7 @@ Disable Pin Unpin + Apply Cancel OK Cancel all @@ -146,6 +148,7 @@ Share Save Reset + Revert to default Undo Close @@ -686,6 +689,8 @@ Set as default No chapters found Are you sure? + Exclude scanlators + No scanlators found Tracking diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt index 74f8211d9..b1a57a50e 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/InfoScreen.kt @@ -23,12 +23,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding -import tachiyomi.presentation.core.util.ThemePreviews import tachiyomi.presentation.core.util.secondaryItemAlpha @Composable @@ -121,7 +121,7 @@ fun InfoScreen( } } -@ThemePreviews +@PreviewLightDark @Composable private fun InfoScaffoldPreview() { InfoScreen(