From d7aee03688019b0ecda0bef96b7e40c21f69910b Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:42:37 +0100 Subject: [PATCH] merge38 Last commit merged: https://github.com/tachiyomiorg/tachiyomi/commit/1d144e67678a99ec7198e5efcb1410b5da4bc42e --- .editorconfig | 9 +- .../java/eu/kanade/domain/DomainModule.kt | 4 +- .../GetAnimeSourcesWithFavoriteCount.kt | 12 +-- .../GetMangaSourcesWithFavoriteCount.kt | 12 +-- .../track/anime/interactor/AddAnimeTracks.kt | 101 ++++++++++++++---- .../service/DelayedAnimeTrackingUpdateJob.kt | 5 +- .../track/manga/interactor/AddMangaTracks.kt | 101 ++++++++++++++---- .../service/DelayedMangaTrackingUpdateJob.kt | 4 +- .../browse/anime/GlobalAnimeSearchScreen.kt | 3 +- .../anime/components/BaseAnimeSourceItem.kt | 4 +- .../components/BrowseAnimeSourceToolbar.kt | 6 +- .../browse/manga/GlobalMangaSearchScreen.kt | 3 +- .../manga/components/BaseMangaSourceItem.kt | 4 +- .../components/BrowseMangaSourceToolbar.kt | 6 +- .../category/ChangeCategoryDialog.kt | 10 +- .../category/components/CategoryDialogs.kt | 12 ++- .../entries/EntryBottomActionMenu.kt | 12 ++- .../presentation/entries/anime/AnimeScreen.kt | 14 ++- .../entries/anime/EpisodeSettingsDialog.kt | 8 +- .../anime/components/AnimeInfoHeader.kt | 4 +- .../entries/manga/ChapterSettingsDialog.kt | 8 +- .../presentation/entries/manga/MangaScreen.kt | 4 +- .../manga/components/MangaInfoHeader.kt | 4 +- .../presentation/history/HistoryDialog.kt | 6 +- .../anime/AnimeLibrarySettingsDialog.kt | 12 ++- .../manga/MangaLibrarySettingsDialog.kt | 21 +++- .../more/settings/screen/Commons.kt | 12 ++- .../settings/screen/SettingsDownloadScreen.kt | 3 +- .../settings/screen/SettingsReaderScreen.kt | 5 + .../more/settings/screen/about/AboutScreen.kt | 3 + .../settings/screen/debug/DebugInfoScreen.kt | 29 +++-- .../presentation/reader/PageIndicatorText.kt | 39 ++++--- .../reader/appbars/ReaderAppBars.kt | 4 +- .../webview/WebViewScreenContent.kt | 4 +- app/src/main/java/eu/kanade/tachiyomi/App.kt | 12 ++- .../java/eu/kanade/tachiyomi/Migrations.kt | 9 +- .../tachiyomi/data/backup/BackupRestorer.kt | 11 +- .../data/backup/models/BackupAnimeTracking.kt | 17 ++- .../data/backup/models/BackupChapter.kt | 16 ++- .../data/backup/models/BackupEpisode.kt | 17 ++- .../data/backup/models/BackupTracking.kt | 15 ++- .../data/download/anime/AnimeDownloadCache.kt | 5 +- .../data/download/anime/AnimeDownloader.kt | 15 ++- .../data/download/manga/MangaDownloadCache.kt | 7 +- .../data/download/manga/MangaDownloader.kt | 12 ++- .../library/anime/AnimeLibraryUpdateJob.kt | 11 +- .../anime/AnimeLibraryUpdateNotifier.kt | 10 +- .../library/manga/MangaLibraryUpdateJob.kt | 11 +- .../manga/MangaLibraryUpdateNotifier.kt | 10 +- .../data/notification/NotificationReceiver.kt | 13 ++- .../kanade/tachiyomi/data/saver/ImageSaver.kt | 10 +- .../tachiyomi/data/track/AnimeTracker.kt | 92 ++++------------ .../tachiyomi/data/track/MangaTracker.kt | 96 ++++------------- .../tachiyomi/data/track/TrackerManager.kt | 2 + .../tachiyomi/data/track/anilist/Anilist.kt | 10 +- .../tachiyomi/data/track/kavita/KavitaApi.kt | 24 ++++- .../tachiyomi/data/track/kitsu/Kitsu.kt | 10 +- .../tachiyomi/data/track/kitsu/KitsuApi.kt | 8 +- .../data/track/myanimelist/MyAnimeList.kt | 10 +- .../data/track/shikimori/Shikimori.kt | 10 +- .../data/track/shikimori/ShikimoriApi.kt | 6 +- .../tachiyomi/data/track/simkl/SimklApi.kt | 4 +- .../data/updater/AppUpdateChecker.kt | 10 +- .../anime/api/AnimeExtensionGithubApi.kt | 5 +- .../PackageInstallerInstallerAnime.kt | 7 +- .../anime/util/AnimeExtensionInstaller.kt | 4 +- .../manga/api/MangaExtensionGithubApi.kt | 5 +- .../PackageInstallerInstallerManga.kt | 7 +- .../manga/util/MangaExtensionInstaller.kt | 4 +- .../source/anime/AndroidAnimeSourceManager.kt | 4 +- .../source/manga/AndroidMangaSourceManager.kt | 4 +- .../browse/BrowseAnimeSourceScreenModel.kt | 2 +- .../browse/BrowseMangaSourceScreenModel.kt | 2 +- .../tachiyomi/ui/entries/anime/AnimeScreen.kt | 24 ++++- .../ui/entries/anime/AnimeScreenModel.kt | 5 +- .../ui/entries/manga/MangaCoverScreenModel.kt | 4 +- .../tachiyomi/ui/entries/manga/MangaScreen.kt | 16 ++- .../ui/entries/manga/MangaScreenModel.kt | 5 +- .../library/anime/AnimeLibraryScreenModel.kt | 63 +++++++---- .../library/manga/MangaLibraryScreenModel.kt | 56 +++++++--- .../kanade/tachiyomi/ui/main/MainActivity.kt | 10 +- .../tachiyomi/ui/player/PlayerActivity.kt | 25 ++++- .../tachiyomi/ui/player/PlayerObserver.kt | 18 ++-- .../settings/sheets/StreamsCatalogSheet.kt | 8 +- .../settings/sheets/VideoChaptersSheet.kt | 8 +- .../ui/player/viewer/GestureHandler.kt | 9 +- .../ui/player/viewer/PlayerControlsView.kt | 7 +- .../tachiyomi/ui/reader/ReaderActivity.kt | 3 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 39 ++++--- .../ui/reader/setting/ReaderPreferences.kt | 7 +- .../ui/reader/viewer/ReaderPageImageView.kt | 7 +- .../eu/kanade/tachiyomi/util/AniSkipApi.kt | 3 +- .../java/eu/kanade/tachiyomi/util/PkceUtil.kt | 8 +- .../tachiyomi/util/lang/DateExtensions.kt | 3 +- .../network/interceptor/WebViewInterceptor.kt | 13 ++- .../tachiyomi/util/storage/FileExtensions.kt | 12 ++- .../java/tachiyomi/core/util/lang/SortUtil.kt | 15 +++ .../tachiyomi/core/util/system/ImageUtil.kt | 42 +++++++- .../source/manga/MangaSourcePagingSource.kt | 11 +- .../domain/entries/anime/model/Anime.kt | 1 + .../domain/entries/manga/model/Manga.kt | 1 + .../items/chapter/model/ChapterUpdate.kt | 15 ++- .../items/chapter/service/ChapterSorter.kt | 5 + .../items/episode/model/EpisodeUpdate.kt | 16 ++- .../items/episode/service/EpisodeSorter.kt | 5 + .../anime/model/AnimeLibrarySortMode.kt | 4 + .../manga/model/MangaLibrarySortMode.kt | 4 + .../interactor/GetApplicationRelease.kt | 1 + .../anime/interactor/GetTracksPerAnime.kt | 11 +- .../manga/interactor/GetTracksPerManga.kt | 11 +- .../domain/library/model/LibraryFlagsTest.kt | 4 +- gradle/libs.versions.toml | 6 +- i18n/src/main/res/values/strings.xml | 2 + .../core/components/VerticalFastScroller.kt | 6 +- .../presentation/core/util/Scrollbar.kt | 3 +- .../components/anime/UpdatesAnimeWidget.kt | 5 +- .../components/manga/UpdatesMangaWidget.kt | 5 +- .../local/entries/anime/LocalAnimeSource.kt | 6 +- .../local/entries/manga/LocalMangaSource.kt | 3 +- 119 files changed, 1087 insertions(+), 463 deletions(-) create mode 100644 core/src/main/java/tachiyomi/core/util/lang/SortUtil.kt diff --git a/.editorconfig b/.editorconfig index bbef1d752..1585df2f1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,8 @@ [*.{kt,kts}] -indent_size=4 -insert_final_newline=true -ij_kotlin_allow_trailing_comma=true -ij_kotlin_allow_trailing_comma_on_call_site=true +max_line_length = 120 +indent_size = 4 +insert_final_newline = true +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true ij_kotlin_name_count_to_use_star_import = 2147483647 ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index c84347e55..fe461a676 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -230,7 +230,7 @@ class DomainModule : InjektModule { addSingletonFactory { AnimeTrackRepositoryImpl(get()) } addFactory { TrackEpisode(get(), get(), get(), get()) } - addFactory { AddAnimeTracks(get(), get(), get()) } + addFactory { AddAnimeTracks(get(), get(), get(), get()) } addFactory { RefreshAnimeTracks(get(), get(), get(), get()) } addFactory { DeleteAnimeTrack(get()) } addFactory { GetTracksPerAnime(get()) } @@ -240,7 +240,7 @@ class DomainModule : InjektModule { addSingletonFactory { MangaTrackRepositoryImpl(get()) } addFactory { TrackChapter(get(), get(), get(), get()) } - addFactory { AddMangaTracks(get(), get(), get()) } + addFactory { AddMangaTracks(get(), get(), get(), get()) } addFactory { RefreshMangaTracks(get(), get(), get(), get()) } addFactory { DeleteMangaTrack(get()) } addFactory { GetTracksPerManga(get()) } diff --git a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetAnimeSourcesWithFavoriteCount.kt b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetAnimeSourcesWithFavoriteCount.kt index 9ee8b277d..e3a0e6e57 100644 --- a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetAnimeSourcesWithFavoriteCount.kt +++ b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetAnimeSourcesWithFavoriteCount.kt @@ -4,12 +4,11 @@ import eu.kanade.domain.source.service.SetMigrateSorting import eu.kanade.domain.source.service.SourcePreferences import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.domain.source.anime.model.AnimeSource import tachiyomi.domain.source.anime.repository.AnimeSourceRepository import tachiyomi.source.local.entries.anime.LocalAnimeSource -import java.text.Collator import java.util.Collections -import java.util.Locale class GetAnimeSourcesWithFavoriteCount( private val repository: AnimeSourceRepository, @@ -32,20 +31,13 @@ class GetAnimeSourcesWithFavoriteCount( direction: SetMigrateSorting.Direction, sorting: SetMigrateSorting.Mode, ): java.util.Comparator> { - val locale = Locale.getDefault() - val collator = Collator.getInstance(locale).apply { - strength = Collator.PRIMARY - } val sortFn: (Pair, Pair) -> Int = { a, b -> when (sorting) { SetMigrateSorting.Mode.ALPHABETICAL -> { when { a.first.isStub && b.first.isStub.not() -> -1 b.first.isStub && a.first.isStub.not() -> 1 - else -> collator.compare( - a.first.name.lowercase(locale), - b.first.name.lowercase(locale), - ) + else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase()) } } SetMigrateSorting.Mode.TOTAL -> { diff --git a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetMangaSourcesWithFavoriteCount.kt b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetMangaSourcesWithFavoriteCount.kt index 243f78ccb..66eae67a2 100644 --- a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetMangaSourcesWithFavoriteCount.kt +++ b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetMangaSourcesWithFavoriteCount.kt @@ -4,12 +4,11 @@ import eu.kanade.domain.source.service.SetMigrateSorting import eu.kanade.domain.source.service.SourcePreferences import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.domain.source.manga.model.Source import tachiyomi.domain.source.manga.repository.MangaSourceRepository import tachiyomi.source.local.entries.manga.LocalMangaSource -import java.text.Collator import java.util.Collections -import java.util.Locale class GetMangaSourcesWithFavoriteCount( private val repository: MangaSourceRepository, @@ -32,20 +31,13 @@ class GetMangaSourcesWithFavoriteCount( direction: SetMigrateSorting.Direction, sorting: SetMigrateSorting.Mode, ): java.util.Comparator> { - val locale = Locale.getDefault() - val collator = Collator.getInstance(locale).apply { - strength = Collator.PRIMARY - } val sortFn: (Pair, Pair) -> Int = { a, b -> when (sorting) { SetMigrateSorting.Mode.ALPHABETICAL -> { when { a.first.isStub && b.first.isStub.not() -> -1 b.first.isStub && a.first.isStub.not() -> 1 - else -> collator.compare( - a.first.name.lowercase(locale), - b.first.name.lowercase(locale), - ) + else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase()) } } SetMigrateSorting.Mode.TOTAL -> { diff --git a/app/src/main/java/eu/kanade/domain/track/anime/interactor/AddAnimeTracks.kt b/app/src/main/java/eu/kanade/domain/track/anime/interactor/AddAnimeTracks.kt index 8af4f887d..003e43c0a 100644 --- a/app/src/main/java/eu/kanade/domain/track/anime/interactor/AddAnimeTracks.kt +++ b/app/src/main/java/eu/kanade/domain/track/anime/interactor/AddAnimeTracks.kt @@ -1,45 +1,108 @@ package eu.kanade.domain.track.anime.interactor +import eu.kanade.domain.track.anime.model.toDbTrack import eu.kanade.domain.track.anime.model.toDomainTrack import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack +import eu.kanade.tachiyomi.data.track.AnimeTracker import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker import eu.kanade.tachiyomi.data.track.Tracker +import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import logcat.LogPriority +import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.anime.model.Anime +import tachiyomi.domain.history.anime.interactor.GetAnimeHistory +import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.time.ZoneOffset class AddAnimeTracks( private val getTracks: GetAnimeTracks, private val insertTrack: InsertAnimeTrack, private val syncChapterProgressWithTrack: SyncEpisodeProgressWithTrack, + private val getEpisodesByAnimeId: GetEpisodesByAnimeId, ) { - suspend fun bindEnhancedTracks(anime: Anime, source: AnimeSource) = withNonCancellableContext { - getTracks.await(anime.id) - .filterIsInstance() - .filter { it.accept(source) } - .forEach { service -> - try { - service.match(anime)?.let { track -> - track.anime_id = anime.id - (service as Tracker).animeService.bind(track) - insertTrack.await(track.toDomainTrack()!!) + // TODO: update all trackers based on common data + suspend fun bind(tracker: AnimeTracker, item: AnimeTrack, animeId: Long) = withNonCancellableContext { + withIOContext { + val allChapters = getEpisodesByAnimeId.await(animeId) + val hasSeenEpisodes = allChapters.any { it.seen } + tracker.bind(item, hasSeenEpisodes) - syncChapterProgressWithTrack.await( - anime.id, - track.toDomainTrack()!!, - service.animeService, + var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext + + insertTrack.await(track) + + // TODO: merge into [SyncChapterProgressWithTrack]? + // Update chapter progress if newer chapters marked read locally + if (hasSeenEpisodes) { + val latestLocalReadChapterNumber = allChapters + .sortedBy { it.episodeNumber } + .takeWhile { it.seen } + .lastOrNull() + ?.episodeNumber ?: -1.0 + + if (latestLocalReadChapterNumber > track.lastEpisodeSeen) { + track = track.copy( + lastEpisodeSeen = latestLocalReadChapterNumber, + ) + tracker.setRemoteLastEpisodeSeen(track.toDbTrack(), latestLocalReadChapterNumber.toInt()) + } + + if (track.startDate <= 0) { + val firstReadChapterDate = Injekt.get().await(animeId) + .sortedBy { it.seenAt } + .firstOrNull() + ?.seenAt + + firstReadChapterDate?.let { + val startDate = firstReadChapterDate.time.convertEpochMillisZone( + ZoneOffset.systemDefault(), + ZoneOffset.UTC, ) + track = track.copy( + startDate = startDate, + ) + tracker.setRemoteStartDate(track.toDbTrack(), startDate) } - } catch (e: Exception) { - logcat( - LogPriority.WARN, - e, - ) { "Could not match anime: ${anime.title} with service $service" } } } + + syncChapterProgressWithTrack.await(animeId, track, tracker) + } + } + + suspend fun bindEnhancedTrackers(anime: Anime, source: AnimeSource) = withNonCancellableContext { + withIOContext { + getTracks.await(anime.id) + .filterIsInstance() + .filter { it.accept(source) } + .forEach { service -> + try { + service.match(anime)?.let { track -> + track.anime_id = anime.id + (service as Tracker).animeService.bind(track) + insertTrack.await(track.toDomainTrack()!!) + + syncChapterProgressWithTrack.await( + anime.id, + track.toDomainTrack()!!, + service.animeService, + ) + } + } catch (e: Exception) { + logcat( + LogPriority.WARN, + e, + ) { "Could not match anime: ${anime.title} with service $service" } + } + } + } } } diff --git a/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt index 4b536906b..d16867a75 100644 --- a/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt @@ -43,7 +43,10 @@ class DelayedAnimeTrackingUpdateJob(private val context: Context, workerParams: track?.copy(lastEpisodeSeen = it.lastEpisodeSeen.toDouble()) } .forEach { animeTrack -> - logcat(LogPriority.DEBUG) { "Updating delayed track item: ${animeTrack.animeId}, last chapter read: ${animeTrack.lastEpisodeSeen}" } + logcat(LogPriority.DEBUG) { + "Updating delayed track item: ${animeTrack.animeId}" + + ", last chapter read: ${animeTrack.lastEpisodeSeen}" + } trackEpisode.await(context, animeTrack.animeId, animeTrack.lastEpisodeSeen) } } diff --git a/app/src/main/java/eu/kanade/domain/track/manga/interactor/AddMangaTracks.kt b/app/src/main/java/eu/kanade/domain/track/manga/interactor/AddMangaTracks.kt index aecdb86df..cde8b66fd 100644 --- a/app/src/main/java/eu/kanade/domain/track/manga/interactor/AddMangaTracks.kt +++ b/app/src/main/java/eu/kanade/domain/track/manga/interactor/AddMangaTracks.kt @@ -1,45 +1,108 @@ package eu.kanade.domain.track.manga.interactor +import eu.kanade.domain.track.manga.model.toDbTrack import eu.kanade.domain.track.manga.model.toDomainTrack +import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker +import eu.kanade.tachiyomi.data.track.MangaTracker import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.source.MangaSource +import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import logcat.LogPriority +import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.domain.history.manga.interactor.GetMangaHistory +import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.track.manga.interactor.GetMangaTracks import tachiyomi.domain.track.manga.interactor.InsertMangaTrack +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.time.ZoneOffset class AddMangaTracks( private val getTracks: GetMangaTracks, private val insertTrack: InsertMangaTrack, private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack, + private val getChaptersByMangaId: GetChaptersByMangaId, ) { - suspend fun bindEnhancedTracks(manga: Manga, source: MangaSource) = withNonCancellableContext { - getTracks.await(manga.id) - .filterIsInstance() - .filter { it.accept(source) } - .forEach { service -> - try { - service.match(manga)?.let { track -> - track.manga_id = manga.id - (service as Tracker).mangaService.bind(track) - insertTrack.await(track.toDomainTrack()!!) + // TODO: update all trackers based on common data + suspend fun bind(tracker: MangaTracker, item: MangaTrack, mangaId: Long) = withNonCancellableContext { + withIOContext { + val allChapters = getChaptersByMangaId.await(mangaId) + val hasReadChapters = allChapters.any { it.read } + tracker.bind(item, hasReadChapters) - syncChapterProgressWithTrack.await( - manga.id, - track.toDomainTrack()!!, - service.mangaService, + var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext + + insertTrack.await(track) + + // TODO: merge into [SyncChapterProgressWithTrack]? + // Update chapter progress if newer chapters marked read locally + if (hasReadChapters) { + val latestLocalReadChapterNumber = allChapters + .sortedBy { it.chapterNumber } + .takeWhile { it.read } + .lastOrNull() + ?.chapterNumber ?: -1.0 + + if (latestLocalReadChapterNumber > track.lastChapterRead) { + track = track.copy( + lastChapterRead = latestLocalReadChapterNumber, + ) + tracker.setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt()) + } + + if (track.startDate <= 0) { + val firstReadChapterDate = Injekt.get().await(mangaId) + .sortedBy { it.readAt } + .firstOrNull() + ?.readAt + + firstReadChapterDate?.let { + val startDate = firstReadChapterDate.time.convertEpochMillisZone( + ZoneOffset.systemDefault(), + ZoneOffset.UTC, ) + track = track.copy( + startDate = startDate, + ) + tracker.setRemoteStartDate(track.toDbTrack(), startDate) } - } catch (e: Exception) { - logcat( - LogPriority.WARN, - e, - ) { "Could not match manga: ${manga.title} with service $service" } } } + + syncChapterProgressWithTrack.await(mangaId, track, tracker) + } + } + + suspend fun bindEnhancedTrackers(manga: Manga, source: MangaSource) = withNonCancellableContext { + withIOContext { + getTracks.await(manga.id) + .filterIsInstance() + .filter { it.accept(source) } + .forEach { service -> + try { + service.match(manga)?.let { track -> + track.manga_id = manga.id + (service as Tracker).mangaService.bind(track) + insertTrack.await(track.toDomainTrack()!!) + + syncChapterProgressWithTrack.await( + manga.id, + track.toDomainTrack()!!, + service.mangaService, + ) + } + } catch (e: Exception) { + logcat( + LogPriority.WARN, + e, + ) { "Could not match manga: ${manga.title} with service $service" } + } + } + } } } diff --git a/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt index 2be1a9b5f..75f2dadd0 100644 --- a/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt @@ -43,7 +43,9 @@ class DelayedMangaTrackingUpdateJob(private val context: Context, workerParams: track?.copy(lastChapterRead = it.lastChapterRead.toDouble()) } .forEach { track -> - logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" } + logcat(LogPriority.DEBUG) { + "Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}" + } trackChapter.await(context, track.mangaId, track.lastChapterRead) } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt index 3c0abdc58..ed64044f4 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt @@ -74,7 +74,8 @@ internal fun GlobalSearchContent( items.forEach { (source, result) -> item(key = source.id) { GlobalSearchResultItem( - title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name, + title = fromSourceId + ?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name, subtitle = LocaleHelper.getDisplayName(source.lang), onClick = { onClickSource(source) }, ) { diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BaseAnimeSourceItem.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BaseAnimeSourceItem.kt index e06ab067d..5cc886fc3 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BaseAnimeSourceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BaseAnimeSourceItem.kt @@ -26,7 +26,9 @@ fun BaseAnimeSourceItem( action: @Composable RowScope.(AnimeSource) -> Unit = {}, content: @Composable RowScope.(AnimeSource, String?) -> Unit = defaultContent, ) { - val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent } + val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { + showLanguageInContent + } BaseBrowseItem( modifier = modifier, onClickItem = onClickItem, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceToolbar.kt index 95e85b4c8..d588babb4 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceToolbar.kt @@ -56,7 +56,11 @@ fun BrowseAnimeSourceToolbar( actions = listOfNotNull( AppBar.Action( title = stringResource(R.string.action_display_mode), - icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule, + icon = if (displayMode == LibraryDisplayMode.List) { + Icons.Filled.ViewList + } else { + Icons.Filled.ViewModule + }, onClick = { selectingDisplayMode = true }, ), if (isLocalSource) { diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt index df4f7ea35..c27856b0e 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt @@ -74,7 +74,8 @@ internal fun GlobalSearchContent( items.forEach { (source, result) -> item(key = source.id) { GlobalSearchResultItem( - title = fromSourceId?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name, + title = fromSourceId + ?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name, subtitle = LocaleHelper.getDisplayName(source.lang), onClick = { onClickSource(source) }, ) { diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BaseMangaSourceItem.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BaseMangaSourceItem.kt index ecb86c6a2..247e19f45 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BaseMangaSourceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BaseMangaSourceItem.kt @@ -26,7 +26,9 @@ fun BaseMangaSourceItem( action: @Composable RowScope.(Source) -> Unit = {}, content: @Composable RowScope.(Source, String?) -> Unit = defaultContent, ) { - val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent } + val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { + showLanguageInContent + } BaseBrowseItem( modifier = modifier, onClickItem = onClickItem, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceToolbar.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceToolbar.kt index f9fb4bca9..a55ac164d 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceToolbar.kt @@ -56,7 +56,11 @@ fun BrowseMangaSourceToolbar( actions = listOfNotNull( AppBar.Action( title = stringResource(R.string.action_display_mode), - icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule, + icon = if (displayMode == LibraryDisplayMode.List) { + Icons.Filled.ViewList + } else { + Icons.Filled.ViewModule + }, onClick = { selectingDisplayMode = true }, ), if (isLocalSource) { diff --git a/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt b/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt index afcf6886d..a7d830af5 100644 --- a/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/category/ChangeCategoryDialog.kt @@ -76,8 +76,14 @@ fun ChangeCategoryDialog( onClick = { onDismissRequest() onConfirm( - selection.filter { it is CheckboxState.State.Checked || it is CheckboxState.TriState.Include }.map { it.value.id }, - selection.filter { it is CheckboxState.State.None || it is CheckboxState.TriState.None }.map { it.value.id }, + selection.filter { + it is CheckboxState.State.Checked || + it is CheckboxState.TriState.Include + }.map { it.value.id }, + selection.filter { + it is CheckboxState.State.None || + it is CheckboxState.TriState.None + }.map { it.value.id }, ) }, ) { diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt index a37c12c48..b7e280050 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryDialogs.kt @@ -87,7 +87,11 @@ fun CategoryCreateDialog( onValueChange = { name = it }, label = { Text(text = stringResource(R.string.name)) }, supportingText = { - val msgRes = if (name.isNotEmpty() && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain + val msgRes = if (name.isNotEmpty() && nameAlreadyExists) { + R.string.error_category_exists + } else { + R.string.information_required_plain + } Text(text = stringResource(msgRes)) }, isError = name.isNotEmpty() && nameAlreadyExists, @@ -147,7 +151,11 @@ fun CategoryRenameDialog( }, label = { Text(text = stringResource(R.string.name)) }, supportingText = { - val msgRes = if (valueHasChanged && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain + val msgRes = if (valueHasChanged && nameAlreadyExists) { + R.string.error_category_exists + } else { + R.string.information_required_plain + } Text(text = stringResource(msgRes)) }, isError = valueHasChanged && nameAlreadyExists, diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt index ccb23d55e..41784efe3 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt @@ -126,7 +126,11 @@ fun EntryBottomActionMenu( ) } if (onRemoveBookmarkClicked != null) { - val removeBookmark = if (isManga) R.string.action_remove_bookmark else R.string.action_remove_bookmark_episode + val removeBookmark = if (isManga) { + R.string.action_remove_bookmark + } else { + R.string.action_remove_bookmark_episode + } Button( title = stringResource(removeBookmark), icon = Icons.Outlined.BookmarkRemove, @@ -156,7 +160,11 @@ fun EntryBottomActionMenu( ) } if (onMarkPreviousAsViewedClicked != null) { - val previousUnviewed = if (isManga) R.string.action_mark_previous_as_read else R.string.action_mark_previous_as_seen + val previousUnviewed = if (isManga) { + R.string.action_mark_previous_as_read + } else { + R.string.action_mark_previous_as_seen + } Button( title = stringResource(previousUnviewed), icon = ImageVector.vectorResource(R.drawable.ic_done_prev_24dp), 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 24813da6e..5f39776bb 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 @@ -510,7 +510,9 @@ private fun AnimeScreenSmallImpl( timer -= 1000L } } - if (timer > 0L && showNextEpisodeAirTime && state.anime.status.toInt() != SAnime.COMPLETED) { + if (timer > 0L && showNextEpisodeAirTime && + state.anime.status.toInt() != SAnime.COMPLETED + ) { NextEpisodeAiringListItem( title = stringResource( R.string.display_mode_episode, @@ -694,7 +696,11 @@ fun AnimeScreenLargeImpl( val isWatching = remember(state.episodes) { state.episodes.fastAny { it.episode.seen } } - Text(text = stringResource(if (isWatching) R.string.action_resume else R.string.action_start)) + Text( + text = stringResource( + if (isWatching) R.string.action_resume else R.string.action_start, + ), + ) }, icon = { Icon( @@ -795,7 +801,9 @@ fun AnimeScreenLargeImpl( timer -= 1000L } } - if (timer > 0L && showNextEpisodeAirTime && state.anime.status.toInt() != SAnime.COMPLETED) { + if (timer > 0L && showNextEpisodeAirTime && + state.anime.status.toInt() != SAnime.COMPLETED + ) { NextEpisodeAiringListItem( title = stringResource( R.string.display_mode_episode, diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt index 04339a335..bafc6243f 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeSettingsDialog.kt @@ -74,7 +74,8 @@ fun EpisodeSettingsDialog( 0 -> { FilterPage( downloadFilter = anime?.downloadedFilter ?: TriState.DISABLED, - onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { anime?.forceDownloaded() == true }, + onDownloadFilterChanged = onDownloadFilterChanged + .takeUnless { anime?.forceDownloaded() == true }, unseenFilter = anime?.unseenFilter ?: TriState.DISABLED, onUnseenFilterChanged = onUnseenFilterChanged, bookmarkedFilter = anime?.bookmarkedFilter ?: TriState.DISABLED, @@ -146,6 +147,11 @@ private fun SortPage( sortDescending = sortDescending.takeIf { sortingMode == Anime.EPISODE_SORTING_UPLOAD_DATE }, onClick = { onItemSelected(Anime.EPISODE_SORTING_UPLOAD_DATE) }, ) + SortItem( + label = stringResource(R.string.action_sort_alpha), + sortDescending = sortDescending.takeIf { sortingMode == Anime.EPISODE_SORTING_ALPHABET }, + onClick = { onItemSelected(Anime.EPISODE_SORTING_ALPHABET) }, + ) } @Composable diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt index aa995715b..a6a7aa3ed 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt @@ -600,7 +600,9 @@ private fun AnimeSummary( val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down) Icon( painter = rememberAnimatedVectorPainter(image, !expanded), - contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand), + contentDescription = stringResource( + if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand, + ), tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())), ) 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 3767e93ca..9b0ba3321 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 @@ -82,7 +82,8 @@ fun ChapterSettingsDialog( 0 -> { FilterPage( downloadFilter = manga?.downloadedFilter ?: TriState.DISABLED, - onDownloadFilterChanged = onDownloadFilterChanged.takeUnless { manga?.forceDownloaded() == true }, + onDownloadFilterChanged = onDownloadFilterChanged + .takeUnless { manga?.forceDownloaded() == true }, unreadFilter = manga?.unreadFilter ?: TriState.DISABLED, onUnreadFilterChanged = onUnreadFilterChanged, bookmarkedFilter = manga?.bookmarkedFilter ?: TriState.DISABLED, @@ -154,6 +155,11 @@ private fun SortPage( sortDescending = sortDescending.takeIf { sortingMode == Manga.CHAPTER_SORTING_UPLOAD_DATE }, onClick = { onItemSelected(Manga.CHAPTER_SORTING_UPLOAD_DATE) }, ) + SortItem( + label = stringResource(R.string.action_sort_alpha), + sortDescending = sortDescending.takeIf { sortingMode == Manga.CHAPTER_SORTING_ALPHABET }, + onClick = { onItemSelected(Manga.CHAPTER_SORTING_ALPHABET) }, + ) } @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 f3faf76ab..7db2f6a21 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 @@ -636,7 +636,9 @@ fun MangaScreenLargeImpl( val isReading = remember(state.chapters) { state.chapters.fastAny { it.chapter.read } } - Text(text = stringResource(if (isReading) R.string.action_resume else R.string.action_start)) + Text( + text = stringResource(if (isReading) R.string.action_resume else R.string.action_start), + ) }, icon = { Icon( diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt index 37071c7cf..129b48a7d 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt @@ -599,7 +599,9 @@ private fun MangaSummary( val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_caret_down) Icon( painter = rememberAnimatedVectorPainter(image, !expanded), - contentDescription = stringResource(if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand), + contentDescription = stringResource( + if (expanded) R.string.manga_info_collapse else R.string.manga_info_expand, + ), tint = MaterialTheme.colorScheme.onBackground, modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())), ) 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 7eec79884..8d9abbd7d 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryDialog.kt @@ -34,7 +34,11 @@ fun HistoryDeleteDialog( Column( verticalArrangement = Arrangement.spacedBy(8.dp), ) { - val subtitle = if (isManga) R.string.dialog_with_checkbox_remove_description else R.string.dialog_with_checkbox_remove_description_anime + val subtitle = if (isManga) { + R.string.dialog_with_checkbox_remove_description + } else { + R.string.dialog_with_checkbox_remove_description_anime + } Text(text = stringResource(subtitle)) LabeledCheckbox( diff --git a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt index b623c4866..7fa23b2c8 100644 --- a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt @@ -165,8 +165,16 @@ private fun ColumnScope.SortPage( onClick = { val isTogglingDirection = sortingMode == mode val direction = when { - isTogglingDirection -> if (sortDescending) AnimeLibrarySort.Direction.Ascending else AnimeLibrarySort.Direction.Descending - else -> if (sortDescending) AnimeLibrarySort.Direction.Descending else AnimeLibrarySort.Direction.Ascending + isTogglingDirection -> if (sortDescending) { + AnimeLibrarySort.Direction.Ascending + } else { + AnimeLibrarySort.Direction.Descending + } + else -> if (sortDescending) { + AnimeLibrarySort.Direction.Descending + } else { + AnimeLibrarySort.Direction.Ascending + } } screenModel.setSort(category, mode, direction) }, diff --git a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt index 2678a45c3..c1cc35769 100644 --- a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt @@ -148,6 +148,13 @@ private fun ColumnScope.SortPage( val sortingMode = category.sort.type val sortDescending = !category.sort.isAscending + val trackerSortOption = + if (screenModel.trackers.isEmpty()) { + emptyList() + } else { + listOf(R.string.action_sort_tracker_score to MangaLibrarySort.Type.TrackerMean) + } + listOf( R.string.action_sort_alpha to MangaLibrarySort.Type.Alphabetical, R.string.action_sort_total to MangaLibrarySort.Type.TotalChapters, @@ -157,15 +164,23 @@ private fun ColumnScope.SortPage( R.string.action_sort_latest_chapter to MangaLibrarySort.Type.LatestChapter, R.string.action_sort_chapter_fetch_date to MangaLibrarySort.Type.ChapterFetchDate, R.string.action_sort_date_added to MangaLibrarySort.Type.DateAdded, - ).map { (titleRes, mode) -> + ).plus(trackerSortOption).map { (titleRes, mode) -> SortItem( label = stringResource(titleRes), sortDescending = sortDescending.takeIf { sortingMode == mode }, onClick = { val isTogglingDirection = sortingMode == mode val direction = when { - isTogglingDirection -> if (sortDescending) MangaLibrarySort.Direction.Ascending else MangaLibrarySort.Direction.Descending - else -> if (sortDescending) MangaLibrarySort.Direction.Descending else MangaLibrarySort.Direction.Ascending + isTogglingDirection -> if (sortDescending) { + MangaLibrarySort.Direction.Ascending + } else { + MangaLibrarySort.Direction.Descending + } + else -> if (sortDescending) { + MangaLibrarySort.Direction.Descending + } else { + MangaLibrarySort.Direction.Ascending + } } screenModel.setSort(category, mode, direction) }, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt index a1a5040b9..b6cd14605 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/Commons.kt @@ -31,11 +31,13 @@ fun getCategoriesLabel( val includedItemsText = when { // Some selected, but not all - includedCategories.isNotEmpty() && includedCategories.size != allCategories.size -> includedCategories.joinToString { - it.visualName( - context, - ) - } + includedCategories.isNotEmpty() && + includedCategories.size != allCategories.size -> + includedCategories.joinToString { + it.visualName( + context, + ) + } // All explicitly selected includedCategories.size == allCategories.size -> stringResource(R.string.all) allExcluded -> stringResource(R.string.none) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt index 49b942590..3f36ccd7f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt @@ -162,7 +162,8 @@ object SettingsDownloadScreen : SearchableSettings { return remember { val file = UniFile.fromFile( File( - "${Environment.getExternalStorageDirectory().absolutePath}${File.separator}${Environment.DIRECTORY_DOWNLOADS}${File.separator}$appName", + Environment.getExternalStorageDirectory().absolutePath + + "${File.separator}${Environment.DIRECTORY_DOWNLOADS}${File.separator}$appName", "downloads", ), )!! 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 21be0053e..a5ab3d4bb 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 @@ -371,6 +371,11 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPreferences.readWithLongTap(), title = stringResource(R.string.pref_read_with_long_tap), ), + Preference.PreferenceItem.SwitchPreference( + pref = readerPreferences.folderPerManga(), + title = stringResource(R.string.pref_create_folder_per_manga), + subtitle = stringResource(R.string.pref_create_folder_per_manga_summary), + ), ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt index 5f3124553..abb73a272 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt @@ -227,6 +227,9 @@ object AboutScreen : Screen() { is GetApplicationRelease.Result.NoNewUpdate -> { context.toast(R.string.update_check_no_new_updates) } + is GetApplicationRelease.Result.OsTooOld -> { + context.toast(R.string.update_check_eol) + } else -> {} } } catch (e: Exception) { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt index 0e3bcde93..464cf2419 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt @@ -76,15 +76,28 @@ class DebugInfoScreen : Screen() { val status by produceState(initialValue = "-") { val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode value = when (result) { - ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed" - ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled" - ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> "Compiled non-matching" - ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ, - ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE, - ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST, + ProfileVerifier.CompilationStatus + .RESULT_CODE_NO_PROFILE, + -> "No profile installed" + ProfileVerifier.CompilationStatus + .RESULT_CODE_COMPILED_WITH_PROFILE, + -> "Compiled" + ProfileVerifier.CompilationStatus + .RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING, + -> "Compiled non-matching" + ProfileVerifier.CompilationStatus + .RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ, + ProfileVerifier.CompilationStatus + .RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE, + ProfileVerifier.CompilationStatus + .RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST, -> "Error $result" - ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported" - ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation" + ProfileVerifier.CompilationStatus + .RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION, + -> "Not supported" + ProfileVerifier.CompilationStatus + .RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION, + -> "Pending compilation" else -> "Unknown code $result" } } 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 69df2a727..31319744b 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/PageIndicatorText.kt @@ -4,11 +4,14 @@ import androidx.compose.foundation.layout.Box import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment 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.unit.sp +import eu.kanade.presentation.theme.TachiyomiTheme +import tachiyomi.presentation.core.util.ThemePreviews @Composable fun PageIndicatorText( @@ -19,24 +22,36 @@ fun PageIndicatorText( val text = "$currentPage / $totalPages" - Box { + val style = TextStyle( + color = Color(235, 235, 235), + fontSize = MaterialTheme.typography.bodySmall.fontSize, + fontWeight = FontWeight.Bold, + letterSpacing = 1.sp, + ) + val strokeStyle = style.copy( + color = Color(45, 45, 45), + drawStyle = Stroke(width = 4f), + ) + + Box( + contentAlignment = Alignment.Center, + ) { Text( text = text, - color = Color(45, 45, 45), - fontSize = MaterialTheme.typography.bodySmall.fontSize, - fontWeight = FontWeight.Bold, - letterSpacing = 1.sp, - style = TextStyle.Default.copy( - drawStyle = Stroke(width = 4f), - ), + style = strokeStyle, ) Text( text = text, - color = Color(235, 235, 235), - fontSize = MaterialTheme.typography.bodySmall.fontSize, - fontWeight = FontWeight.Bold, - letterSpacing = 1.sp, + style = style, ) } } + +@ThemePreviews +@Composable +private fun PageIndicatorTextPreview() { + TachiyomiTheme { + PageIndicatorText(currentPage = 10, totalPages = 69) + } +} 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 e044d5ccf..7b360b04d 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 @@ -99,7 +99,9 @@ fun ReaderAppBars( AppBarActions( listOfNotNull( AppBar.Action( - title = stringResource(if (bookmarked) R.string.action_remove_bookmark else R.string.action_bookmark), + title = stringResource( + if (bookmarked) R.string.action_remove_bookmark else R.string.action_bookmark, + ), icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder, onClick = onToggleBookmarked, ), diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt index 04133bc49..750b05d6b 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt @@ -167,7 +167,9 @@ fun WebViewScreenContent( modifier = Modifier .clip(MaterialTheme.shapes.small) .clickable { - uriHandler.openUri("https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare") + uriHandler.openUri( + "https://tachiyomi.org/docs/guides/troubleshooting/#cloudflare", + ) }, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 6a1649f55..14c6e0f16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -11,6 +11,7 @@ import android.content.IntentFilter import android.os.Build import android.os.Looper import android.webkit.WebView +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner @@ -203,7 +204,7 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { if (chromiumElement?.methodName.equals("getAll", ignoreCase = true)) { return WebViewUtil.SPOOF_PACKAGE_NAME } - } catch (e: Exception) { + } catch (_: Exception) { } } return super.getPackageName() @@ -247,7 +248,12 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { fun register() { if (!registered) { - registerReceiver(this, IntentFilter(ACTION_DISABLE_INCOGNITO_MODE)) + ContextCompat.registerReceiver( + this@App, + this, + IntentFilter(ACTION_DISABLE_INCOGNITO_MODE), + ContextCompat.RECEIVER_NOT_EXPORTED, + ) registered = true } } @@ -266,7 +272,7 @@ private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNIT /** * Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it. */ -internal object CoilDiskCache { +private object CoilDiskCache { private const val FOLDER_NAME = "image_cache" private var instance: DiskCache? = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 6c9461a75..5c2927c59 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -304,7 +304,9 @@ object Migrations { SecurityPreferences.SecureScreenMode.ALWAYS, ) } - if (DeviceUtil.isMiui && basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) { + if (DeviceUtil.isMiui && + basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER + ) { basePreferences.extensionInstaller().set( BasePreferences.ExtensionInstaller.LEGACY, ) @@ -507,7 +509,10 @@ object Migrations { if (oldVersion < 107) { replacePreferences( preferenceStore = preferenceStore, - filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }, + filterPredicate = { + it.key.startsWith("pref_mangasync_") || + it.key.startsWith("track_token_") + }, newKey = { Preference.privateKey(it) }, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt index d5fb7643a..12918457e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt @@ -150,7 +150,9 @@ class BackupRestorer( private suspend fun performRestore(uri: Uri, sync: Boolean): Boolean { val backup = BackupUtil.decodeBackup(context, uri) - restoreAmount = backup.backupManga.size + backup.backupAnime.size + 3 // +3 for categories, app prefs, source prefs + restoreAmount = + backup.backupManga.size + + backup.backupAnime.size + 3 // +3 for categories, app prefs, source prefs // Restore categories if (backup.backupCategories.isNotEmpty()) { @@ -1262,7 +1264,12 @@ class BackupRestorer( } restoreProgress += 1 - showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.extension_settings), context.getString(R.string.restoring_backup)) + showRestoreProgress( + restoreProgress, + restoreAmount, + context.getString(R.string.extension_settings), + context.getString(R.string.restoring_backup), + ) } private fun restoreExtensions(extensions: List) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt index 64d16fba2..b46da6846 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt @@ -72,7 +72,22 @@ data class BackupAnimeTracking( } } -val backupAnimeTrackMapper = { _id: Long, anime_id: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastEpisodeSeen: Double, totalEpisodes: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long -> +val backupAnimeTrackMapper = { + _id: Long, + anime_id: + Long, + syncId: Long, + mediaId: Long, + libraryId: Long?, + title: String, + lastEpisodeSeen: Double, + totalEpisodes: Long, + status: Long, + score: Double, + remoteUrl: String, + startDate: Long, + finishDate: Long, + -> BackupAnimeTracking( syncId = syncId.toInt(), mediaId = mediaId, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt index c9a3bb8b5..dd7cfae2c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt @@ -39,7 +39,21 @@ data class BackupChapter( } } -val backupChapterMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, read: Boolean, bookmark: Boolean, lastPageRead: Long, chapterNumber: Double, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long -> +val backupChapterMapper = { + _: Long, + _: Long, + url: String, + name: String, + scanlator: String?, + read: Boolean, + bookmark: Boolean, + lastPageRead: Long, + chapterNumber: Double, + source_order: Long, + dateFetch: Long, + dateUpload: Long, + lastModifiedAt: Long, + -> BackupChapter( url = url, name = name, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt index eb30ce972..690ea2d6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt @@ -41,7 +41,22 @@ data class BackupEpisode( } } -val backupEpisodeMapper = { _: Long, _: Long, url: String, name: String, scanlator: String?, seen: Boolean, bookmark: Boolean, lastSecondSeen: Long, totalSeconds: Long, episodeNumber: Double, source_order: Long, dateFetch: Long, dateUpload: Long, lastModifiedAt: Long -> +val backupEpisodeMapper = { + _: Long, + _: Long, + url: String, + name: String, + scanlator: String?, + seen: Boolean, + bookmark: Boolean, + lastSecondSeen: Long, + totalSeconds: Long, + episodeNumber: Double, + source_order: Long, + dateFetch: Long, + dateUpload: Long, + lastModifiedAt: Long, + -> BackupEpisode( url = url, name = name, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt index aaa7208c0..0ca8d00db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt @@ -54,7 +54,20 @@ data class BackupTracking( } val backupTrackMapper = { - _: Long, _: Long, syncId: Long, mediaId: Long, libraryId: Long?, title: String, lastChapterRead: Double, totalChapters: Long, status: Long, score: Double, remoteUrl: String, startDate: Long, finishDate: Long -> + _: Long, + _: Long, + syncId: Long, + mediaId: Long, + libraryId: Long?, + title: String, + lastChapterRead: Double, + totalChapters: Long, + status: Long, + score: Double, + remoteUrl: String, + startDate: Long, + finishDate: Long, + -> BackupTracking( syncId = syncId.toInt(), mediaId = mediaId, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadCache.kt index d87ca7b7e..269d7d711 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadCache.kt @@ -138,7 +138,10 @@ class AnimeDownloadCache( if (sourceDir != null) { val animeDir = sourceDir.animeDirs[provider.getAnimeDirName(animeTitle)] if (animeDir != null) { - return provider.getValidEpisodeDirNames(episodeName, episodeScanlator).any { it in animeDir.episodeDirs } + return provider.getValidEpisodeDirNames( + episodeName, + episodeScanlator, + ).any { it in animeDir.episodeDirs } } } return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt index 2493bdf7a..f02b7b6a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt @@ -206,7 +206,9 @@ class AnimeDownloader( val activeDownloadsFlow = queueState.transformLatest { queue -> while (true) { val activeDownloads = queue.asSequence() - .filter { it.status.value <= AnimeDownload.State.DOWNLOADING.value } // Ignore completed downloads, leave them in the queue + .filter { + it.status.value <= AnimeDownload.State.DOWNLOADING.value + } // Ignore completed downloads, leave them in the queue .groupBy { it.source } .toList().take(3) // Concurrently download from 5 different sources .map { (_, downloads) -> downloads.first() } @@ -554,7 +556,8 @@ class AnimeDownloader( val ffmpegOptions = getFFmpegOptions(video, headerOptions, ffmpegFilename()) val ffprobeCommand = { file: String, ffprobeHeaders: String? -> FFmpegKitConfig.parseArguments( - "${ffprobeHeaders?.plus(" ") ?: ""}-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 \"$file\"", + "${ffprobeHeaders?.plus(" ") ?: ""}-v error -show_entries " + + "format=duration -of default=noprint_wrappers=1:nokey=1 \"$file\"", ) } var duration = 0L @@ -905,7 +908,9 @@ class AnimeDownloader( val downloads = queue.filter { predicate(it) } store.removeAll(downloads) downloads.forEach { download -> - if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { + if (download.status == AnimeDownload.State.DOWNLOADING || + download.status == AnimeDownload.State.QUEUE + ) { download.status = AnimeDownload.State.NOT_DOWNLOADED } } @@ -927,7 +932,9 @@ class AnimeDownloader( it.forEach { download -> download.progressSubject = null download.progressCallback = null - if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { + if (download.status == AnimeDownload.State.DOWNLOADING || + download.status == AnimeDownload.State.QUEUE + ) { download.status = AnimeDownload.State.NOT_DOWNLOADED } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt index 83e6ccedd..76a95407c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadCache.kt @@ -391,9 +391,10 @@ class MangaDownloadCache( // Folder of images it.isDirectory -> it.name // CBZ files - it.isFile && it.name?.endsWith(".cbz") == true -> it.name!!.substringBeforeLast( - ".cbz", - ) + it.isFile && it.name?.endsWith(".cbz") == true -> + it.name!!.substringBeforeLast( + ".cbz", + ) // Anything else is irrelevant else -> null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 8d889cedd..9dfe2785c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -206,7 +206,9 @@ class MangaDownloader( val activeDownloadsFlow = queueState.transformLatest { queue -> while (true) { val activeDownloads = queue.asSequence() - .filter { it.status.value <= MangaDownload.State.DOWNLOADING.value } // Ignore completed downloads, leave them in the queue + .filter { + it.status.value <= MangaDownload.State.DOWNLOADING.value + } // Ignore completed downloads, leave them in the queue .groupBy { it.source } .toList().take(5) // Concurrently download from 5 different sources .map { (_, downloads) -> downloads.first() } @@ -726,7 +728,9 @@ class MangaDownloader( val downloads = queue.filter { predicate(it) } store.removeAll(downloads) downloads.forEach { download -> - if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { + if (download.status == MangaDownload.State.DOWNLOADING || + download.status == MangaDownload.State.QUEUE + ) { download.status = MangaDownload.State.NOT_DOWNLOADED } } @@ -746,7 +750,9 @@ class MangaDownloader( private fun _clearQueue() { _queueState.update { it.forEach { download -> - if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { + if (download.status == MangaDownload.State.DOWNLOADING || + download.status == MangaDownload.State.QUEUE + ) { download.status = MangaDownload.State.NOT_DOWNLOADED } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt index 188fe0ca3..f828d9c6c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt @@ -287,7 +287,8 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa hasDownloads.set(true) } - libraryPreferences.newAnimeUpdatesCount().getAndSet { it + newChapters.size } + libraryPreferences.newAnimeUpdatesCount() + .getAndSet { it + newChapters.size } // Convert to the manga that contains new chapters newUpdates.add(anime to newChapters.toTypedArray()) @@ -296,7 +297,9 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa val errorMessage = when (e) { is NoEpisodesException -> context.getString(R.string.no_chapters_error) // failedUpdates will already have the source, don't need to copy it into the message - is AnimeSourceNotInstalledException -> context.getString(R.string.loader_not_implemented_error) + is AnimeSourceNotInstalledException -> context.getString( + R.string.loader_not_implemented_error, + ) else -> e.message } failedUpdates.add(anime to errorMessage) @@ -506,7 +509,9 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa if (interval > 0) { val restrictions = preferences.autoUpdateDeviceRestrictions().get() val constraints = Constraints( - requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED }, + requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { + NetworkType.UNMETERED + } else { NetworkType.CONNECTED }, requiresCharging = DEVICE_CHARGING in restrictions, requiresBatteryNotLow = true, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt index c2b4d7a96..2e4c3fd71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt @@ -86,7 +86,12 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { } else { val updatingText = anime.joinToString("\n") { it.title.chop(40) } progressNotificationBuilder - .setContentTitle(context.getString(R.string.notification_updating_progress, percentFormatter.format(current.toFloat() / total))) + .setContentTitle( + context.getString( + R.string.notification_updating_progress, + percentFormatter.format(current.toFloat() / total), + ), + ) .setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) } @@ -381,7 +386,8 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { companion object { // TODO: Change when implemented on Aniyomi website - const val HELP_WARNING_URL = "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" + const val HELP_WARNING_URL = + "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt index 828077819..0489ba591 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt @@ -286,7 +286,8 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa downloadChapters(manga, newChapters) hasDownloads.set(true) } - libraryPreferences.newMangaUpdatesCount().getAndSet { it + newChapters.size } + libraryPreferences.newMangaUpdatesCount() + .getAndSet { it + newChapters.size } // Convert to the manga that contains new chapters newUpdates.add(manga to newChapters.toTypedArray()) @@ -295,7 +296,9 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa val errorMessage = when (e) { is NoChaptersException -> context.getString(R.string.no_chapters_error) // failedUpdates will already have the source, don't need to copy it into the message - is SourceNotInstalledException -> context.getString(R.string.loader_not_implemented_error) + is SourceNotInstalledException -> context.getString( + R.string.loader_not_implemented_error, + ) else -> e.message } failedUpdates.add(manga to errorMessage) @@ -504,7 +507,9 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa if (interval > 0) { val restrictions = preferences.autoUpdateDeviceRestrictions().get() val constraints = Constraints( - requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { NetworkType.UNMETERED } else { NetworkType.CONNECTED }, + requiredNetworkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) { + NetworkType.UNMETERED + } else { NetworkType.CONNECTED }, requiresCharging = DEVICE_CHARGING in restrictions, requiresBatteryNotLow = true, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt index ec418a647..618b987da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt @@ -86,7 +86,12 @@ class MangaLibraryUpdateNotifier(private val context: Context) { } else { val updatingText = manga.joinToString("\n") { it.title.chop(40) } progressNotificationBuilder - .setContentTitle(context.getString(R.string.notification_updating_progress, percentFormatter.format(current.toFloat() / total))) + .setContentTitle( + context.getString( + R.string.notification_updating_progress, + percentFormatter.format(current.toFloat() / total), + ), + ) .setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) } @@ -384,7 +389,8 @@ class MangaLibraryUpdateNotifier(private val context: Context) { companion object { // TODO: Change when implemented on Aniyomi website - const val HELP_WARNING_URL = "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" + const val HELP_WARNING_URL = + "https://aniyomi.org/docs/faq/library#why-am-i-warned-about-large-bulk-updates-and-downloads" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index fff968dbe..d2e41d1c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -892,12 +892,21 @@ class NotificationReceiver : BroadcastReceiver() { * @param context context of application * @return [PendingIntent] */ - internal fun downloadAppUpdatePendingBroadcast(context: Context, url: String, title: String? = null): PendingIntent { + internal fun downloadAppUpdatePendingBroadcast( + context: Context, + url: String, + title: String? = null, + ): PendingIntent { return Intent(context, NotificationReceiver::class.java).run { action = ACTION_START_APP_UPDATE putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_URL, url) title?.let { putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_TITLE, it) } - PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + PendingIntent.getBroadcast( + context, + 0, + this, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index 8717f43a5..732fbca0e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -175,12 +175,19 @@ sealed class Image( } sealed interface Location { - data class Pictures(val relativePath: String) : Location + data class Pictures(val relativePath: String) : Location { + companion object { + fun create(relativePath: String = ""): Pictures { + return Pictures(relativePath) + } + } + } data object Cache : Location fun directory(context: Context): File { return when (this) { + Cache -> context.cacheImageDir is Pictures -> { val file = File( Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), @@ -194,7 +201,6 @@ sealed interface Location { } file } - Cache -> context.cacheImageDir } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/AnimeTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/AnimeTracker.kt index 033165d99..ac5678d57 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/AnimeTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/AnimeTracker.kt @@ -1,28 +1,23 @@ package eu.kanade.tachiyomi.data.track import android.app.Application -import eu.kanade.domain.track.anime.interactor.SyncEpisodeProgressWithTrack -import eu.kanade.domain.track.anime.model.toDbTrack +import eu.kanade.domain.track.anime.interactor.AddAnimeTracks import eu.kanade.domain.track.anime.model.toDomainTrack import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch -import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.system.toast import logcat.LogPriority import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat -import tachiyomi.domain.history.anime.interactor.GetAnimeHistory -import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.time.ZoneOffset import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack +private val addTracks: AddAnimeTracks by injectLazy() private val insertTrack: InsertAnimeTrack by injectLazy() -private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack by injectLazy() interface AnimeTracker { @@ -61,55 +56,7 @@ interface AnimeTracker { suspend fun register(item: AnimeTrack, animeId: Long) { item.anime_id = animeId try { - withIOContext { - val allEpisodes = Injekt.get().await(animeId) - val hasSeenEpisodes = allEpisodes.any { it.seen } - bind(item, hasSeenEpisodes) - - var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext - - insertTrack.await(track) - - // TODO: merge into SyncChaptersWithTrackServiceTwoWay? - // Update episode progress if newer episodes marked seen locally - if (hasSeenEpisodes) { - val latestLocalSeenEpisodeNumber = allEpisodes - .sortedBy { it.episodeNumber } - .takeWhile { it.seen } - .lastOrNull() - ?.episodeNumber ?: -1.0 - - if (latestLocalSeenEpisodeNumber > track.lastEpisodeSeen) { - track = track.copy( - lastEpisodeSeen = latestLocalSeenEpisodeNumber, - ) - setRemoteLastEpisodeSeen( - track.toDbTrack(), - latestLocalSeenEpisodeNumber.toInt(), - ) - } - - if (track.startDate <= 0) { - val firstReadChapterDate = Injekt.get().await(animeId) - .sortedBy { it.seenAt } - .firstOrNull() - ?.seenAt - - firstReadChapterDate?.let { - val startDate = firstReadChapterDate.time.convertEpochMillisZone( - ZoneOffset.systemDefault(), - ZoneOffset.UTC, - ) - track = track.copy( - startDate = startDate, - ) - setRemoteStartDate(track.toDbTrack(), startDate) - } - } - } - - syncEpisodeProgressWithTrack.await(animeId, track, this@AnimeTracker) - } + addTracks.bind(this, item, animeId) } catch (e: Throwable) { withUIContext { Injekt.get().toast(e.message) } } @@ -120,11 +67,14 @@ interface AnimeTracker { if (track.status == getCompletionStatus() && track.total_episodes != 0) { track.last_episode_seen = track.total_episodes.toFloat() } - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteLastEpisodeSeen(track: AnimeTrack, episodeNumber: Int) { - if (track.last_episode_seen == 0f && track.last_episode_seen < episodeNumber && track.status != getRewatchingStatus()) { + if (track.last_episode_seen == 0f && + track.last_episode_seen < episodeNumber && + track.status != getRewatchingStatus() + ) { track.status = getWatchingStatus() } track.last_episode_seen = episodeNumber.toFloat() @@ -132,35 +82,33 @@ interface AnimeTracker { track.status = getCompletionStatus() track.finished_watching_date = System.currentTimeMillis() } - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteScore(track: AnimeTrack, scoreString: String) { track.score = indexToScore(getScoreList().indexOf(scoreString)) - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteStartDate(track: AnimeTrack, epochMillis: Long) { track.started_watching_date = epochMillis - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteFinishDate(track: AnimeTrack, epochMillis: Long) { track.finished_watching_date = epochMillis - withIOContext { updateRemote(track) } + updateRemote(track) } - private suspend fun updateRemote(track: AnimeTrack) { - withIOContext { - try { - update(track) - track.toDomainTrack(idRequired = false)?.let { - insertTrack.await(it) - } - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=${track.id}" } - withUIContext { Injekt.get().toast(e.message) } + private suspend fun updateRemote(track: AnimeTrack): Unit = withIOContext { + try { + update(track) + track.toDomainTrack(idRequired = false)?.let { + insertTrack.await(it) } + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=${track.id}" } + withUIContext { Injekt.get().toast(e.message) } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTracker.kt index a24b52f2a..da5830b07 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTracker.kt @@ -1,28 +1,23 @@ package eu.kanade.tachiyomi.data.track import android.app.Application -import eu.kanade.domain.track.manga.interactor.SyncChapterProgressWithTrack -import eu.kanade.domain.track.manga.model.toDbTrack +import eu.kanade.domain.track.manga.interactor.AddMangaTracks import eu.kanade.domain.track.manga.model.toDomainTrack import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch -import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.system.toast import logcat.LogPriority import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat -import tachiyomi.domain.history.manga.interactor.GetMangaHistory -import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.track.manga.interactor.InsertMangaTrack import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.time.ZoneOffset import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack +private val addTracks: AddMangaTracks by injectLazy() private val insertTrack: InsertMangaTrack by injectLazy() -private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack by injectLazy() interface MangaTracker { @@ -57,59 +52,10 @@ interface MangaTracker { suspend fun refresh(track: MangaTrack): MangaTrack - // TODO: move this to an interactor, and update all trackers based on common data suspend fun register(item: MangaTrack, mangaId: Long) { item.manga_id = mangaId try { - withIOContext { - val allChapters = Injekt.get().await(mangaId) - val hasReadChapters = allChapters.any { it.read } - bind(item, hasReadChapters) - - var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext - - insertTrack.await(track) - - // TODO: merge into SyncChaptersWithTrackServiceTwoWay? - // Update chapter progress if newer chapters marked read locally - if (hasReadChapters) { - val latestLocalReadChapterNumber = allChapters - .sortedBy { it.chapterNumber } - .takeWhile { it.read } - .lastOrNull() - ?.chapterNumber ?: -1.0 - - if (latestLocalReadChapterNumber > track.lastChapterRead) { - track = track.copy( - lastChapterRead = latestLocalReadChapterNumber, - ) - setRemoteLastChapterRead( - track.toDbTrack(), - latestLocalReadChapterNumber.toInt(), - ) - } - - if (track.startDate <= 0) { - val firstReadChapterDate = Injekt.get().await(mangaId) - .sortedBy { it.readAt } - .firstOrNull() - ?.readAt - - firstReadChapterDate?.let { - val startDate = firstReadChapterDate.time.convertEpochMillisZone( - ZoneOffset.systemDefault(), - ZoneOffset.UTC, - ) - track = track.copy( - startDate = startDate, - ) - setRemoteStartDate(track.toDbTrack(), startDate) - } - } - } - - syncChapterProgressWithTrack.await(mangaId, track, this@MangaTracker) - } + addTracks.bind(this, item, mangaId) } catch (e: Throwable) { withUIContext { Injekt.get().toast(e.message) } } @@ -120,47 +66,49 @@ interface MangaTracker { if (track.status == getCompletionStatus() && track.total_chapters != 0) { track.last_chapter_read = track.total_chapters.toFloat() } - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteLastChapterRead(track: MangaTrack, chapterNumber: Int) { - if (track.last_chapter_read == 0f && track.last_chapter_read < chapterNumber && track.status != getRereadingStatus()) { + if (track.last_chapter_read == 0f && + track.last_chapter_read < chapterNumber && track.status != getRereadingStatus() + ) { track.status = getReadingStatus() } track.last_chapter_read = chapterNumber.toFloat() - if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) { + if (track.total_chapters != 0 && + track.last_chapter_read.toInt() == track.total_chapters + ) { track.status = getCompletionStatus() track.finished_reading_date = System.currentTimeMillis() } - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteScore(track: MangaTrack, scoreString: String) { track.score = indexToScore(getScoreList().indexOf(scoreString)) - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteStartDate(track: MangaTrack, epochMillis: Long) { track.started_reading_date = epochMillis - withIOContext { updateRemote(track) } + updateRemote(track) } suspend fun setRemoteFinishDate(track: MangaTrack, epochMillis: Long) { track.finished_reading_date = epochMillis - withIOContext { updateRemote(track) } + updateRemote(track) } - private suspend fun updateRemote(track: MangaTrack) { - withIOContext { - try { - update(track) - track.toDomainTrack(idRequired = false)?.let { - insertTrack.await(it) - } - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=${track.id}" } - withUIContext { Injekt.get().toast(e.message) } + private suspend fun updateRemote(track: MangaTrack): Unit = withIOContext { + try { + update(track) + track.toDomainTrack(idRequired = false)?.let { + insertTrack.await(it) } + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) { "Failed to update remote track data id=${track.id}" } + withUIContext { Injekt.get().toast(e.message) } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt index 5062a9664..36296cbf0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt @@ -34,6 +34,8 @@ class TrackerManager(context: Context) { val trackers = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi, simkl) + fun loggedInTrackers() = trackers.filter { it.isLoggedIn } + fun get(id: Long) = trackers.find { it.id == id } fun hasLoggedIn() = trackers.any { it.isLoggedIn } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index fc5675dfd..7e7a165ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -19,7 +19,15 @@ import uy.kohesive.injekt.injectLazy import tachiyomi.domain.track.anime.model.AnimeTrack as DomainAnimeTrack import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack -class Anilist(id: Long) : BaseTracker(id, "AniList"), MangaTracker, AnimeTracker, DeletableMangaTracker, DeletableAnimeTracker { +class Anilist(id: Long) : + BaseTracker( + id, + "AniList", + ), + MangaTracker, + AnimeTracker, + DeletableMangaTracker, + DeletableAnimeTracker { companion object { const val READING = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt index c88fd24e4..77e3503bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/KavitaApi.kt @@ -48,11 +48,19 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor when (it.code) { 200 -> return it.parseAs().token 401 -> { - logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } + logcat(LogPriority.WARN) { + "Unauthorized / api key not valid: Cleaned api URL: " + + "$apiUrl, Api key is empty: ${apiKey.isEmpty()}" + } throw IOException("Unauthorized / api key not valid") } 500 -> { - logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } + logcat( + LogPriority.WARN, + ) { + "Error fetching JWT token. Cleaned api URL: " + + "$apiUrl, Api key is empty: ${apiKey.isEmpty()}" + } throw IOException("Error fetching JWT token") } else -> {} @@ -62,7 +70,8 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor // Not sure which one to catch } catch (e: SocketTimeoutException) { logcat(LogPriority.WARN) { - "Could not fetch JWT token. Probably due to connectivity issue or the url '$apiUrl' is not available, skipping" + "Could not fetch JWT token. Probably due to connectivity " + + "issue or the url '$apiUrl' is not available, skipping" } return null } catch (e: Exception) { @@ -129,7 +138,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor } } } catch (e: Exception) { - logcat(LogPriority.WARN, e) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" } + logcat( + LogPriority.WARN, + e, + ) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" } throw e } return 0F @@ -164,7 +176,9 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor } suspend fun updateProgress(track: MangaTrack): MangaTrack { - val requestUrl = "${getApiFromUrl(track.tracking_url)}/Tachiyomi/mark-chapter-until-as-read?seriesId=${getIdFromUrl( + val requestUrl = "${getApiFromUrl( + track.tracking_url, + )}/Tachiyomi/mark-chapter-until-as-read?seriesId=${getIdFromUrl( track.tracking_url, )}&chapterNumber=${track.last_chapter_read}" authClient.newCall( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index f3706bf24..5c49d4569 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -17,7 +17,15 @@ import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat -class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), AnimeTracker, MangaTracker, DeletableMangaTracker, DeletableAnimeTracker { +class Kitsu(id: Long) : + BaseTracker( + id, + "Kitsu", + ), + AnimeTracker, + MangaTracker, + DeletableMangaTracker, + DeletableAnimeTracker { companion object { const val READING = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 714be1f64..272f86d25 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -483,9 +483,13 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) "https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/" private const val algoliaAppId = "AWQO5J657S" private const val algoliaFilter = - "&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22chapterCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" + "&facetFilters=%5B%22kind%3Amanga%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C" + + "%22chapterCount%22%2C%22posterImage%22%2C%22" + + "startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" private const val algoliaFilterAnime = - "&facetFilters=%5B%22kind%3Aanime%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C%22episodeCount%22%2C%22posterImage%22%2C%22startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" + "&facetFilters=%5B%22kind%3Aanime%22%5D&attributesToRetrieve=%5B%22synopsis%22%2C%22canonicalTitle%22%2C" + + "%22episodeCount%22%2C%22posterImage%22%2C%22" + + "startDate%22%2C%22subtype%22%2C%22endDate%22%2C%20%22id%22%5D" fun mangaUrl(remoteId: Long): String { return baseMangaUrl + remoteId diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 4f59e9326..45d16b76a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -17,7 +17,15 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), MangaTracker, AnimeTracker, DeletableMangaTracker, DeletableAnimeTracker { +class MyAnimeList(id: Long) : + BaseTracker( + id, + "MyAnimeList", + ), + MangaTracker, + AnimeTracker, + DeletableMangaTracker, + DeletableAnimeTracker { companion object { const val READING = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index 49cae3751..073fb00ad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -17,7 +17,15 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), MangaTracker, AnimeTracker, DeletableMangaTracker, DeletableAnimeTracker { +class Shikimori(id: Long) : + BaseTracker( + id, + "Shikimori", + ), + MangaTracker, + AnimeTracker, + DeletableMangaTracker, + DeletableAnimeTracker { companion object { const val READING = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index e8c68f5c4..7b64643d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -61,7 +61,8 @@ class ShikimoriApi( ).awaitSuccess() .parseAs() .let { - track.library_id = it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request + track.library_id = + it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request } track } @@ -105,7 +106,8 @@ class ShikimoriApi( ).awaitSuccess() .parseAs() .let { - track.library_id = it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request + track.library_id = + it["id"]!!.jsonPrimitive.long // save id of the entry for possible future delete request } track } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/SimklApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/SimklApi.kt index 2ec9e22c2..26c52d55e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/SimklApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/SimklApi.kt @@ -172,7 +172,9 @@ class SimklApi(private val client: OkHttpClient, interceptor: SimklInterceptor) title = obj["title_romaji"]?.jsonPrimitive?.content ?: obj["title"]!!.jsonPrimitive.content total_episodes = obj["ep_count"]?.jsonPrimitive?.intOrNull ?: 1 cover_url = "https://simkl.in/posters/" + obj["poster"]!!.jsonPrimitive.content + "_m.webp" - summary = obj["all_titles"]?.jsonArray?.joinToString("\n", "All titles:\n") { it.jsonPrimitive.content } ?: "" + summary = obj["all_titles"]?.jsonArray + ?.joinToString("\n", "All titles:\n") { it.jsonPrimitive.content } ?: "" + tracking_url = obj["url"]!!.jsonPrimitive.content publishing_status = obj["status"]?.jsonPrimitive?.content ?: "ended" publishing_type = obj["type"]?.jsonPrimitive?.content ?: type 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 3acf2a747..6052044f3 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,6 +1,7 @@ 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 @@ -12,6 +13,11 @@ 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 + return withIOContext { val result = getApplicationRelease.await( GetApplicationRelease.Arguments( @@ -28,7 +34,9 @@ class AppUpdateChecker { is GetApplicationRelease.Result.NewUpdate -> AppUpdateNotifier(context).promptUpdate( result.release, ) - is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier(context).promptFdroidUpdate() + is GetApplicationRelease.Result.ThirdPartyInstallation -> AppUpdateNotifier( + context, + ).promptFdroidUpdate() else -> {} } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt index e92061eb5..89fcbd9a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/api/AnimeExtensionGithubApi.kt @@ -72,7 +72,10 @@ internal class AnimeExtensionGithubApi { } } - suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List? { + suspend fun checkForUpdates( + context: Context, + fromAvailableExtensionList: Boolean = false, + ): List? { // Limit checks to once a day at most if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { return null diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt index cae0b3a60..d3d179f9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/installer/PackageInstallerInstallerAnime.kt @@ -105,7 +105,12 @@ class PackageInstallerInstallerAnime(private val service: Service) : InstallerAn } init { - ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_EXPORTED) + ContextCompat.registerReceiver( + service, + packageActionReceiver, + IntentFilter(INSTALL_ACTION), + ContextCompat.RECEIVER_EXPORTED, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt index 1988bd482..f2aa94372 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt @@ -138,7 +138,9 @@ internal class AnimeExtensionInstaller(private val context: Context) { emit(downloadStatus) // Stop polling when the download fails or finishes - if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) { + if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || + downloadStatus == DownloadManager.STATUS_FAILED + ) { return@flow } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt index 97a839f5e..c4ad6f138 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/api/MangaExtensionGithubApi.kt @@ -72,7 +72,10 @@ internal class MangaExtensionGithubApi { } } - suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List? { + suspend fun checkForUpdates( + context: Context, + fromAvailableExtensionList: Boolean = false, + ): List? { // Limit checks to once a day at most if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { return null diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt index 795fef519..8b981e8f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/installer/PackageInstallerInstallerManga.kt @@ -105,7 +105,12 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa } init { - ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_EXPORTED) + ContextCompat.registerReceiver( + service, + packageActionReceiver, + IntentFilter(INSTALL_ACTION), + ContextCompat.RECEIVER_EXPORTED, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt index 27a7105e7..cb1a5a20e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt @@ -138,7 +138,9 @@ internal class MangaExtensionInstaller(private val context: Context) { emit(downloadStatus) // Stop polling when the download fails or finishes - if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) { + if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || + downloadStatus == DownloadManager.STATUS_FAILED + ) { return@flow } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt index ba4d0f92a..94478377a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt @@ -37,7 +37,9 @@ class AndroidAnimeSourceManager( private val stubSourcesMap = ConcurrentHashMap() - override val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } + override val catalogueSources: Flow> = sourcesMapFlow.map { + it.values.filterIsInstance() + } init { scope.launch { diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt index 58822c651..5afe14548 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/AndroidMangaSourceManager.kt @@ -37,7 +37,9 @@ class AndroidMangaSourceManager( private val stubSourcesMap = ConcurrentHashMap() - override val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } + override val catalogueSources: Flow> = sourcesMapFlow.map { + it.values.filterIsInstance() + } init { scope.launch { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt index f28ad5873..4adca4bcc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt @@ -232,7 +232,7 @@ class BrowseAnimeSourceScreenModel( new = new.removeCovers(coverCache) } else { setAnimeDefaultEpisodeFlags.await(anime) - addTracks.bindEnhancedTracks(anime, source) + addTracks.bindEnhancedTrackers(anime, source) } updateAnime.await(new.toAnimeUpdate()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt index 4aadba7c0..63fbb0fe6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt @@ -233,7 +233,7 @@ class BrowseMangaSourceScreenModel( new = new.removeCovers(coverCache) } else { setMangaDefaultChapterFlags.await(manga) - addTracks.bindEnhancedTracks(manga, source) + addTracks.bindEnhancedTrackers(manga, source) } updateManga.await(new.toMangaUpdate()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt index b2dacd35a..5e27a9497 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt @@ -132,7 +132,13 @@ class AnimeScreen( screenModel.source, ) }.takeIf { isAnimeHttpSource }, - onWebViewLongClicked = { copyAnimeUrl(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource }, + onWebViewLongClicked = { + copyAnimeUrl( + context, + screenModel.anime, + screenModel.source, + ) + }.takeIf { isAnimeHttpSource }, onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable }, onTagSearch = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onFilterButtonClicked = screenModel::showSettingsDialog, @@ -145,11 +151,21 @@ class AnimeScreen( }, onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } }, onCoverClicked = screenModel::showCoverDialog, - onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource }, + onShareClicked = { + shareAnime( + context, + screenModel.anime, + screenModel.source, + ) + }.takeIf { isAnimeHttpSource }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.anime.favorite }, - onEditFetchIntervalClicked = screenModel::showSetAnimeFetchIntervalDialog.takeIf { screenModel.isUpdateIntervalEnabled && successState.anime.favorite }, - onMigrateClicked = { navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) }.takeIf { successState.anime.favorite }, + onEditFetchIntervalClicked = screenModel::showSetAnimeFetchIntervalDialog.takeIf { + screenModel.isUpdateIntervalEnabled && successState.anime.favorite + }, + onMigrateClicked = { + navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) + }.takeIf { successState.anime.favorite }, changeAnimeSkipIntro = screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite }, onMultiBookmarkClicked = screenModel::bookmarkEpisodes, onMultiMarkAsSeenClicked = screenModel::markEpisodesSeen, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index ee7388f9f..6a1289fcc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -141,7 +141,8 @@ class AnimeScreenModel( val relativeTime by uiPreferences.relativeTime().asState(screenModelScope) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) - val isUpdateIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateItemRestrictions().get() + val isUpdateIntervalEnabled = + LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateItemRestrictions().get() private val selectedPositions: Array = arrayOf(-1, -1) // first and last selected index in list private val selectedEpisodeIds: HashSet = HashSet() @@ -338,7 +339,7 @@ class AnimeScreenModel( } // Finally match with enhanced tracking when available - addTracks.bindEnhancedTracks(anime, state.source) + addTracks.bindEnhancedTrackers(anime, state.source) if (autoOpenTrack) { showTrackDialog() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt index 652e3c3cb..086adaf94 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt @@ -102,8 +102,8 @@ class MangaCoverScreenModel( imageSaver.save( Image.Cover( bitmap = bitmap, - name = "cover", - location = if (temp) Location.Cache else Location.Pictures(manga.title), + name = manga.title, + location = if (temp) Location.Cache else Location.Pictures.create(), ), ) } 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 1ecd2f158..4ee3bbaac 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 @@ -120,7 +120,13 @@ class MangaScreen( screenModel.source, ) }.takeIf { isHttpSource }, - onWebViewLongClicked = { copyMangaUrl(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource }, + onWebViewLongClicked = { + copyMangaUrl( + context, + screenModel.manga, + screenModel.source, + ) + }.takeIf { isHttpSource }, onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable }, onTagSearch = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onFilterButtonClicked = screenModel::showSettingsDialog, @@ -131,8 +137,12 @@ class MangaScreen( onShareClicked = { shareManga(context, screenModel.manga, screenModel.source) }.takeIf { isHttpSource }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onEditCategoryClicked = screenModel::showChangeCategoryDialog.takeIf { successState.manga.favorite }, - onEditFetchIntervalClicked = screenModel::showSetMangaFetchIntervalDialog.takeIf { screenModel.isUpdateIntervalEnabled && successState.manga.favorite }, - onMigrateClicked = { navigator.push(MigrateMangaSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite }, + onEditFetchIntervalClicked = screenModel::showSetMangaFetchIntervalDialog.takeIf { + screenModel.isUpdateIntervalEnabled && successState.manga.favorite + }, + onMigrateClicked = { + navigator.push(MigrateMangaSearchScreen(successState.manga.id)) + }.takeIf { successState.manga.favorite }, onMultiBookmarkClicked = screenModel::bookmarkChapters, onMultiMarkAsReadClicked = screenModel::markChaptersRead, onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead, 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 6f85c4ce4..c65d1c5f4 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 @@ -136,7 +136,8 @@ class MangaScreenModel( val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val skipFiltered by readerPreferences.skipFiltered().asState(screenModelScope) - val isUpdateIntervalEnabled = LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateItemRestrictions().get() + val isUpdateIntervalEnabled = + LibraryPreferences.ENTRY_OUTSIDE_RELEASE_PERIOD in libraryPreferences.autoUpdateItemRestrictions().get() private val selectedPositions: Array = arrayOf(-1, -1) // first and last selected index in list private val selectedChapterIds: HashSet = HashSet() @@ -334,7 +335,7 @@ class MangaScreenModel( } // Finally match with enhanced tracking when available - addTracks.bindEnhancedTracks(manga, state.source) + addTracks.bindEnhancedTrackers(manga, state.source) if (autoOpenTrack) { showTrackDialog() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt index 9a822f61a..a1423d332 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt @@ -25,7 +25,6 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager -import eu.kanade.tachiyomi.data.track.AnimeTracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.util.episode.getNextUnseen import eu.kanade.tachiyomi.util.removeCovers @@ -42,6 +41,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.TriState +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withIOContext @@ -62,12 +62,11 @@ import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime +import tachiyomi.domain.track.anime.model.AnimeTrack import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.Collator import java.util.Collections -import java.util.Locale /** * Typealias for the library anime, using the category as keys, and list of anime as values. @@ -107,7 +106,7 @@ class AnimeLibraryScreenModel( ) { searchQuery, library, tracks, loggedInTrackers, _ -> library .applyFilters(tracks, loggedInTrackers) - .applySort() + .applySort(tracks) .mapValues { (_, value) -> if (searchQuery != null) { // Filter query @@ -171,7 +170,7 @@ class AnimeLibraryScreenModel( * Applies library filters to the given map of anime. */ private suspend fun AnimeLibraryMap.applyFilters( - trackMap: Map>, + trackMap: Map>, loggedInTrackers: Map, ): AnimeLibraryMap { val prefs = getAnimelibItemPreferencesFlow().first() @@ -216,7 +215,9 @@ class AnimeLibraryScreenModel( val filterFnTracking: (AnimeLibraryItem) -> Boolean = tracking@{ item -> if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true - val animeTracks = trackMap[item.libraryAnime.id].orEmpty() + val animeTracks = trackMap + .mapValues { entry -> entry.value.map { it.syncId } }[item.libraryAnime.id] + .orEmpty() val isExcluded = excludedTracks.isNotEmpty() && animeTracks.fastAny { it in excludedTracks } val isIncluded = includedTracks.isEmpty() || animeTracks.fastAny { it in includedTracks } @@ -239,16 +240,26 @@ class AnimeLibraryScreenModel( /** * Applies library sorting to the given map of anime. */ - private fun AnimeLibraryMap.applySort(): AnimeLibraryMap { - val locale = Locale.getDefault() - val collator = Collator.getInstance(locale).apply { - strength = Collator.PRIMARY - } + private fun AnimeLibraryMap.applySort( + // Map> + trackMap: Map>, + ): AnimeLibraryMap { val sortAlphabetically: (AnimeLibraryItem, AnimeLibraryItem) -> Int = { i1, i2 -> - collator.compare( - i1.libraryAnime.anime.title.lowercase(locale), - i2.libraryAnime.anime.title.lowercase(locale), - ) + i1.libraryAnime.anime.title.lowercase().compareToWithCollator(i2.libraryAnime.anime.title.lowercase()) + } + + val defaultTrackerScoreSortValue = -1.0 + val trackerScores by lazy { + val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id } + trackMap.mapValues { entry -> + when { + entry.value.isEmpty() -> null + else -> + entry.value + .mapNotNull { trackerMap[it.syncId]?.animeService?.get10PointScore(it) } + .average() + } + } } val sortFn: (AnimeLibraryItem, AnimeLibraryItem) -> Int = { i1, i2 -> @@ -282,12 +293,18 @@ class AnimeLibraryScreenModel( AnimeLibrarySort.Type.DateAdded -> { i1.libraryAnime.anime.dateAdded.compareTo(i2.libraryAnime.anime.dateAdded) } + AnimeLibrarySort.Type.TrackerMean -> { + val item1Score = trackerScores[i1.libraryAnime.id] ?: defaultTrackerScoreSortValue + val item2Score = trackerScores[i2.libraryAnime.id] ?: defaultTrackerScoreSortValue + item1Score.compareTo(item2Score) + } AnimeLibrarySort.Type.AiringTime -> when { i1.libraryAnime.anime.nextEpisodeAiringAt == 0L -> if (sort.isAscending) 1 else -1 i2.libraryAnime.anime.nextEpisodeAiringAt == 0L -> if (sort.isAscending) -1 else 1 - i1.libraryAnime.unseenCount == i2.libraryAnime.unseenCount -> i1.libraryAnime.anime.nextEpisodeAiringAt.compareTo( - i2.libraryAnime.anime.nextEpisodeAiringAt, - ) + i1.libraryAnime.unseenCount == i2.libraryAnime.unseenCount -> + i1.libraryAnime.anime.nextEpisodeAiringAt.compareTo( + i2.libraryAnime.anime.nextEpisodeAiringAt, + ) else -> i1.libraryAnime.unseenCount.compareTo(i2.libraryAnime.unseenCount) } } @@ -380,7 +397,7 @@ class AnimeLibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedInTrackers = trackerManager.trackers.filter { it.isLoggedIn && it is AnimeTracker } + val loggedInTrackers = trackerManager.loggedInTrackers() return if (loggedInTrackers.isNotEmpty()) { val prefFlows = loggedInTrackers .map { libraryPreferences.filterTrackedAnime(it.id.toInt()).changes() } @@ -541,7 +558,13 @@ class AnimeLibraryScreenModel( } fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState { - return (if (isLandscape) libraryPreferences.animeLandscapeColumns() else libraryPreferences.animePortraitColumns()).asState( + return ( + if (isLandscape) { + libraryPreferences.animeLandscapeColumns() + } else { + libraryPreferences.animePortraitColumns() + } + ).asState( screenModelScope, ) } 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 423abade9..a904f008f 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 @@ -23,7 +23,6 @@ import eu.kanade.presentation.library.LibraryToolbarTitle import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager -import eu.kanade.tachiyomi.data.track.MangaTracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource @@ -42,6 +41,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.TriState +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withIOContext @@ -62,12 +62,11 @@ import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.domain.track.manga.interactor.GetTracksPerManga +import tachiyomi.domain.track.manga.model.MangaTrack import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.text.Collator import java.util.Collections -import java.util.Locale /** * Typealias for the library manga, using the category as keys, and list of manga as values. @@ -107,7 +106,7 @@ class MangaLibraryScreenModel( ) { searchQuery, library, tracks, loggedInTrackers, _ -> library .applyFilters(tracks, loggedInTrackers) - .applySort() + .applySort(tracks) .mapValues { (_, value) -> if (searchQuery != null) { // Filter query @@ -171,7 +170,7 @@ class MangaLibraryScreenModel( * Applies library filters to the given map of manga. */ private suspend fun MangaLibraryMap.applyFilters( - trackMap: Map>, + trackMap: Map>, loggedInTrackers: Map, ): MangaLibraryMap { val prefs = getLibraryItemPreferencesFlow().first() @@ -216,7 +215,9 @@ class MangaLibraryScreenModel( val filterFnTracking: (MangaLibraryItem) -> Boolean = tracking@{ item -> if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true - val mangaTracks = trackMap[item.libraryManga.id].orEmpty() + val mangaTracks = trackMap + .mapValues { entry -> entry.value.map { it.syncId } }[item.libraryManga.id] + .orEmpty() val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks } val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks } @@ -239,16 +240,26 @@ class MangaLibraryScreenModel( /** * Applies library sorting to the given map of manga. */ - private fun MangaLibraryMap.applySort(): MangaLibraryMap { - val locale = Locale.getDefault() - val collator = Collator.getInstance(locale).apply { - strength = Collator.PRIMARY - } + private fun MangaLibraryMap.applySort( + // Map> + trackMap: Map>, + ): MangaLibraryMap { val sortAlphabetically: (MangaLibraryItem, MangaLibraryItem) -> Int = { i1, i2 -> - collator.compare( - i1.libraryManga.manga.title.lowercase(locale), - i2.libraryManga.manga.title.lowercase(locale), - ) + i1.libraryManga.manga.title.lowercase().compareToWithCollator(i2.libraryManga.manga.title.lowercase()) + } + + val defaultTrackerScoreSortValue = -1.0 + val trackerScores by lazy { + val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id } + trackMap.mapValues { entry -> + when { + entry.value.isEmpty() -> null + else -> + entry.value + .mapNotNull { trackerMap[it.syncId]?.mangaService?.get10PointScore(it) } + .average() + } + } } val sortFn: (MangaLibraryItem, MangaLibraryItem) -> Int = { i1, i2 -> @@ -282,6 +293,11 @@ class MangaLibraryScreenModel( MangaLibrarySort.Type.DateAdded -> { i1.libraryManga.manga.dateAdded.compareTo(i2.libraryManga.manga.dateAdded) } + MangaLibrarySort.Type.TrackerMean -> { + val item1Score = trackerScores[i1.libraryManga.id] ?: defaultTrackerScoreSortValue + val item2Score = trackerScores[i2.libraryManga.id] ?: defaultTrackerScoreSortValue + item1Score.compareTo(item2Score) + } } } @@ -372,7 +388,7 @@ class MangaLibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedInTrackers = trackerManager.trackers.filter { it.isLoggedIn && it is MangaTracker } + val loggedInTrackers = trackerManager.loggedInTrackers() return if (loggedInTrackers.isNotEmpty()) { val prefFlows = loggedInTrackers .map { libraryPreferences.filterTrackedManga(it.id.toInt()).changes() } @@ -533,7 +549,13 @@ class MangaLibraryScreenModel( } fun getColumnsPreferenceForCurrentOrientation(isLandscape: Boolean): PreferenceMutableState { - return (if (isLandscape) libraryPreferences.mangaLandscapeColumns() else libraryPreferences.mangaPortraitColumns()).asState( + return ( + if (isLandscape) { + libraryPreferences.mangaLandscapeColumns() + } else { + libraryPreferences.mangaPortraitColumns() + } + ).asState( screenModelScope, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 6f1f1ef28..882cbb6ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -263,8 +263,14 @@ class MainActivity : BaseActivity() { .filter { !it } .onEach { val currentScreen = navigator.lastItem - if ((currentScreen is BrowseMangaSourceScreen || (currentScreen is MangaScreen && currentScreen.fromSource)) || - (currentScreen is BrowseAnimeSourceScreen || (currentScreen is AnimeScreen && currentScreen.fromSource)) + if (( + currentScreen is BrowseMangaSourceScreen || + (currentScreen is MangaScreen && currentScreen.fromSource) + ) || + ( + currentScreen is BrowseAnimeSourceScreen || + (currentScreen is AnimeScreen && currentScreen.fromSource) + ) ) { navigator.popUntilRoot() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt index 64ab6291f..332ce2218 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt @@ -432,7 +432,11 @@ class PlayerActivity : BaseActivity() { } fun onSubtitleSelected(index: Int) { - if (streams.subtitle.index == index || streams.subtitle.index > subtitleTracks.lastIndex) return + if (streams.subtitle.index == index || + streams.subtitle.index > subtitleTracks.lastIndex + ) { + return + } streams.subtitle.index = index if (index == 0) { player.sid = -1 @@ -631,7 +635,10 @@ class PlayerActivity : BaseActivity() { } private fun setupPlayerBrightness() { - val useDeviceBrightness = playerPreferences.playerBrightnessValue().get() == -1.0F || !playerPreferences.rememberPlayerBrightness().get() + val useDeviceBrightness = + playerPreferences.playerBrightnessValue().get() == -1.0F || + !playerPreferences.rememberPlayerBrightness().get() + brightness = if (useDeviceBrightness) { Utils.getScreenBrightness(this) ?: 0.5F } else { @@ -1621,10 +1628,14 @@ class PlayerActivity : BaseActivity() { viewModel.viewModelScope.launchUI { if (playerPreferences.adjustOrientationVideoDimensions().get()) { if ((player.videoW ?: 1) / (player.videoH ?: 1) >= 1) { - this@PlayerActivity.requestedOrientation = playerPreferences.defaultPlayerOrientationLandscape().get() + this@PlayerActivity.requestedOrientation = + playerPreferences.defaultPlayerOrientationLandscape().get() + switchControlsOrientation(true) } else { - this@PlayerActivity.requestedOrientation = playerPreferences.defaultPlayerOrientationPortrait().get() + this@PlayerActivity.requestedOrientation = + playerPreferences.defaultPlayerOrientationPortrait().get() + switchControlsOrientation(false) } } @@ -1731,7 +1742,11 @@ class PlayerActivity : BaseActivity() { val autoSkipAniSkip = playerPreferences.autoSkipAniSkip().get() skipType = - aniSkipInterval?.firstOrNull { it.interval.startTime <= position && it.interval.endTime > position }?.skipType + aniSkipInterval + ?.firstOrNull { + it.interval.startTime <= position && + it.interval.endTime > position + }?.skipType skipType?.let { skipType -> val aniSkipPlayerUtils = AniSkipApi.PlayerUtils(binding, aniSkipInterval!!) if (netflixStyle) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerObserver.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerObserver.kt index b732be050..2fe10b58f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerObserver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerObserver.kt @@ -30,14 +30,16 @@ class PlayerObserver(val activity: PlayerActivity) : override fun event(eventId: Int) { when (eventId) { - MPVLib.mpvEventId.MPV_EVENT_FILE_LOADED -> activity.viewModel.viewModelScope.launchIO { activity.fileLoaded() } - MPVLib.mpvEventId.MPV_EVENT_START_FILE -> activity.viewModel.viewModelScope.launchUI { - activity.player.paused = false - activity.refreshUi() - // Fixes a minor Ui bug but I have no idea why - val isEpisodeOnline = withIOContext { activity.viewModel.isEpisodeOnline() != true } - if (isEpisodeOnline) activity.showLoadingIndicator(false) - } + MPVLib.mpvEventId.MPV_EVENT_FILE_LOADED -> + activity.viewModel.viewModelScope.launchIO { activity.fileLoaded() } + MPVLib.mpvEventId.MPV_EVENT_START_FILE -> + activity.viewModel.viewModelScope.launchUI { + activity.player.paused = false + activity.refreshUi() + // Fixes a minor Ui bug but I have no idea why + val isEpisodeOnline = withIOContext { activity.viewModel.isEpisodeOnline() != true } + if (isEpisodeOnline) activity.showLoadingIndicator(false) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/StreamsCatalogSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/StreamsCatalogSheet.kt index c03f29abd..6b68906c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/StreamsCatalogSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/StreamsCatalogSheet.kt @@ -153,7 +153,13 @@ private fun StreamsPageBuilder( } } - val addTrackRes = if (externalTrackCode == "sub") R.string.player_add_external_subtitles else R.string.player_add_external_audio + val addTrackRes = + if (externalTrackCode == "sub") { + R.string.player_add_external_subtitles + } else { + R.string.player_add_external_audio + } + Row( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt index d0b87d4c4..b80578a2b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt @@ -63,7 +63,13 @@ fun VideoChaptersSheet( val nextChapterTime = videoChapters.getOrNull(index + 1)?.time?.toInt() val selected = (index == videoChapters.lastIndex && currentTimePosition >= videoChapterTime) || - (currentTimePosition >= videoChapterTime && (nextChapterTime == null || currentTimePosition < nextChapterTime)) + ( + currentTimePosition >= videoChapterTime && + ( + nextChapterTime == null || + currentTimePosition < nextChapterTime + ) + ) val onClick = { currentTimePosition = videoChapter.time.roundToInt() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/GestureHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/GestureHandler.kt index a1cd57e47..4eff335e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/GestureHandler.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/GestureHandler.kt @@ -29,7 +29,14 @@ class GestureHandler( private val playerControls = activity.playerControls override fun onSingleTapUp(e: MotionEvent): Boolean { - if (SeekState.mode == SeekState.LOCKED || SeekState.mode != SeekState.DOUBLE_TAP || activity.player.timePos == null || activity.player.duration == null) return false + if (SeekState.mode == SeekState.LOCKED || + SeekState.mode != SeekState.DOUBLE_TAP || + activity.player.timePos == null || + activity.player.duration == null + ) { + return false + } + when { e.x < width * 0.4F && interval != 0 -> if (activity.player.timePos!! > 0) { activity.doubleTapSeek( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt index f59718abe..9b5739de0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt @@ -239,7 +239,12 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr animationHandler.removeCallbacks(fadeOutControlsRunnable) animationHandler.removeCallbacks(hideUiForSeekRunnable) - if (!(binding.topControlsGroup.visibility == View.INVISIBLE && binding.middleControlsGroup.visibility == INVISIBLE && binding.bottomControlsGroup.visibility == INVISIBLE)) { + if (!( + binding.topControlsGroup.visibility == View.INVISIBLE && + binding.middleControlsGroup.visibility == INVISIBLE && + binding.bottomControlsGroup.visibility == INVISIBLE + ) + ) { wasPausedBeforeSeeking = player.paused!! showControls = binding.unlockedView.isVisible binding.topControlsGroup.visibility = View.INVISIBLE 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 edb00ce3b..aa080ec60 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 @@ -477,7 +477,8 @@ class ReaderActivity : BaseActivity() { } else { if (readerPreferences.fullscreen().get()) { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) - windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + windowInsetsController.systemBarsBehavior = + WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE } } } 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 b90d8c23f..b50bddcb4 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 @@ -161,20 +161,22 @@ class ReaderViewModel @JvmOverloads constructor( (manga.unreadFilterRaw == Manga.CHAPTER_SHOW_READ && !it.read) || (manga.unreadFilterRaw == Manga.CHAPTER_SHOW_UNREAD && it.read) || ( - manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded( - it.name, - it.scanlator, - manga.title, - manga.source, - ) + manga.downloadedFilterRaw == + Manga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded( + it.name, + it.scanlator, + manga.title, + manga.source, + ) ) || ( - manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded( - it.name, - it.scanlator, - manga.title, - manga.source, - ) + manga.downloadedFilterRaw == + Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded( + it.name, + it.scanlator, + manga.title, + manga.source, + ) ) || (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) || (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_NOT_BOOKMARKED && it.bookmark) @@ -783,14 +785,23 @@ class ReaderViewModel @JvmOverloads constructor( val filename = generateFilename(manga, page) - // Copy file in background + // Pictures directory. + val relativePath = if (readerPreferences.folderPerManga().get()) { + DiskUtil.buildValidFilename( + manga.title, + ) + } else { + "" + } + + // Copy file in background. viewModelScope.launchNonCancellable { try { val uri = imageSaver.save( image = Image.Page( inputStream = page.stream!!, name = filename, - location = Location.Pictures(DiskUtil.buildValidFilename(manga.title)), + location = Location.Pictures.create(relativePath), ), ) withUIContext { 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 138ea1966..c3775caf7 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 @@ -71,10 +71,9 @@ class ReaderPreferences( fun webtoonSidePadding() = preferenceStore.getInt("webtoon_side_padding", WEBTOON_PADDING_MIN) - fun readerHideThreshold() = preferenceStore.getEnum( - "reader_hide_threshold", - ReaderHideThreshold.LOW, - ) + fun readerHideThreshold() = preferenceStore.getEnum("reader_hide_threshold", ReaderHideThreshold.LOW) + + fun folderPerManga() = preferenceStore.getBoolean("create_folder_per_manga", false) fun skipRead() = preferenceStore.getBoolean("skip_read", false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index 5feae6529..f39180441 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -110,7 +110,12 @@ open class ReaderPageImageView @JvmOverloads constructor( } private fun SubsamplingScaleImageView.landscapeZoom(forward: Boolean) { - if (config != null && config!!.landscapeZoom && config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE && sWidth > sHeight && scale == minScale) { + if (config != null && + config!!.landscapeZoom && + config!!.minimumScaleType == SCALE_TYPE_CENTER_INSIDE && + sWidth > sHeight && + scale == minScale + ) { handler?.postDelayed(500) { val point = when (config!!.zoomStartPosition) { ZoomStartPosition.LEFT -> if (forward) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt b/app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt index ba30a4fd4..e6b62e908 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/AniSkipApi.kt @@ -29,7 +29,8 @@ class AniSkipApi { // credits: https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/others/AniSkip.kt fun getResult(malId: Int, episodeNumber: Int, episodeLength: Long): List? { val url = - "https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=$episodeLength" + "https://api.aniskip.com/v2/skip-times/$malId/$episodeNumber?types[]=ed" + + "&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=$episodeLength" return try { val a = client.newCall(GET(url)).execute().body.string() val res = json.decodeFromString(a) diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/PkceUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/PkceUtil.kt index e8d165f57..9699a7e9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/PkceUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/PkceUtil.kt @@ -1,15 +1,15 @@ package eu.kanade.tachiyomi.util -import android.util.Base64 import java.security.SecureRandom +import java.util.Base64 object PkceUtil { - private const val PKCE_BASE64_ENCODE_SETTINGS = Base64.NO_WRAP or Base64.NO_PADDING or Base64.URL_SAFE - fun generateCodeVerifier(): String { val codeVerifier = ByteArray(50) SecureRandom().nextBytes(codeVerifier) - return Base64.encodeToString(codeVerifier, PKCE_BASE64_ENCODE_SETTINGS) + return Base64.getUrlEncoder() + .withoutPadding() + .encodeToString(codeVerifier) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt index e2f683685..797890ef5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt @@ -56,7 +56,8 @@ fun Date.toRelativeString( return dateFormat.format(this) } val now = Date() - val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) + val difference = now.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) - + this.timeWithOffset.floorNearest(MILLISECONDS_IN_DAY) val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt() return when { difference < 0 -> dateFormat.format(this) diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index 6790bab34..356a99366 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -99,4 +99,15 @@ private fun isRequestHeaderSafe(_name: String, _value: String): Boolean { if (name == "connection" && value == "upgrade") return false return true } -private val unsafeHeaderNames = listOf("content-length", "host", "trailer", "te", "upgrade", "cookie2", "keep-alive", "transfer-encoding", "set-cookie") +private val unsafeHeaderNames = + listOf( + "content-length", + "host", + "trailer", + "te", + "upgrade", + "cookie2", + "keep-alive", + "transfer-encoding", + "set-cookie", + ) diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt index b218c3817..d83200302 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt @@ -35,9 +35,17 @@ fun File.copyAndSetReadOnlyTo(target: File, overwrite: Boolean = false, bufferSi if (target.exists()) { if (!overwrite) { - throw FileAlreadyExistsException(file = this, other = target, reason = "The destination file already exists.") + throw FileAlreadyExistsException( + file = this, + other = target, + reason = "The destination file already exists.", + ) } else if (!target.delete()) { - throw FileAlreadyExistsException(file = this, other = target, reason = "Tried to overwrite the destination, but failed to delete it.") + throw FileAlreadyExistsException( + file = this, + other = target, + reason = "Tried to overwrite the destination, but failed to delete it.", + ) } } diff --git a/core/src/main/java/tachiyomi/core/util/lang/SortUtil.kt b/core/src/main/java/tachiyomi/core/util/lang/SortUtil.kt new file mode 100644 index 000000000..03c15d43b --- /dev/null +++ b/core/src/main/java/tachiyomi/core/util/lang/SortUtil.kt @@ -0,0 +1,15 @@ +package tachiyomi.core.util.lang + +import java.text.Collator +import java.util.Locale + +private val collator by lazy { + val locale = Locale.getDefault() + Collator.getInstance(locale).apply { + strength = Collator.PRIMARY + } +} + +fun String.compareToWithCollator(other: String): Int { + return collator.compare(this, other) +} diff --git a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt index 2863c7999..fabfa23e4 100644 --- a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt +++ b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt @@ -366,8 +366,26 @@ object ImageUtil { val botLeftIsDark = botLeftPixel.isDark() val botRightIsDark = botRightPixel.isDark() - var darkBG = (topLeftIsDark && (botLeftIsDark || botRightIsDark || topRightIsDark || midLeftIsDark || topMidIsDark)) || - (topRightIsDark && (botRightIsDark || botLeftIsDark || midRightIsDark || topMidIsDark)) + var darkBG = + ( + topLeftIsDark && + ( + botLeftIsDark || + botRightIsDark || + topRightIsDark || + midLeftIsDark || + topMidIsDark + ) + ) || + ( + topRightIsDark && + ( + botRightIsDark || + botLeftIsDark || + midRightIsDark || + topMidIsDark + ) + ) val topAndBotPixels = listOf( topLeftPixel, @@ -521,10 +539,26 @@ object ImageUtil { darkBG -> { return ColorDrawable(blackColor) } - topIsBlackStreak || (topCornersIsDark && topOffsetCornersIsDark && (topMidIsDark || overallBlackPixels > 9)) -> { + topIsBlackStreak || + ( + topCornersIsDark && + topOffsetCornersIsDark && + ( + topMidIsDark || + overallBlackPixels > 9 + ) + ) -> { intArrayOf(blackColor, blackColor, whiteColor, whiteColor) } - bottomIsBlackStreak || (botCornersIsDark && botOffsetCornersIsDark && (bottomCenterPixel.isDark() || overallBlackPixels > 9)) -> { + bottomIsBlackStreak || + ( + botCornersIsDark && + botOffsetCornersIsDark && + ( + bottomCenterPixel.isDark() || + overallBlackPixels > 9 + ) + ) -> { intArrayOf(whiteColor, whiteColor, blackColor, blackColor) } else -> { diff --git a/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt index 6ae08e274..a6ce01b15 100644 --- a/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/manga/MangaSourcePagingSource.kt @@ -9,9 +9,14 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.domain.items.chapter.model.NoChaptersException import tachiyomi.domain.source.manga.repository.SourcePagingSourceType -class SourceSearchPagingSource(source: CatalogueSource, val query: String, val filters: FilterList) : SourcePagingSource( - source, -) { +class SourceSearchPagingSource( + source: CatalogueSource, + val query: String, + val filters: FilterList, +) : + SourcePagingSource( + source, + ) { override suspend fun requestNextPage(currentPage: Int): MangasPage { return source.getSearchManga(currentPage, query, filters) } diff --git a/domain/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt b/domain/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt index deb87efbf..116126042 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/anime/model/Anime.kt @@ -100,6 +100,7 @@ data class Anime( const val EPISODE_SORTING_SOURCE = 0x00000000L const val EPISODE_SORTING_NUMBER = 0x00000100L const val EPISODE_SORTING_UPLOAD_DATE = 0x00000200L + const val EPISODE_SORTING_ALPHABET = 0x00000300L const val EPISODE_SORTING_MASK = 0x00000300L const val EPISODE_DISPLAY_NAME = 0x00000000L diff --git a/domain/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt b/domain/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt index 0ed0e4db2..90cf36310 100644 --- a/domain/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt +++ b/domain/src/main/java/tachiyomi/domain/entries/manga/model/Manga.kt @@ -85,6 +85,7 @@ data class Manga( const val CHAPTER_SORTING_SOURCE = 0x00000000L const val CHAPTER_SORTING_NUMBER = 0x00000100L const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200L + const val CHAPTER_SORTING_ALPHABET = 0x00000300L const val CHAPTER_SORTING_MASK = 0x00000300L const val CHAPTER_DISPLAY_NAME = 0x00000000L diff --git a/domain/src/main/java/tachiyomi/domain/items/chapter/model/ChapterUpdate.kt b/domain/src/main/java/tachiyomi/domain/items/chapter/model/ChapterUpdate.kt index 6300469ce..86db77af1 100644 --- a/domain/src/main/java/tachiyomi/domain/items/chapter/model/ChapterUpdate.kt +++ b/domain/src/main/java/tachiyomi/domain/items/chapter/model/ChapterUpdate.kt @@ -16,5 +16,18 @@ data class ChapterUpdate( ) fun Chapter.toChapterUpdate(): ChapterUpdate { - return ChapterUpdate(id, mangaId, read, bookmark, lastPageRead, dateFetch, sourceOrder, url, name, dateUpload, chapterNumber, scanlator) + return ChapterUpdate( + id, + mangaId, + read, + bookmark, + lastPageRead, + dateFetch, + sourceOrder, + url, + name, + dateUpload, + chapterNumber, + scanlator, + ) } diff --git a/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterSorter.kt b/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterSorter.kt index 10cbe81d9..f0d68061f 100644 --- a/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterSorter.kt +++ b/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterSorter.kt @@ -1,5 +1,6 @@ package tachiyomi.domain.items.chapter.service +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter @@ -20,6 +21,10 @@ fun getChapterSort(manga: Manga, sortDescending: Boolean = manga.sortDescending( true -> { c1, c2 -> c2.dateUpload.compareTo(c1.dateUpload) } false -> { c1, c2 -> c1.dateUpload.compareTo(c2.dateUpload) } } + Manga.CHAPTER_SORTING_ALPHABET -> when (sortDescending) { + true -> { c1, c2 -> c2.name.compareToWithCollator(c1.name) } + false -> { c1, c2 -> c1.name.compareToWithCollator(c2.name) } + } else -> throw NotImplementedError("Invalid chapter sorting method: ${manga.sorting}") } } diff --git a/domain/src/main/java/tachiyomi/domain/items/episode/model/EpisodeUpdate.kt b/domain/src/main/java/tachiyomi/domain/items/episode/model/EpisodeUpdate.kt index cc0b8b0fa..a0a307e62 100644 --- a/domain/src/main/java/tachiyomi/domain/items/episode/model/EpisodeUpdate.kt +++ b/domain/src/main/java/tachiyomi/domain/items/episode/model/EpisodeUpdate.kt @@ -17,5 +17,19 @@ data class EpisodeUpdate( ) fun Episode.toEpisodeUpdate(): EpisodeUpdate { - return EpisodeUpdate(id, animeId, seen, bookmark, lastSecondSeen, totalSeconds, dateFetch, sourceOrder, url, name, dateUpload, episodeNumber, scanlator) + return EpisodeUpdate( + id, + animeId, + seen, + bookmark, + lastSecondSeen, + totalSeconds, + dateFetch, + sourceOrder, + url, + name, + dateUpload, + episodeNumber, + scanlator, + ) } diff --git a/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeSorter.kt b/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeSorter.kt index 9d01f8771..0e4e60c3b 100644 --- a/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeSorter.kt +++ b/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeSorter.kt @@ -1,5 +1,6 @@ package tachiyomi.domain.items.episode.service +import tachiyomi.core.util.lang.compareToWithCollator import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode @@ -20,6 +21,10 @@ fun getEpisodeSort(anime: Anime, sortDescending: Boolean = anime.sortDescending( true -> { e1, e2 -> e2.dateUpload.compareTo(e1.dateUpload) } false -> { e1, e2 -> e1.dateUpload.compareTo(e2.dateUpload) } } + Anime.EPISODE_SORTING_ALPHABET -> when (sortDescending) { + true -> { e1, e2 -> e2.name.compareToWithCollator(e1.name) } + false -> { e1, e2 -> e1.name.compareToWithCollator(e2.name) } + } else -> throw NotImplementedError("Invalid episode sorting method: ${anime.sorting}") } } diff --git a/domain/src/main/java/tachiyomi/domain/library/anime/model/AnimeLibrarySortMode.kt b/domain/src/main/java/tachiyomi/domain/library/anime/model/AnimeLibrarySortMode.kt index 6c354e5f0..86f7f826f 100644 --- a/domain/src/main/java/tachiyomi/domain/library/anime/model/AnimeLibrarySortMode.kt +++ b/domain/src/main/java/tachiyomi/domain/library/anime/model/AnimeLibrarySortMode.kt @@ -32,6 +32,7 @@ data class AnimeLibrarySort( data object LatestEpisode : Type(0b00010100) data object EpisodeFetchDate : Type(0b00011000) data object DateAdded : Type(0b00011100) + data object TrackerMean : Type(0b000100000) data object AiringTime : Type(0b00100000) companion object { @@ -78,6 +79,7 @@ data class AnimeLibrarySort( Type.LatestEpisode, Type.EpisodeFetchDate, Type.DateAdded, + Type.TrackerMean, Type.AiringTime, ) } @@ -105,6 +107,7 @@ data class AnimeLibrarySort( "LATEST_EPISODE" -> Type.LatestEpisode "EPISODE_FETCH_DATE" -> Type.EpisodeFetchDate "DATE_ADDED" -> Type.DateAdded + "TRACKER_MEAN" -> Type.TrackerMean "AIRING_TIME" -> Type.AiringTime else -> Type.Alphabetical } @@ -126,6 +129,7 @@ data class AnimeLibrarySort( Type.LatestEpisode -> "LATEST_EPISODE" Type.EpisodeFetchDate -> "EPISODE_FETCH_DATE" Type.DateAdded -> "DATE_ADDED" + Type.TrackerMean -> "TRACKER_MEAN" Type.AiringTime -> "AIRING_TIME" } val direction = if (direction == Direction.Ascending) "ASCENDING" else "DESCENDING" diff --git a/domain/src/main/java/tachiyomi/domain/library/manga/model/MangaLibrarySortMode.kt b/domain/src/main/java/tachiyomi/domain/library/manga/model/MangaLibrarySortMode.kt index 5d28423dc..bc69e08d0 100644 --- a/domain/src/main/java/tachiyomi/domain/library/manga/model/MangaLibrarySortMode.kt +++ b/domain/src/main/java/tachiyomi/domain/library/manga/model/MangaLibrarySortMode.kt @@ -32,6 +32,7 @@ data class MangaLibrarySort( data object LatestChapter : Type(0b00010100) data object ChapterFetchDate : Type(0b00011000) data object DateAdded : Type(0b00011100) + data object TrackerMean : Type(0b000100000) companion object { fun valueOf(flag: Long): Type { @@ -77,6 +78,7 @@ data class MangaLibrarySort( Type.LatestChapter, Type.ChapterFetchDate, Type.DateAdded, + Type.TrackerMean, ) } val directions by lazy { setOf(Direction.Ascending, Direction.Descending) } @@ -103,6 +105,7 @@ data class MangaLibrarySort( "LATEST_CHAPTER" -> Type.LatestChapter "CHAPTER_FETCH_DATE" -> Type.ChapterFetchDate "DATE_ADDED" -> Type.DateAdded + "TRACKER_MEAN" -> Type.TrackerMean else -> Type.Alphabetical } val ascending = if (values[1] == "ASCENDING") Direction.Ascending else Direction.Descending @@ -123,6 +126,7 @@ data class MangaLibrarySort( Type.LatestChapter -> "LATEST_CHAPTER" Type.ChapterFetchDate -> "CHAPTER_FETCH_DATE" Type.DateAdded -> "DATE_ADDED" + Type.TrackerMean -> "TRACKER_MEAN" } val direction = if (direction == Direction.Ascending) "ASCENDING" else "DESCENDING" return "$type,$direction" diff --git a/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt b/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt index 1a0ff4e4b..113e48af3 100644 --- a/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt +++ b/domain/src/main/java/tachiyomi/domain/release/interactor/GetApplicationRelease.kt @@ -87,6 +87,7 @@ class GetApplicationRelease( sealed interface Result { data class NewUpdate(val release: Release) : Result data object NoNewUpdate : Result + data object OsTooOld : Result data object ThirdPartyInstallation : Result } } diff --git a/domain/src/main/java/tachiyomi/domain/track/anime/interactor/GetTracksPerAnime.kt b/domain/src/main/java/tachiyomi/domain/track/anime/interactor/GetTracksPerAnime.kt index fd5853631..619ad1aa0 100644 --- a/domain/src/main/java/tachiyomi/domain/track/anime/interactor/GetTracksPerAnime.kt +++ b/domain/src/main/java/tachiyomi/domain/track/anime/interactor/GetTracksPerAnime.kt @@ -2,19 +2,14 @@ package tachiyomi.domain.track.anime.interactor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import tachiyomi.domain.track.anime.model.AnimeTrack import tachiyomi.domain.track.anime.repository.AnimeTrackRepository class GetTracksPerAnime( private val trackRepository: AnimeTrackRepository, ) { - fun subscribe(): Flow>> { - return trackRepository.getAnimeTracksAsFlow().map { tracks -> - tracks - .groupBy { it.animeId } - .mapValues { entry -> - entry.value.map { it.syncId } - } - } + fun subscribe(): Flow>> { + return trackRepository.getAnimeTracksAsFlow().map { tracks -> tracks.groupBy { it.animeId } } } } diff --git a/domain/src/main/java/tachiyomi/domain/track/manga/interactor/GetTracksPerManga.kt b/domain/src/main/java/tachiyomi/domain/track/manga/interactor/GetTracksPerManga.kt index 906936cb0..48b263bde 100644 --- a/domain/src/main/java/tachiyomi/domain/track/manga/interactor/GetTracksPerManga.kt +++ b/domain/src/main/java/tachiyomi/domain/track/manga/interactor/GetTracksPerManga.kt @@ -2,19 +2,14 @@ package tachiyomi.domain.track.manga.interactor import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import tachiyomi.domain.track.manga.model.MangaTrack import tachiyomi.domain.track.manga.repository.MangaTrackRepository class GetTracksPerManga( private val trackRepository: MangaTrackRepository, ) { - fun subscribe(): Flow>> { - return trackRepository.getMangaTracksAsFlow().map { tracks -> - tracks - .groupBy { it.mangaId } - .mapValues { entry -> - entry.value.map { it.syncId } - } - } + fun subscribe(): Flow>> { + return trackRepository.getMangaTracksAsFlow().map { tracks -> tracks.groupBy { it.mangaId } } } } diff --git a/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt b/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt index 0f051599e..f2e382ec2 100644 --- a/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt +++ b/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt @@ -14,9 +14,9 @@ class LibraryFlagsTest { @Test fun `Check the amount of flags`() { LibraryDisplayMode.values.size shouldBe 4 - MangaLibrarySort.types.size shouldBe 8 + MangaLibrarySort.types.size shouldBe 9 MangaLibrarySort.directions.size shouldBe 2 - AnimeLibrarySort.types.size shouldBe 9 + AnimeLibrarySort.types.size shouldBe 10 AnimeLibrarySort.directions.size shouldBe 2 } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index beed01e0b..b7de463fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutlib_version = "10.9.1" +aboutlib_version = "10.9.2" okhttp_version = "5.0.0-alpha.11" shizuku_version = "12.2.0" sqlite = "2.4.0" @@ -9,7 +9,7 @@ voyager = "1.0.0-rc08" richtext = "0.17.0" [libraries] -desugar = "com.android.tools:desugar_jdk_libs:2.0.3" +desugar = "com.android.tools:desugar_jdk_libs:2.0.4" android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" rxjava = "io.reactivex:rxjava:1.3.8" @@ -82,7 +82,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" } junit = "org.junit.jupiter:junit-jupiter:5.10.0" -kotest-assertions = "io.kotest:kotest-assertions-core:5.7.2" +kotest-assertions = "io.kotest:kotest-assertions-core:5.8.0" mockk = "io.mockk:mockk:1.13.8" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 391e7d10b..6135bfad5 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -366,6 +366,8 @@ Vertical Both Actions + Save pages into separate folders + Creates folders according to entries\' title Show on actions long tap Background color White diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt index 23d6d9296..c91c5a3ad 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt @@ -97,7 +97,8 @@ fun VerticalFastScroller( } val thumbBottomPadding = with(LocalDensity.current) { bottomContentPadding.toPx() } - val heightPx = contentHeight.toFloat() - thumbTopPadding - thumbBottomPadding - listState.layoutInfo.afterContentPadding + val heightPx = contentHeight.toFloat() - thumbTopPadding - + thumbBottomPadding - listState.layoutInfo.afterContentPadding val thumbHeightPx = with(LocalDensity.current) { ThumbLength.toPx() } val trackHeightPx = heightPx - thumbHeightPx @@ -267,7 +268,8 @@ fun VerticalGridFastScroller( } val thumbBottomPadding = with(LocalDensity.current) { bottomContentPadding.toPx() } - val heightPx = contentHeight.toFloat() - thumbTopPadding - thumbBottomPadding - state.layoutInfo.afterContentPadding + val heightPx = + contentHeight.toFloat() - thumbTopPadding - thumbBottomPadding - state.layoutInfo.afterContentPadding val thumbHeightPx = with(LocalDensity.current) { ThumbLength.toPx() } val trackHeightPx = heightPx - thumbHeightPx diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt index eebe3fd7a..82bf7fbf3 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt @@ -122,7 +122,8 @@ private fun Modifier.drawScrollbar( items .fastFirstOrNull { (it.key as? String)?.startsWith(STICKY_HEADER_KEY_PREFIX)?.not() ?: true }!! .run { - val startPadding = if (reverseDirection) layoutInfo.afterContentPadding else layoutInfo.beforeContentPadding + val startPadding = + if (reverseDirection) layoutInfo.afterContentPadding else layoutInfo.beforeContentPadding startPadding + ((estimatedItemSize * index - offset) / totalSize * viewportSize) } } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt index d58ca0c27..5a806fb7a 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/anime/UpdatesAnimeWidget.kt @@ -70,7 +70,10 @@ fun UpdatesAnimeWidget( .padding(horizontal = 3.dp), contentAlignment = Alignment.Center, ) { - val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply { + val intent = Intent( + LocalContext.current, + Class.forName(Constants.MAIN_ACTIVITY), + ).apply { action = Constants.SHORTCUT_ANIME putExtra(Constants.ANIME_EXTRA, animeId) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt index e758dccba..d8d420965 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/components/manga/UpdatesMangaWidget.kt @@ -70,7 +70,10 @@ fun UpdatesMangaWidget( .padding(horizontal = 3.dp), contentAlignment = Alignment.Center, ) { - val intent = Intent(LocalContext.current, Class.forName(Constants.MAIN_ACTIVITY)).apply { + val intent = Intent( + LocalContext.current, + Class.forName(Constants.MAIN_ACTIVITY), + ).apply { action = Constants.SHORTCUT_MANGA putExtra(Constants.MANGA_EXTRA, mangaId) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt index d02b9449a..f5127dceb 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/anime/LocalAnimeSource.kt @@ -62,7 +62,8 @@ actual class LocalAnimeSource( override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage { val baseDirsFiles = fileSystem.getFilesInBaseDirectories() - val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } + val lastModifiedLimit by + lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } var animeDirs = baseDirsFiles // Filter out files that are hidden and is not a folder @@ -148,7 +149,8 @@ actual class LocalAnimeSource( @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchAnime")) override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { val baseDirsFiles = fileSystem.getFilesInBaseDirectories() - val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } + val lastModifiedLimit by + lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } var animeDirs = baseDirsFiles // Filter out files that are hidden and is not a folder .filter { it.isDirectory && !it.name.startsWith('.') } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt index ce6789596..e834c96a6 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt @@ -76,7 +76,8 @@ actual class LocalMangaSource( override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { val baseDirsFiles = fileSystem.getFilesInBaseDirectories() - val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } + val lastModifiedLimit by + lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } var mangaDirs = baseDirsFiles // Filter out files that are hidden and is not a folder