diff --git a/README.md b/README.md index 6e4bb522a0..75bc9488f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | Build | Preview Release | Codefactor | Stable | Translate Aniyomi | Discord Server | |-------|-----------|-------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| ![CI](https://github.com/aniyomiorg/aniyomi/workflows/CI/badge.svg?branch=master&event=push) | [![latest preview build](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi-preview/releases) | [![CodeFactor](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi/badge)](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi) | [![stable release](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi/releases) | [![Translation status](https://hosted.weblate.org/widgets/aniyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/aniyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | +| [![CI](https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml/badge.svg)] (https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml) | [![latest preview build](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi-preview/releases) | [![CodeFactor](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi/badge)](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi) | [![stable release](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi/releases) | [![Translation status](https://hosted.weblate.org/widgets/aniyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/aniyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | # ![app icon](.github/readme-images/app-icon.png)Aniyomi diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1bb38d0983..a64dfcb678 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -192,16 +192,14 @@ dependencies { // Job scheduling implementation(androidx.bundles.workmanager) - // RX + // RxJava implementation(libs.bundles.reactivex) implementation(libs.flowreactivenetwork) - // Network client + // Networking implementation(libs.bundles.okhttp) implementation(libs.okio) - - // TLS 1.3 support for Android < 10 - implementation(libs.conscrypt.android) + implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10 // Data serialization (JSON, protobuf, xml) implementation(kotlinx.bundles.serialization) @@ -238,9 +236,10 @@ dependencies { implementation(libs.insetter) implementation(libs.bundles.richtext) implementation(libs.aboutLibraries.compose) - implementation(libs.cascade) implementation(libs.bundles.voyager) - implementation(libs.materialmotion.core) + implementation(libs.compose.cascade) + implementation(libs.compose.materialmotion) + implementation(libs.compose.simpleicons) // Logging implementation(libs.logcat) diff --git a/app/src/main/java/eu/kanade/data/source/anime/AnimeSourceMapper.kt b/app/src/main/java/eu/kanade/data/source/anime/AnimeSourceMapper.kt deleted file mode 100644 index f5d02a9859..0000000000 --- a/app/src/main/java/eu/kanade/data/source/anime/AnimeSourceMapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.data.source.anime - -import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager -import tachiyomi.domain.source.anime.model.AnimeSource -import tachiyomi.domain.source.anime.model.AnimeSourceData - -val animeSourceMapper: (eu.kanade.tachiyomi.animesource.AnimeSource) -> AnimeSource = { source -> - AnimeSource( - source.id, - source.lang, - source.name, - supportsLatest = false, - isStub = source is AnimeSourceManager.StubAnimeSource, - ) -} - -val catalogueAnimeSourceMapper: (AnimeCatalogueSource) -> AnimeSource = { source -> - animeSourceMapper(source).copy(supportsLatest = source.supportsLatest) -} - -val animeSourceDataMapper: (Long, String, String) -> AnimeSourceData = { id, lang, name -> - AnimeSourceData(id, lang, name) -} diff --git a/app/src/main/java/eu/kanade/data/source/manga/MangaSourceMapper.kt b/app/src/main/java/eu/kanade/data/source/manga/MangaSourceMapper.kt deleted file mode 100644 index 008e3cdce1..0000000000 --- a/app/src/main/java/eu/kanade/data/source/manga/MangaSourceMapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.data.source.manga - -import eu.kanade.tachiyomi.source.CatalogueSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager -import tachiyomi.domain.source.manga.model.MangaSourceData -import tachiyomi.domain.source.manga.model.Source - -val mangaSourceMapper: (eu.kanade.tachiyomi.source.MangaSource) -> Source = { source -> - Source( - source.id, - source.lang, - source.name, - supportsLatest = false, - isStub = source is MangaSourceManager.StubMangaSource, - ) -} - -val catalogueMangaSourceMapper: (CatalogueSource) -> Source = { source -> - mangaSourceMapper(source).copy(supportsLatest = source.supportsLatest) -} - -val mangaSourceDataMapper: (Long, String, String) -> MangaSourceData = { id, lang, name -> - MangaSourceData(id, lang, name) -} diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index f01281ea2f..03c28cd0ca 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -1,15 +1,5 @@ package eu.kanade.domain -import eu.kanade.data.source.anime.AnimeSourceRepositoryImpl -import eu.kanade.data.source.manga.MangaSourceRepositoryImpl -import eu.kanade.domain.category.anime.interactor.CreateAnimeCategoryWithName -import eu.kanade.domain.category.anime.interactor.ResetAnimeCategoryFlags -import eu.kanade.domain.category.anime.interactor.SetDisplayModeForAnimeCategory -import eu.kanade.domain.category.anime.interactor.SetSortModeForAnimeCategory -import eu.kanade.domain.category.manga.interactor.CreateMangaCategoryWithName -import eu.kanade.domain.category.manga.interactor.ResetMangaCategoryFlags -import eu.kanade.domain.category.manga.interactor.SetDisplayModeForMangaCategory -import eu.kanade.domain.category.manga.interactor.SetSortModeForMangaCategory import eu.kanade.domain.download.anime.interactor.DeleteAnimeDownload import eu.kanade.domain.download.manga.interactor.DeleteChapterDownload import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags @@ -22,31 +12,22 @@ import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionsByType import eu.kanade.domain.extension.manga.interactor.GetExtensionSources import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionsByType -import eu.kanade.domain.history.anime.interactor.GetNextEpisodes -import eu.kanade.domain.history.manga.interactor.GetNextChapters import eu.kanade.domain.items.chapter.interactor.SetReadStatus import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay -import eu.kanade.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags import eu.kanade.domain.items.episode.interactor.SetSeenStatus import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay import eu.kanade.domain.source.anime.interactor.GetAnimeSourcesWithFavoriteCount -import eu.kanade.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime import eu.kanade.domain.source.anime.interactor.GetEnabledAnimeSources import eu.kanade.domain.source.anime.interactor.GetLanguagesWithAnimeSources -import eu.kanade.domain.source.anime.interactor.GetRemoteAnime import eu.kanade.domain.source.anime.interactor.ToggleAnimeSource import eu.kanade.domain.source.anime.interactor.ToggleAnimeSourcePin -import eu.kanade.domain.source.anime.repository.AnimeSourceRepository import eu.kanade.domain.source.manga.interactor.GetEnabledMangaSources import eu.kanade.domain.source.manga.interactor.GetLanguagesWithMangaSources import eu.kanade.domain.source.manga.interactor.GetMangaSourcesWithFavoriteCount -import eu.kanade.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga -import eu.kanade.domain.source.manga.interactor.GetRemoteManga import eu.kanade.domain.source.manga.interactor.ToggleMangaSource import eu.kanade.domain.source.manga.interactor.ToggleMangaSourcePin -import eu.kanade.domain.source.manga.repository.MangaSourceRepository import eu.kanade.domain.source.service.SetMigrateSorting import eu.kanade.domain.source.service.ToggleLanguage import tachiyomi.data.category.anime.AnimeCategoryRepositoryImpl @@ -58,23 +39,33 @@ import tachiyomi.data.history.manga.MangaHistoryRepositoryImpl import tachiyomi.data.items.chapter.ChapterRepositoryImpl import tachiyomi.data.items.episode.EpisodeRepositoryImpl import tachiyomi.data.source.anime.AnimeSourceDataRepositoryImpl +import tachiyomi.data.source.anime.AnimeSourceRepositoryImpl import tachiyomi.data.source.manga.MangaSourceDataRepositoryImpl +import tachiyomi.data.source.manga.MangaSourceRepositoryImpl import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl import tachiyomi.data.track.manga.MangaTrackRepositoryImpl import tachiyomi.data.updates.anime.AnimeUpdatesRepositoryImpl import tachiyomi.data.updates.manga.MangaUpdatesRepositoryImpl +import tachiyomi.domain.category.anime.interactor.CreateAnimeCategoryWithName import tachiyomi.domain.category.anime.interactor.DeleteAnimeCategory import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.anime.interactor.RenameAnimeCategory import tachiyomi.domain.category.anime.interactor.ReorderAnimeCategory +import tachiyomi.domain.category.anime.interactor.ResetAnimeCategoryFlags import tachiyomi.domain.category.anime.interactor.SetAnimeCategories +import tachiyomi.domain.category.anime.interactor.SetDisplayModeForAnimeCategory +import tachiyomi.domain.category.anime.interactor.SetSortModeForAnimeCategory import tachiyomi.domain.category.anime.interactor.UpdateAnimeCategory import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository +import tachiyomi.domain.category.manga.interactor.CreateMangaCategoryWithName import tachiyomi.domain.category.manga.interactor.DeleteMangaCategory import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.category.manga.interactor.RenameMangaCategory import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory +import tachiyomi.domain.category.manga.interactor.ResetMangaCategoryFlags +import tachiyomi.domain.category.manga.interactor.SetDisplayModeForMangaCategory import tachiyomi.domain.category.manga.interactor.SetMangaCategories +import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory import tachiyomi.domain.category.manga.interactor.UpdateMangaCategory import tachiyomi.domain.category.manga.repository.MangaCategoryRepository import tachiyomi.domain.entries.anime.interactor.GetAnime @@ -96,26 +87,36 @@ import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.entries.manga.repository.MangaRepository import tachiyomi.domain.history.anime.interactor.GetAnimeHistory +import tachiyomi.domain.history.anime.interactor.GetNextEpisodes import tachiyomi.domain.history.anime.interactor.RemoveAnimeHistory import tachiyomi.domain.history.anime.interactor.UpsertAnimeHistory import tachiyomi.domain.history.anime.repository.AnimeHistoryRepository import tachiyomi.domain.history.manga.interactor.GetMangaHistory +import tachiyomi.domain.history.manga.interactor.GetNextChapters import tachiyomi.domain.history.manga.interactor.GetTotalReadDuration import tachiyomi.domain.history.manga.interactor.RemoveMangaHistory import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory import tachiyomi.domain.history.manga.repository.MangaHistoryRepository import tachiyomi.domain.items.chapter.interactor.GetChapter import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId +import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter import tachiyomi.domain.items.chapter.interactor.UpdateChapter import tachiyomi.domain.items.chapter.repository.ChapterRepository import tachiyomi.domain.items.episode.interactor.GetEpisode import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId +import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode import tachiyomi.domain.items.episode.interactor.UpdateEpisode import tachiyomi.domain.items.episode.repository.EpisodeRepository +import tachiyomi.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime +import tachiyomi.domain.source.anime.interactor.GetRemoteAnime import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository +import tachiyomi.domain.source.anime.repository.AnimeSourceRepository +import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga +import tachiyomi.domain.source.manga.interactor.GetRemoteManga import tachiyomi.domain.source.manga.repository.MangaSourceDataRepository +import tachiyomi.domain.source.manga.repository.MangaSourceRepository import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime @@ -186,7 +187,7 @@ class DomainModule : InjektModule { addFactory { ResetMangaViewerFlags(get()) } addFactory { SetMangaChapterFlags(get()) } addFactory { - eu.kanade.domain.items.chapter.interactor.SetMangaDefaultChapterFlags( + SetMangaDefaultChapterFlags( get(), get(), get(), diff --git a/app/src/main/java/eu/kanade/domain/download/anime/interactor/DeleteEpisodeDownload.kt b/app/src/main/java/eu/kanade/domain/download/anime/interactor/DeleteEpisodeDownload.kt index 911ec167b0..eac3c1332e 100644 --- a/app/src/main/java/eu/kanade/domain/download/anime/interactor/DeleteEpisodeDownload.kt +++ b/app/src/main/java/eu/kanade/domain/download/anime/interactor/DeleteEpisodeDownload.kt @@ -1,10 +1,10 @@ package eu.kanade.domain.download.anime.interactor import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager class DeleteAnimeDownload( private val sourceManager: AnimeSourceManager, diff --git a/app/src/main/java/eu/kanade/domain/download/manga/interactor/DeleteChapterDownload.kt b/app/src/main/java/eu/kanade/domain/download/manga/interactor/DeleteChapterDownload.kt index 81b8c3aacd..0b74f8b1eb 100644 --- a/app/src/main/java/eu/kanade/domain/download/manga/interactor/DeleteChapterDownload.kt +++ b/app/src/main/java/eu/kanade/domain/download/manga/interactor/DeleteChapterDownload.kt @@ -1,10 +1,10 @@ package eu.kanade.domain.download.manga.interactor import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.service.MangaSourceManager class DeleteChapterDownload( private val sourceManager: MangaSourceManager, diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SetReadStatus.kt b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SetReadStatus.kt index eae6058dd9..6e07301dc5 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SetReadStatus.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SetReadStatus.kt @@ -1,10 +1,10 @@ package eu.kanade.domain.items.chapter.interactor import eu.kanade.domain.download.manga.interactor.DeleteChapterDownload -import eu.kanade.domain.download.service.DownloadPreferences import logcat.LogPriority import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.repository.MangaRepository import tachiyomi.domain.items.chapter.model.Chapter diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt b/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt index ab73c63dd8..36b0f30b3c 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/model/Chapter.kt @@ -1,5 +1,6 @@ package eu.kanade.domain.items.chapter.model +import data.Chapters import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl import eu.kanade.tachiyomi.source.model.SChapter import tachiyomi.domain.items.chapter.model.Chapter @@ -26,6 +27,16 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter { ) } +fun Chapter.copyFrom(other: Chapters): Chapter { + return copy( + name = other.name, + url = other.url, + dateUpload = other.date_upload, + chapterNumber = other.chapter_number, + scanlator = other.scanlator?.ifBlank { null }, + ) +} + fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also { it.id = id it.manga_id = mangaId diff --git a/app/src/main/java/eu/kanade/domain/items/episode/interactor/SetSeenStatus.kt b/app/src/main/java/eu/kanade/domain/items/episode/interactor/SetSeenStatus.kt index 09c0c300be..bfc181df96 100644 --- a/app/src/main/java/eu/kanade/domain/items/episode/interactor/SetSeenStatus.kt +++ b/app/src/main/java/eu/kanade/domain/items/episode/interactor/SetSeenStatus.kt @@ -1,10 +1,10 @@ package eu.kanade.domain.items.episode.interactor import eu.kanade.domain.download.anime.interactor.DeleteAnimeDownload -import eu.kanade.domain.download.service.DownloadPreferences import logcat.LogPriority import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.repository.AnimeRepository import tachiyomi.domain.items.episode.model.Episode diff --git a/app/src/main/java/eu/kanade/domain/items/episode/model/Episode.kt b/app/src/main/java/eu/kanade/domain/items/episode/model/Episode.kt index 70f6d09562..0c284cfa67 100644 --- a/app/src/main/java/eu/kanade/domain/items/episode/model/Episode.kt +++ b/app/src/main/java/eu/kanade/domain/items/episode/model/Episode.kt @@ -1,5 +1,6 @@ package eu.kanade.domain.items.episode.model +import dataanime.Episodes import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl import tachiyomi.domain.items.episode.model.Episode @@ -26,6 +27,16 @@ fun Episode.copyFromSEpisode(sEpisode: SEpisode): Episode { ) } +fun Episode.copyFrom(other: Episodes): Episode { + return copy( + name = other.name, + url = other.url, + dateUpload = other.date_upload, + episodeNumber = other.episode_number, + scanlator = other.scanlator?.ifBlank { null }, + ) +} + fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also { it.id = id it.anime_id = animeId 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 2dc21f3c72..2aede9abe2 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 @@ -1,11 +1,12 @@ package eu.kanade.domain.source.anime.interactor -import eu.kanade.domain.source.anime.repository.AnimeSourceRepository 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.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 @@ -21,7 +22,9 @@ class GetAnimeSourcesWithFavoriteCount( preferences.migrationSortingMode().changes(), repository.getAnimeSourcesWithFavoriteCount(), ) { direction, mode, list -> - list.sortedWith(sortFn(direction, mode)) + list + .filterNot { it.first.id == LocalAnimeSource.ID } + .sortedWith(sortFn(direction, mode)) } } diff --git a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetEnabledAnimeSources.kt b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetEnabledAnimeSources.kt index 5bde5bc2d5..d6a332c790 100644 --- a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetEnabledAnimeSources.kt +++ b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetEnabledAnimeSources.kt @@ -1,6 +1,5 @@ package eu.kanade.domain.source.anime.interactor -import eu.kanade.domain.source.anime.repository.AnimeSourceRepository import eu.kanade.domain.source.service.SourcePreferences import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -8,6 +7,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import tachiyomi.domain.source.anime.model.AnimeSource import tachiyomi.domain.source.anime.model.Pin import tachiyomi.domain.source.anime.model.Pins +import tachiyomi.domain.source.anime.repository.AnimeSourceRepository import tachiyomi.source.local.entries.anime.LocalAnimeSource class GetEnabledAnimeSources( diff --git a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetLanguagesWithAnimeSources.kt b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetLanguagesWithAnimeSources.kt index fcde642e2f..3b5564bbfd 100644 --- a/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetLanguagesWithAnimeSources.kt +++ b/app/src/main/java/eu/kanade/domain/source/anime/interactor/GetLanguagesWithAnimeSources.kt @@ -1,11 +1,11 @@ package eu.kanade.domain.source.anime.interactor -import eu.kanade.domain.source.anime.repository.AnimeSourceRepository import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.util.system.LocaleHelper import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import tachiyomi.domain.source.anime.model.AnimeSource +import tachiyomi.domain.source.anime.repository.AnimeSourceRepository class GetLanguagesWithAnimeSources( private val repository: AnimeSourceRepository, diff --git a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt index f6aa92c8f1..2c8a2b63ff 100644 --- a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt +++ b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetEnabledMangaSources.kt @@ -1,6 +1,5 @@ package eu.kanade.domain.source.manga.interactor -import eu.kanade.domain.source.manga.repository.MangaSourceRepository import eu.kanade.domain.source.service.SourcePreferences import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -8,6 +7,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import tachiyomi.domain.source.manga.model.Pin import tachiyomi.domain.source.manga.model.Pins import tachiyomi.domain.source.manga.model.Source +import tachiyomi.domain.source.manga.repository.MangaSourceRepository import tachiyomi.source.local.entries.manga.LocalMangaSource class GetEnabledMangaSources( diff --git a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetLanguagesWithMangaSources.kt b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetLanguagesWithMangaSources.kt index 9bbb67a11a..f76c0d8865 100644 --- a/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetLanguagesWithMangaSources.kt +++ b/app/src/main/java/eu/kanade/domain/source/manga/interactor/GetLanguagesWithMangaSources.kt @@ -1,11 +1,11 @@ package eu.kanade.domain.source.manga.interactor -import eu.kanade.domain.source.manga.repository.MangaSourceRepository import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.util.system.LocaleHelper import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import tachiyomi.domain.source.manga.model.Source +import tachiyomi.domain.source.manga.repository.MangaSourceRepository class GetLanguagesWithMangaSources( private val repository: MangaSourceRepository, 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 1352961d75..9dc574f409 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 @@ -1,11 +1,12 @@ package eu.kanade.domain.source.manga.interactor -import eu.kanade.domain.source.manga.repository.MangaSourceRepository 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.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 @@ -21,7 +22,9 @@ class GetMangaSourcesWithFavoriteCount( preferences.migrationSortingMode().changes(), repository.getMangaSourcesWithFavoriteCount(), ) { direction, mode, list -> - list.sortedWith(sortFn(direction, mode)) + list + .filterNot { it.first.id == LocalMangaSource.ID } + .sortedWith(sortFn(direction, mode)) } } 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 cc5bf7a164..4d977fcfef 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 @@ -31,30 +31,33 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame val trackManager = Injekt.get() val delayedTrackingStore = Injekt.get() - withIOContext { - val tracks = delayedTrackingStore.getAnimeItems().mapNotNull { - val track = getTracks.awaitOne(it.trackId) - if (track == null) { - delayedTrackingStore.removeAnimeItem(it.trackId) + val results = withIOContext { + delayedTrackingStore.getAnimeItems() + .mapNotNull { + val track = getTracks.awaitOne(it.trackId) + if (track == null) { + delayedTrackingStore.removeAnimeItem(it.trackId) + } + track?.copy(lastEpisodeSeen = it.lastEpisodeSeen.toDouble()) } - track - } - - tracks.forEach { animeTrack -> - try { - val service = trackManager.getService(animeTrack.syncId) - if (service != null && service.isLogged) { - service.animeService.update(animeTrack.toDbTrack(), true) - insertTrack.await(animeTrack) + .mapNotNull { animeTrack -> + try { + val service = trackManager.getService(animeTrack.syncId) + if (service != null && service.isLogged) { + logcat(LogPriority.DEBUG) { "Updating delayed track item: ${animeTrack.id}, last episode seen: ${animeTrack.lastEpisodeSeen}" } + service.animeService.update(animeTrack.toDbTrack(), true) + insertTrack.await(animeTrack) + } + delayedTrackingStore.removeAnimeItem(animeTrack.id) + null + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + false } - delayedTrackingStore.removeAnimeItem(animeTrack.id) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) } - } } - return Result.success() + return if (results.isNotEmpty()) Result.failure() else Result.success() } companion object { 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 fe2c40cd22..242bd5b2c3 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 @@ -31,30 +31,33 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame val trackManager = Injekt.get() val delayedTrackingStore = Injekt.get() - withIOContext { - val tracks = delayedTrackingStore.getMangaItems().mapNotNull { - val track = getTracks.awaitOne(it.trackId) - if (track == null) { - delayedTrackingStore.removeMangaItem(it.trackId) + val results = withIOContext { + delayedTrackingStore.getMangaItems() + .mapNotNull { + val track = getTracks.awaitOne(it.trackId) + if (track == null) { + delayedTrackingStore.removeMangaItem(it.trackId) + } + track?.copy(lastChapterRead = it.lastChapterRead.toDouble()) } - track - } - - tracks.forEach { track -> - try { - val service = trackManager.getService(track.syncId) - if (service != null && service.isLogged) { - service.mangaService.update(track.toDbTrack(), true) - insertTrack.await(track) + .mapNotNull { track -> + try { + val service = trackManager.getService(track.syncId) + if (service != null && service.isLogged) { + logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" } + service.mangaService.update(track.toDbTrack(), true) + insertTrack.await(track) + } + delayedTrackingStore.removeMangaItem(track.id) + null + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + false } - delayedTrackingStore.removeMangaItem(track.id) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) } - } } - return Result.success() + return if (results.isNotEmpty()) Result.failure() else Result.success() } companion object { diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt index bcd460c2a6..6bd23cda92 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import eu.kanade.presentation.browse.anime.components.BrowseAnimeSourceComfortableGrid @@ -22,11 +23,11 @@ import eu.kanade.presentation.browse.anime.components.BrowseAnimeSourceList import eu.kanade.presentation.components.AppBar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeSource -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import kotlinx.coroutines.flow.StateFlow import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.NoEpisodesException import tachiyomi.domain.library.model.LibraryDisplayMode +import tachiyomi.domain.source.anime.model.StubAnimeSource import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction @@ -147,7 +148,7 @@ fun BrowseAnimeSourceContent( @Composable fun MissingSourceScreen( - source: AnimeSourceManager.StubAnimeSource, + source: StubAnimeSource, navigateUp: () -> Unit, ) { Scaffold( @@ -160,7 +161,7 @@ fun MissingSourceScreen( }, ) { paddingValues -> EmptyScreen( - message = source.getSourceNotInstalledException().message!!, + message = stringResource(R.string.source_not_installed, source.toString()), modifier = Modifier.padding(paddingValues), ) } 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 c837eb96d4..426f0e909c 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 @@ -59,7 +59,7 @@ fun GlobalAnimeSearchScreen( } @Composable -fun GlobalAnimeSearchContent( +private fun GlobalAnimeSearchContent( items: Map, contentPadding: PaddingValues, getAnime: @Composable (AnimeCatalogueSource, Anime) -> State, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt index 9c19f1b312..907156ea57 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceComfortableGrid.kt @@ -58,7 +58,7 @@ fun BrowseAnimeSourceComfortableGrid( } @Composable -fun BrowseAnimeSourceComfortableGridItem( +private fun BrowseAnimeSourceComfortableGridItem( anime: Anime, onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt index 8aeb5e2cf7..6622880687 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/components/BrowseAnimeSourceList.kt @@ -53,7 +53,7 @@ fun BrowseAnimeSourceList( } @Composable -fun BrowseAnimeSourceListItem( +private fun BrowseAnimeSourceListItem( anime: Anime, onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt index 90c9d60bab..f9fd9b4672 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt @@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import eu.kanade.presentation.browse.manga.components.BrowseMangaSourceComfortableGrid @@ -22,11 +23,11 @@ import eu.kanade.presentation.browse.manga.components.BrowseMangaSourceList import eu.kanade.presentation.components.AppBar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.MangaSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import kotlinx.coroutines.flow.StateFlow import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.NoChaptersException import tachiyomi.domain.library.model.LibraryDisplayMode +import tachiyomi.domain.source.manga.model.StubMangaSource import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction @@ -147,7 +148,7 @@ fun BrowseSourceContent( @Composable fun MissingSourceScreen( - source: MangaSourceManager.StubMangaSource, + source: StubMangaSource, navigateUp: () -> Unit, ) { Scaffold( @@ -160,7 +161,7 @@ fun MissingSourceScreen( }, ) { paddingValues -> EmptyScreen( - message = source.getSourceNotInstalledException().message!!, + message = stringResource(R.string.source_not_installed, source.toString()), modifier = Modifier.padding(paddingValues), ) } 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 18ce69e94d..30308f6cea 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 @@ -59,7 +59,7 @@ fun GlobalMangaSearchScreen( } @Composable -fun GlobalSearchContent( +private fun GlobalSearchContent( items: Map, contentPadding: PaddingValues, getManga: @Composable (CatalogueSource, Manga) -> State, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt index 79b334221a..33d738a1bb 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceComfortableGrid.kt @@ -57,7 +57,7 @@ fun BrowseMangaSourceComfortableGrid( } @Composable -fun BrowseMangaSourceComfortableGridItem( +private fun BrowseMangaSourceComfortableGridItem( manga: Manga, onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceList.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceList.kt index 6f8af32eed..f4bf07f41e 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseMangaSourceList.kt @@ -52,7 +52,7 @@ fun BrowseMangaSourceList( } @Composable -fun BrowseMangaSourceListItem( +private fun BrowseMangaSourceListItem( manga: Manga, onClick: () -> Unit = {}, onLongClick: () -> Unit = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseSourceLoadingItem.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseSourceLoadingItem.kt index aeee75aeb2..86aa684017 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseSourceLoadingItem.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/components/BrowseSourceLoadingItem.kt @@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @Composable -fun BrowseSourceLoadingItem() { +internal fun BrowseSourceLoadingItem() { Row( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/eu/kanade/presentation/components/Banners.kt b/app/src/main/java/eu/kanade/presentation/components/Banners.kt index 5f288d7e27..14525e137b 100644 --- a/app/src/main/java/eu/kanade/presentation/components/Banners.kt +++ b/app/src/main/java/eu/kanade/presentation/components/Banners.kt @@ -69,8 +69,7 @@ fun AppStateBanners( val mainInsets = WindowInsets.statusBars val mainInsetsTop = mainInsets.getTop(density) SubcomposeLayout(modifier = modifier) { constraints -> - val indexingId = if (indexing) 0 else -1 - val indexingPlaceable = subcompose(indexingId) { + val indexingPlaceable = subcompose(0) { AnimatedVisibility( visible = indexing, enter = expandVertically(), @@ -83,8 +82,7 @@ fun AppStateBanners( }.fastMap { it.measure(constraints) } val indexingHeight = indexingPlaceable.fastMaxBy { it.height }?.height ?: 0 - val downloadedId = if (indexing) 1 else 0 - val downloadedOnlyPlaceable = subcompose(downloadedId) { + val downloadedOnlyPlaceable = subcompose(1) { AnimatedVisibility( visible = downloadedOnlyMode, enter = expandVertically(), @@ -98,12 +96,7 @@ fun AppStateBanners( }.fastMap { it.measure(constraints) } val downloadedOnlyHeight = downloadedOnlyPlaceable.fastMaxBy { it.height }?.height ?: 0 - val incognitoId = when { - indexing && downloadedOnlyMode -> 3 - indexing || downloadedOnlyMode -> 2 - else -> 1 - } - val incognitoPlaceable = subcompose(incognitoId) { + val incognitoPlaceable = subcompose(2) { AnimatedVisibility( visible = incognitoMode, enter = expandVertically(), diff --git a/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt b/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt index 37099032f1..d44a894e7d 100644 --- a/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/components/EntryDownloadDropdownMenu.kt @@ -13,7 +13,6 @@ fun EntryDownloadDropdownMenu( expanded: Boolean, onDismissRequest: () -> Unit, onDownloadClicked: (DownloadAction) -> Unit, - includeDownloadAllOption: Boolean = true, isManga: Boolean, ) { DropdownMenu( @@ -28,7 +27,6 @@ fun EntryDownloadDropdownMenu( DownloadAction.NEXT_10_ITEMS to pluralStringResource(downloadAmount, 10, 10), DownloadAction.NEXT_25_ITEMS to pluralStringResource(downloadAmount, 25, 25), DownloadAction.UNVIEWED_ITEMS to stringResource(downloadUnviewed), - (DownloadAction.ALL_ITEMS to stringResource(R.string.download_all)).takeIf { includeDownloadAllOption }, ).map { (downloadAction, string) -> DropdownMenuItem( text = { Text(text = string) }, diff --git a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt index 2f5e8a0fb6..5ec22b8268 100644 --- a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt +++ b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import eu.kanade.domain.download.service.DownloadPreferences +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.presentation.core.components.material.IconButtonTokens import uy.kohesive.injekt.injectLazy diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt index 15af354496..31f306b736 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt @@ -56,7 +56,7 @@ fun TabbedDialog( TabRow( modifier = Modifier.weight(1f), selectedTabIndex = pagerState.currentPage, - indicator = { TabIndicator(it[pagerState.currentPage]) }, + indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) }, divider = {}, ) { tabTitles.fastForEachIndexed { i, tab -> diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt index 2944edeef3..372b048444 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt @@ -92,7 +92,7 @@ fun TabbedScreen( FlexibleTabRow( scrollable = scrollable, selectedTabIndex = state.currentPage, - indicator = { TabIndicator(it[state.currentPage]) }, + indicator = { TabIndicator(it[state.currentPage], state.currentPageOffsetFraction) }, ) { tabs.forEachIndexed { index, tab -> Tab( 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 2557d7a1b7..bcd41f7321 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryBottomActionMenu.kt @@ -325,7 +325,6 @@ fun LibraryBottomActionMenu( expanded = downloadExpanded, onDismissRequest = onDismissRequest, onDownloadClicked = onDownloadClicked, - includeDownloadAllOption = false, isManga = isManga, ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt index dc3fc542d1..4b13112b27 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryScreenConstants.kt @@ -6,7 +6,6 @@ enum class DownloadAction { NEXT_10_ITEMS, NEXT_25_ITEMS, UNVIEWED_ITEMS, - ALL_ITEMS, } enum class EditCoverAction { diff --git a/app/src/main/java/eu/kanade/presentation/entries/EntryToolbar.kt b/app/src/main/java/eu/kanade/presentation/entries/EntryToolbar.kt index a26a9d3fdd..1e30b7872b 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/EntryToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/EntryToolbar.kt @@ -43,6 +43,7 @@ fun EntryToolbar( onClickShare: (() -> Unit)?, onClickDownload: ((DownloadAction) -> Unit)?, onClickEditCategory: (() -> Unit)?, + onClickRefresh: () -> Unit, onClickMigrate: (() -> Unit)?, // Anime only changeAnimeSkipIntro: (() -> Unit)?, @@ -112,44 +113,49 @@ fun EntryToolbar( Icon(Icons.Outlined.FilterList, contentDescription = stringResource(R.string.action_filter), tint = filterTint) } - if (onClickEditCategory != null || onClickMigrate != null || onClickShare != null || changeAnimeSkipIntro != null) { - OverflowMenu { closeMenu -> - if (onClickEditCategory != null) { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_edit_categories)) }, - onClick = { - onClickEditCategory() - closeMenu() - }, - ) - } - if (onClickMigrate != null) { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_migrate)) }, - onClick = { - onClickMigrate() - closeMenu() - }, - ) - } - if (changeAnimeSkipIntro != null) { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_change_intro_length)) }, - onClick = { - changeAnimeSkipIntro() - closeMenu() - }, - ) - } - if (onClickShare != null) { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_share)) }, - onClick = { - onClickShare() - closeMenu() - }, - ) - } + OverflowMenu { closeMenu -> + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_webview_refresh)) }, + onClick = { + onClickRefresh() + closeMenu() + }, + ) + if (onClickEditCategory != null) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_edit_categories)) }, + onClick = { + onClickEditCategory() + closeMenu() + }, + ) + } + if (onClickMigrate != null) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_migrate)) }, + onClick = { + onClickMigrate() + closeMenu() + }, + ) + } + if (changeAnimeSkipIntro != null) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_change_intro_length)) }, + onClick = { + changeAnimeSkipIntro() + closeMenu() + }, + ) + } + if (onClickShare != null) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_share)) }, + onClick = { + onClickShare() + closeMenu() + }, + ) } } } 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 ada1dadff3..bba77206fd 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 @@ -63,7 +63,6 @@ import eu.kanade.presentation.entries.anime.components.ExpandableAnimeDescriptio import eu.kanade.presentation.entries.anime.components.NextEpisodeAiringListItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import eu.kanade.tachiyomi.source.anime.getNameForAnimeInfo import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreenState import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem @@ -74,6 +73,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.coroutines.delay import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.model.StubAnimeSource import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.TwoPanelBox import tachiyomi.presentation.core.components.VerticalFastScroller @@ -289,6 +289,7 @@ private fun AnimeScreenSmallImpl( onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, + onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, changeAnimeSkipIntro = changeAnimeSkipIntro, actionModeCounter = episodes.count { it.selected }, @@ -371,7 +372,7 @@ private fun AnimeScreenSmallImpl( author = state.anime.author, artist = state.anime.artist, sourceName = remember { state.source.getNameForAnimeInfo() }, - isStubSource = remember { state.source is AnimeSourceManager.StubAnimeSource }, + isStubSource = remember { state.source is StubAnimeSource }, coverDataProvider = { state.anime }, status = state.anime.status, onCoverClick = onCoverClicked, @@ -546,6 +547,7 @@ fun AnimeScreenLargeImpl( onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, + onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, changeAnimeSkipIntro = changeAnimeSkipIntro, actionModeCounter = episodes.count { it.selected }, @@ -612,7 +614,7 @@ fun AnimeScreenLargeImpl( author = state.anime.author, artist = state.anime.artist, sourceName = remember { state.source.getNameForAnimeInfo() }, - isStubSource = remember { state.source is AnimeSourceManager.StubAnimeSource }, + isStubSource = remember { state.source is StubAnimeSource }, coverDataProvider = { state.anime }, status = state.anime.status, onCoverClick = onCoverClicked, diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeOptionsDialogScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeOptionsDialogScreen.kt index bb2d194e9b..3fe3bd5545 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeOptionsDialogScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/EpisodeOptionsDialogScreen.kt @@ -40,7 +40,6 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import eu.kanade.tachiyomi.ui.player.loader.EpisodeLoader import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.DelicateCoroutinesApi @@ -55,6 +54,7 @@ import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.interactor.GetEpisode import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.screens.LoadingScreen import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt index 2f6fd805e7..3c2d7794be 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt @@ -1,16 +1,18 @@ package eu.kanade.presentation.entries.anime.components import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark +import androidx.compose.material.icons.filled.Circle import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text @@ -63,9 +65,25 @@ fun AnimeEpisodeListItem( ) .padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp), ) { - Column(modifier = Modifier.weight(1f)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(2.dp), + verticalAlignment = Alignment.CenterVertically, + ) { var textHeight by remember { mutableStateOf(0) } + if (!seen) { + Icon( + imageVector = Icons.Filled.Circle, + contentDescription = stringResource(R.string.unread), + modifier = Modifier + .height(8.dp) + .padding(end = 4.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } if (bookmark) { Icon( imageVector = Icons.Filled.Bookmark, @@ -74,11 +92,11 @@ fun AnimeEpisodeListItem( .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }), tint = MaterialTheme.colorScheme.primary, ) - Spacer(modifier = Modifier.width(2.dp)) } Text( text = title, style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = textAlpha), maxLines = 1, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, @@ -86,11 +104,12 @@ fun AnimeEpisodeListItem( ) } - Spacer(modifier = Modifier.height(6.dp)) - - Row(modifier = Modifier.alpha(textSubtitleAlpha)) { + Row { ProvideTextStyle( - value = MaterialTheme.typography.bodyMedium.copy(fontSize = 12.sp), + value = MaterialTheme.typography.bodyMedium.copy( + fontSize = 12.sp, + color = LocalContentColor.current.copy(alpha = textSubtitleAlpha), + ), ) { if (date != null) { Text( 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 fb411d5df5..3c57de0128 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 @@ -60,7 +60,6 @@ import eu.kanade.presentation.entries.manga.components.MangaChapterListItem import eu.kanade.presentation.entries.manga.components.MangaInfoBox import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.manga.getNameForMangaInfo import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem import eu.kanade.tachiyomi.ui.entries.manga.MangaScreenState @@ -69,6 +68,7 @@ import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.system.copyToClipboard import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.model.StubMangaSource import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.TwoPanelBox import tachiyomi.presentation.core.components.VerticalFastScroller @@ -277,6 +277,7 @@ private fun MangaScreenSmallImpl( onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, + onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, changeAnimeSkipIntro = null, actionModeCounter = chapters.count { it.selected }, @@ -353,7 +354,7 @@ private fun MangaScreenSmallImpl( author = state.manga.author, artist = state.manga.artist, sourceName = remember { state.source.getNameForMangaInfo() }, - isStubSource = remember { state.source is MangaSourceManager.StubMangaSource }, + isStubSource = remember { state.source is StubMangaSource }, coverDataProvider = { state.manga }, status = state.manga.status, onCoverClick = onCoverClicked, @@ -500,6 +501,7 @@ fun MangaScreenLargeImpl( onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, + onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, changeAnimeSkipIntro = null, actionModeCounter = chapters.count { it.selected }, @@ -565,7 +567,7 @@ fun MangaScreenLargeImpl( author = state.manga.author, artist = state.manga.artist, sourceName = remember { state.source.getNameForMangaInfo() }, - isStubSource = remember { state.source is MangaSourceManager.StubMangaSource }, + isStubSource = remember { state.source is StubMangaSource }, coverDataProvider = { state.manga }, status = state.manga.status, onCoverClick = onCoverClicked, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt index 6253495c3b..82f1ba06b0 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt @@ -1,16 +1,17 @@ package eu.kanade.presentation.entries.manga.components import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark +import androidx.compose.material.icons.filled.Circle import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Text @@ -63,9 +64,25 @@ fun MangaChapterListItem( ) .padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp), ) { - Column(modifier = Modifier.weight(1f)) { - Row(verticalAlignment = Alignment.CenterVertically) { + Column( + modifier = Modifier.weight(1f), + verticalArrangement = Arrangement.spacedBy(6.dp), + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(2.dp), + verticalAlignment = Alignment.CenterVertically, + ) { var textHeight by remember { mutableStateOf(0) } + if (!read) { + Icon( + imageVector = Icons.Filled.Circle, + contentDescription = stringResource(R.string.unread), + modifier = Modifier + .height(8.dp) + .padding(end = 4.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } if (bookmark) { Icon( imageVector = Icons.Filled.Bookmark, @@ -74,11 +91,11 @@ fun MangaChapterListItem( .sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }), tint = MaterialTheme.colorScheme.primary, ) - Spacer(modifier = Modifier.width(2.dp)) } Text( text = title, style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = textAlpha), maxLines = 1, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, @@ -86,11 +103,12 @@ fun MangaChapterListItem( ) } - Spacer(modifier = Modifier.height(6.dp)) - - Row(modifier = Modifier.alpha(textSubtitleAlpha)) { + Row { ProvideTextStyle( - value = MaterialTheme.typography.bodyMedium.copy(fontSize = 12.sp), + value = MaterialTheme.typography.bodyMedium.copy( + fontSize = 12.sp, + color = LocalContentColor.current.copy(alpha = textSubtitleAlpha), + ), ) { if (date != null) { Text( diff --git a/app/src/main/java/eu/kanade/presentation/library/LibraryTabs.kt b/app/src/main/java/eu/kanade/presentation/library/LibraryTabs.kt index e8f14b645e..bc77c451b4 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibraryTabs.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibraryTabs.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp import eu.kanade.presentation.category.visualName import tachiyomi.domain.category.model.Category +import tachiyomi.presentation.core.components.PagerState import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.TabIndicator import tachiyomi.presentation.core.components.material.TabText @@ -15,22 +16,22 @@ import tachiyomi.presentation.core.components.material.TabText @Composable fun LibraryTabs( categories: List, - currentPageIndex: Int, + pagerState: PagerState, getNumberOfItemsForCategory: (Category) -> Int?, onTabItemClick: (Int) -> Unit, ) { Column { ScrollableTabRow( - selectedTabIndex = currentPageIndex, + selectedTabIndex = pagerState.currentPage, edgePadding = 0.dp, - indicator = { TabIndicator(it[currentPageIndex]) }, + indicator = { TabIndicator(it[pagerState.currentPage], pagerState.currentPageOffsetFraction) }, // TODO: use default when width is fixed upstream // https://issuetracker.google.com/issues/242879624 divider = {}, ) { categories.forEachIndexed { index, category -> Tab( - selected = currentPageIndex == index, + selected = pagerState.currentPage == index, onClick = { onTabItemClick(index) }, text = { TabText( diff --git a/app/src/main/java/eu/kanade/presentation/library/LibraryToolbar.kt b/app/src/main/java/eu/kanade/presentation/library/LibraryToolbar.kt index c656cd8dc9..fdeffe898a 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibraryToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibraryToolbar.kt @@ -37,6 +37,7 @@ fun LibraryToolbar( onClickInvertSelection: () -> Unit, onClickFilter: () -> Unit, onClickRefresh: () -> Unit, + onClickGlobalUpdate: () -> Unit, onClickOpenRandomEntry: () -> Unit, searchQuery: String?, onSearchQueryChange: (String?) -> Unit, @@ -57,6 +58,7 @@ fun LibraryToolbar( onSearchQueryChange = onSearchQueryChange, onClickFilter = onClickFilter, onClickRefresh = onClickRefresh, + onClickGlobalUpdate = onClickGlobalUpdate, onClickOpenRandomEntry = onClickOpenRandomEntry, scrollBehavior = scrollBehavior, navigateUp = navigateUp, @@ -71,6 +73,7 @@ fun LibraryRegularToolbar( onSearchQueryChange: (String?) -> Unit, onClickFilter: () -> Unit, onClickRefresh: () -> Unit, + onClickGlobalUpdate: () -> Unit, onClickOpenRandomEntry: () -> Unit, scrollBehavior: TopAppBarScrollBehavior?, navigateUp: (() -> Unit)? = null, @@ -105,6 +108,13 @@ fun LibraryRegularToolbar( OverflowMenu { closeMenu -> DropdownMenuItem( text = { Text(text = stringResource(R.string.pref_category_library_update)) }, + onClick = { + onClickGlobalUpdate() + closeMenu() + }, + ) + DropdownMenuItem( + text = { Text(text = stringResource(R.string.action_update_category)) }, onClick = { onClickRefresh() closeMenu() diff --git a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt index 02e607d7f1..11c6fe6fc9 100644 --- a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt @@ -66,7 +66,7 @@ fun AnimeLibraryContent( } LibraryTabs( categories = categories, - currentPageIndex = pagerState.currentPage, + pagerState = pagerState, getNumberOfItemsForCategory = getNumberOfAnimeForCategory, ) { scope.launch { pagerState.animateScrollToPage(it) } } } 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 f0644f2c8c..4c075dcffe 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 @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings import eu.kanade.presentation.components.TriStateItem @@ -22,6 +21,7 @@ import tachiyomi.domain.library.anime.model.AnimeLibrarySort import tachiyomi.domain.library.anime.model.sort import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.display +import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.RadioItem @@ -185,7 +185,7 @@ private fun ColumnScope.DisplayPage( ) } - HeadingItem(R.string.badges_header) + HeadingItem(R.string.complications_header) val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState() CheckboxItem( label = stringResource(R.string.action_display_download_badge_anime), @@ -210,6 +210,14 @@ private fun ColumnScope.DisplayPage( screenModel.togglePreference(LibraryPreferences::languageBadge) }, ) + val showContinueViewingButton by screenModel.libraryPreferences.showContinueViewingButton().collectAsState() + CheckboxItem( + label = stringResource(R.string.action_display_show_continue_reading_button), + checked = showContinueViewingButton, + onClick = { + screenModel.togglePreference(LibraryPreferences::showContinueViewingButton) + }, + ) HeadingItem(R.string.tabs_header) val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState() @@ -228,14 +236,4 @@ private fun ColumnScope.DisplayPage( screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems) }, ) - - HeadingItem(R.string.other_header) - val showContinueWatchingButton by screenModel.libraryPreferences.showContinueViewingButton().collectAsState() - CheckboxItem( - label = stringResource(R.string.action_display_show_continue_reading_button), - checked = showContinueWatchingButton, - onClick = { - screenModel.togglePreference(LibraryPreferences::showContinueViewingButton) - }, - ) } diff --git a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt index 4a49534b3d..db60bbf825 100644 --- a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt @@ -66,7 +66,7 @@ fun MangaLibraryContent( } LibraryTabs( categories = categories, - currentPageIndex = pagerState.currentPage, + pagerState = pagerState, getNumberOfItemsForCategory = getNumberOfMangaForCategory, ) { scope.launch { pagerState.animateScrollToPage(it) } } } 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 7f3fb26df7..ba911a5e5b 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 @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings import eu.kanade.presentation.components.TriStateItem @@ -22,6 +21,7 @@ import tachiyomi.domain.library.manga.model.MangaLibrarySort import tachiyomi.domain.library.manga.model.sort import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.display +import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.RadioItem @@ -184,7 +184,7 @@ private fun ColumnScope.DisplayPage( ) } - HeadingItem(R.string.badges_header) + HeadingItem(R.string.complications_header) val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState() CheckboxItem( label = stringResource(R.string.action_display_download_badge), @@ -209,6 +209,14 @@ private fun ColumnScope.DisplayPage( screenModel.togglePreference(LibraryPreferences::languageBadge) }, ) + val showContinueViewingButton by screenModel.libraryPreferences.showContinueViewingButton().collectAsState() + CheckboxItem( + label = stringResource(R.string.action_display_show_continue_reading_button), + checked = showContinueViewingButton, + onClick = { + screenModel.togglePreference(LibraryPreferences::showContinueViewingButton) + }, + ) HeadingItem(R.string.tabs_header) val categoryTabs by screenModel.libraryPreferences.categoryTabs().collectAsState() @@ -227,14 +235,4 @@ private fun ColumnScope.DisplayPage( screenModel.togglePreference(LibraryPreferences::categoryNumberOfItems) }, ) - - HeadingItem(R.string.other_header) - val showContinueReadingButton by screenModel.libraryPreferences.showContinueViewingButton().collectAsState() - CheckboxItem( - label = stringResource(R.string.action_display_show_continue_reading_button), - checked = showContinueReadingButton, - onClick = { - screenModel.togglePreference(LibraryPreferences::showContinueViewingButton) - }, - ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt index 4b27869352..e075ead89f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -26,13 +26,13 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.Constants import eu.kanade.tachiyomi.ui.more.DownloadQueueState +import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Scaffold diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt index f055df9507..ae1f6f5a81 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -10,14 +10,16 @@ import androidx.compose.material.icons.outlined.Public import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import compose.icons.SimpleIcons +import compose.icons.simpleicons.Discord +import compose.icons.simpleicons.Github +import compose.icons.simpleicons.Reddit import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.more.LogoHeader @@ -145,22 +147,22 @@ object AboutScreen : Screen() { ) { LinkIcon( label = stringResource(R.string.website), - painter = rememberVectorPainter(Icons.Outlined.Public), + icon = Icons.Outlined.Public, url = "https://aniyomi.org", ) LinkIcon( label = "Discord", - painter = painterResource(R.drawable.ic_discord_24dp), + icon = SimpleIcons.Discord, url = "https://discord.gg/F32UjdJZrR", ) LinkIcon( label = "Reddit", - painter = painterResource(R.drawable.ic_reddit_24dp), + icon = SimpleIcons.Reddit, url = "https://www.reddit.com/r/Aniyomi", ) LinkIcon( label = "GitHub", - painter = painterResource(R.drawable.ic_github_24dp), + icon = SimpleIcons.Github, url = "https://github.com/aniyomiorg/aniyomi", ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt index 198f9aac65..bdaa85ffa8 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearAnimeDatabaseScreen.kt @@ -32,7 +32,6 @@ import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime import eu.kanade.presentation.browse.anime.components.AnimeSourceIcon import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions @@ -44,6 +43,7 @@ import kotlinx.coroutines.flow.update import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchUI import tachiyomi.core.util.lang.withNonCancellableContext +import tachiyomi.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime import tachiyomi.domain.source.anime.model.AnimeSource import tachiyomi.domain.source.anime.model.AnimeSourceWithCount import tachiyomi.mi.data.AnimeDatabase diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt index 84843550f6..5ca86e8c1f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/ClearDatabaseScreen.kt @@ -32,7 +32,6 @@ import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga import eu.kanade.presentation.browse.manga.components.MangaSourceIcon import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBarActions @@ -45,6 +44,7 @@ import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchUI import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.data.Database +import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga import tachiyomi.domain.source.manga.model.MangaSourceWithCount import tachiyomi.domain.source.manga.model.Source import tachiyomi.presentation.core.components.FastScrollLazyColumn diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 75a07d01ff..4c4bc44fb6 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -25,7 +25,6 @@ import androidx.core.net.toUri import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R @@ -62,6 +61,7 @@ import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.manga.repository.MangaRepository +import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt index 67780659d2..0e5dc4d0f5 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.hippo.unifile.UniFile -import eu.kanade.domain.backup.service.BackupPreferences import eu.kanade.presentation.extensions.RequestStoragePermission import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState @@ -58,6 +57,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.launch +import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.util.isScrolledToEnd 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 35e984a7bf..bb19ebb98f 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 @@ -20,7 +20,6 @@ import androidx.compose.ui.util.fastMap import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.presentation.category.visualName import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.widget.TriStateListDialog @@ -30,6 +29,7 @@ import kotlinx.coroutines.runBlocking import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.category.model.Category +import tachiyomi.domain.download.service.DownloadPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File @@ -61,11 +61,6 @@ object SettingsDownloadScreen : SearchableSettings { pref = downloadPreferences.saveChaptersAsCBZ(), title = stringResource(R.string.save_chapter_as_cbz), ), - Preference.PreferenceItem.SwitchPreference( - pref = downloadPreferences.splitTallImages(), - title = stringResource(R.string.split_tall_images), - subtitle = stringResource(R.string.split_tall_images_summary), - ), Preference.PreferenceItem.ListPreference( pref = downloadPreferences.numberOfDownloads(), title = stringResource(R.string.pref_download_slots), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt index bcbdf9d87f..2f92a9d4e7 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsGeneralScreen.kt @@ -19,7 +19,6 @@ import androidx.compose.ui.res.stringResource import androidx.core.app.ActivityCompat import androidx.core.os.LocaleListCompat import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.home.HomeScreen @@ -27,6 +26,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.drop import org.xmlpull.v1.XmlPullParser +import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index cad50ccbdc..c79487d6cf 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -31,28 +31,28 @@ import androidx.core.content.ContextCompat import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.category.manga.interactor.ResetMangaCategoryFlags -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.category.visualName import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.widget.TriStateListDialog import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW -import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING -import eu.kanade.tachiyomi.data.preference.DEVICE_NETWORK_NOT_METERED -import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.ENTRY_HAS_UNVIEWED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_COMPLETED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_VIEWED import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.ui.category.CategoriesTab import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.manga.interactor.GetMangaCategories +import tachiyomi.domain.category.manga.interactor.ResetMangaCategoryFlags import tachiyomi.domain.category.model.Category +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED import tachiyomi.presentation.core.components.WheelPicker import tachiyomi.presentation.core.components.WheelPickerDefaults import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt index 39604f866b..49331d833a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt @@ -52,11 +52,11 @@ import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi import eu.kanade.tachiyomi.data.track.simkl.SimklApi -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toast import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.withUIContext +import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.presentation.core.components.material.padding import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/components/StatsItem.kt b/app/src/main/java/eu/kanade/presentation/more/stats/components/StatsItem.kt index 65e01b1991..32f4fcd28b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/stats/components/StatsItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/stats/components/StatsItem.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -76,7 +75,7 @@ private fun RowScope.BaseStatsItem( ) if (icon != null) { Icon( - painter = rememberVectorPainter(icon), + imageVector = icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary, ) diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index a2e2aa0cec..b3e6c79b11 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.isScrolledToStart fun TrackStatusSelector( selection: Int, onSelectionChange: (Int) -> Unit, - selections: Map, + selections: Map, onConfirm: () -> Unit, onDismissRequest: () -> Unit, ) { @@ -71,7 +71,7 @@ fun TrackStatusSelector( onClick = null, ) Text( - text = value, + text = value?.let { stringResource(it) } ?: "", style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 24.dp), ) diff --git a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt index 35eed666a4..763f729cfb 100644 --- a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.track.anime +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable @@ -30,6 +31,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import eu.kanade.domain.track.anime.model.toDbTrack import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.presentation.track.manga.TrackDetailsItem import eu.kanade.presentation.track.manga.TrackInfoItemMenu @@ -72,10 +74,10 @@ fun AnimeTrackInfoDialogHome( TrackInfoItem( title = item.track.title, service = item.service, - status = item.service.getStatus(item.track.status), + status = item.service.getStatus(item.track.status.toInt()), onStatusClick = { onStatusClick(item) }, - episodes = "${item.track.last_episode_seen.toInt()}".let { - val totalEpisodes = item.track.total_episodes + episodes = "${item.track.lastEpisodeSeen.toInt()}".let { + val totalEpisodes = item.track.totalEpisodes if (totalEpisodes > 0) { // Add known total episode count "$it / $totalEpisodes" @@ -84,16 +86,16 @@ fun AnimeTrackInfoDialogHome( } }, onEpisodesClick = { onEpisodeClick(item) }, - score = item.service.animeService.displayScore(item.track) + score = item.service.animeService.displayScore(item.track.toDbTrack()) .takeIf { supportsScoring && item.track.score != 0F }, onScoreClick = { onScoreClick(item) } .takeIf { supportsScoring }, - startDate = remember(item.track.started_watching_date) { dateFormat.format(item.track.started_watching_date) } - .takeIf { supportsReadingDates && item.track.started_watching_date != 0L }, + startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) } + .takeIf { supportsReadingDates && item.track.startDate != 0L }, onStartDateClick = { onStartDateEdit(item) } // TODO .takeIf { supportsReadingDates }, - endDate = dateFormat.format(item.track.finished_watching_date) - .takeIf { supportsReadingDates && item.track.finished_watching_date != 0L }, + endDate = dateFormat.format(item.track.finishDate) + .takeIf { supportsReadingDates && item.track.finishDate != 0L }, onEndDateClick = { onEndDateEdit(item) } .takeIf { supportsReadingDates }, onNewSearch = { onNewSearch(item) }, @@ -114,7 +116,7 @@ fun AnimeTrackInfoDialogHome( private fun TrackInfoItem( title: String, service: TrackService, - status: String, + @StringRes status: Int?, onStatusClick: () -> Unit, episodes: String, onEpisodesClick: () -> Unit, @@ -176,7 +178,7 @@ private fun TrackInfoItem( Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1f), - text = status, + text = status?.let { stringResource(it) } ?: "", onClick = onStatusClick, ) VerticalDivider() diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt index 188bfff254..9123e819d1 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.track.manga +import androidx.annotation.StringRes import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -42,6 +43,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import eu.kanade.domain.track.manga.model.toDbTrack import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.track.components.TrackLogoIcon import eu.kanade.tachiyomi.R @@ -83,10 +85,10 @@ fun MangaTrackInfoDialogHome( TrackInfoItem( title = item.track.title, service = item.service, - status = item.service.getStatus(item.track.status), + status = item.service.getStatus(item.track.status.toInt()), onStatusClick = { onStatusClick(item) }, - chapters = "${item.track.last_chapter_read.toInt()}".let { - val totalChapters = item.track.total_chapters + chapters = "${item.track.lastChapterRead.toInt()}".let { + val totalChapters = item.track.totalChapters if (totalChapters > 0) { // Add known total chapter count "$it / $totalChapters" @@ -95,16 +97,16 @@ fun MangaTrackInfoDialogHome( } }, onChaptersClick = { onChapterClick(item) }, - score = item.service.mangaService.displayScore(item.track) + score = item.service.mangaService.displayScore(item.track.toDbTrack()) .takeIf { supportsScoring && item.track.score != 0F }, onScoreClick = { onScoreClick(item) } .takeIf { supportsScoring }, - startDate = remember(item.track.started_reading_date) { dateFormat.format(item.track.started_reading_date) } - .takeIf { supportsReadingDates && item.track.started_reading_date != 0L }, + startDate = remember(item.track.startDate) { dateFormat.format(item.track.startDate) } + .takeIf { supportsReadingDates && item.track.startDate != 0L }, onStartDateClick = { onStartDateEdit(item) } // TODO .takeIf { supportsReadingDates }, - endDate = dateFormat.format(item.track.finished_reading_date) - .takeIf { supportsReadingDates && item.track.finished_reading_date != 0L }, + endDate = dateFormat.format(item.track.finishDate) + .takeIf { supportsReadingDates && item.track.finishDate != 0L }, onEndDateClick = { onEndDateEdit(item) } .takeIf { supportsReadingDates }, onNewSearch = { onNewSearch(item) }, @@ -125,7 +127,7 @@ fun MangaTrackInfoDialogHome( private fun TrackInfoItem( title: String, service: TrackService, - status: String, + @StringRes status: Int?, onStatusClick: () -> Unit, chapters: String, onChaptersClick: () -> Unit, @@ -187,7 +189,7 @@ private fun TrackInfoItem( Row(modifier = Modifier.height(IntrinsicSize.Min)) { TrackDetailsItem( modifier = Modifier.weight(1f), - text = status, + text = status?.let { stringResource(it) } ?: "", onClick = onStatusClick, ) VerticalDivider() diff --git a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt index f3fa113b0f..dfa3fb8d65 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt @@ -16,7 +16,9 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark +import androidx.compose.material.icons.filled.Circle import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -196,12 +198,22 @@ fun AnimeUpdatesUiItem( text = update.animeTitle, maxLines = 1, style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = textAlpha), overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(textAlpha), ) Row(verticalAlignment = Alignment.CenterVertically) { var textHeight by remember { mutableStateOf(0) } + if (!update.seen) { + Icon( + imageVector = Icons.Filled.Circle, + contentDescription = stringResource(R.string.unread), + modifier = Modifier + .height(8.dp) + .padding(end = 4.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } if (update.bookmark) { Icon( imageVector = Icons.Filled.Bookmark, @@ -216,19 +228,19 @@ fun AnimeUpdatesUiItem( text = update.episodeName, maxLines = 1, style = MaterialTheme.typography.bodySmall, + color = LocalContentColor.current.copy(alpha = textAlpha), overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, modifier = Modifier - .weight(weight = 1f, fill = false) - .alpha(textAlpha), + .weight(weight = 1f, fill = false), ) if (watchProgress != null) { DotSeparatorText() Text( text = watchProgress, maxLines = 1, + color = LocalContentColor.current.copy(alpha = ReadItemAlpha), overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(ReadItemAlpha), ) } } diff --git a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt index e65a55c600..bbf38c182f 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt @@ -16,7 +16,9 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Bookmark +import androidx.compose.material.icons.filled.Circle import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -194,11 +196,21 @@ fun MangaUpdatesUiItem( text = update.mangaTitle, maxLines = 1, style = MaterialTheme.typography.bodyMedium, + color = LocalContentColor.current.copy(alpha = textAlpha), overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(textAlpha), ) Row(verticalAlignment = Alignment.CenterVertically) { var textHeight by remember { mutableStateOf(0) } + if (!update.read) { + Icon( + imageVector = Icons.Filled.Circle, + contentDescription = stringResource(R.string.unread), + modifier = Modifier + .height(8.dp) + .padding(end = 4.dp), + tint = MaterialTheme.colorScheme.primary, + ) + } if (update.bookmark) { Icon( imageVector = Icons.Filled.Bookmark, @@ -213,19 +225,19 @@ fun MangaUpdatesUiItem( text = update.chapterName, maxLines = 1, style = MaterialTheme.typography.bodySmall, + color = LocalContentColor.current.copy(alpha = textAlpha), overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, modifier = Modifier - .weight(weight = 1f, fill = false) - .alpha(textAlpha), + .weight(weight = 1f, fill = false), ) if (readProgress != null) { DotSeparatorText() Text( text = readProgress, maxLines = 1, + color = LocalContentColor.current.copy(alpha = ReadItemAlpha), overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(ReadItemAlpha), ) } } diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index eab459b14f..fe89516d64 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -3,7 +3,6 @@ package eu.kanade.presentation.util import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey @@ -34,7 +33,7 @@ interface AssistContentScreen { } @Composable -fun DefaultNavigatorScreenTransition(navigator: Navigator, modifier: Modifier = Modifier) { +fun DefaultNavigatorScreenTransition(navigator: Navigator) { val slideDistance = rememberSlideDistance() ScreenTransition( navigator = navigator, @@ -44,6 +43,5 @@ fun DefaultNavigatorScreenTransition(navigator: Navigator, modifier: Modifier = slideDistance = slideDistance, ) }, - modifier = modifier, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index ecf50efdb9..c34fa42d31 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -31,8 +31,6 @@ import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.data.coil.AnimeCoverFetcher import eu.kanade.tachiyomi.data.coil.AnimeCoverKeyer import eu.kanade.tachiyomi.data.coil.AnimeKeyer -import eu.kanade.tachiyomi.data.coil.DomainAnimeKeyer -import eu.kanade.tachiyomi.data.coil.DomainMangaKeyer import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.MangaKeyer @@ -147,16 +145,12 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { add(GifDecoder.Factory()) } add(TachiyomiImageDecoder.Factory()) - add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit))) - add(AnimeCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit))) - add(AnimeCoverFetcher.DomainAnimeFactory(lazy(callFactoryInit), lazy(diskCacheInit))) + add(MangaCoverFetcher.MangaFactory(lazy(callFactoryInit), lazy(diskCacheInit))) + add(AnimeCoverFetcher.AnimeFactory(lazy(callFactoryInit), lazy(diskCacheInit))) add(AnimeCoverFetcher.AnimeCoverFactory(lazy(callFactoryInit), lazy(diskCacheInit))) add(AnimeKeyer()) - add(DomainAnimeKeyer()) - add(MangaCoverFetcher.DomainMangaFactory(lazy(callFactoryInit), lazy(diskCacheInit))) add(MangaCoverFetcher.MangaCoverFactory(lazy(callFactoryInit), lazy(diskCacheInit))) add(MangaKeyer()) - add(DomainMangaKeyer()) add(AnimeCoverKeyer()) add(MangaCoverKeyer()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 84c15f1781..fcebf23f03 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -10,10 +10,7 @@ import data.History import data.Mangas import dataanime.Animehistory import dataanime.Animes -import eu.kanade.domain.backup.service.BackupPreferences import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.download.service.DownloadPreferences -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore import eu.kanade.domain.track.manga.store.DelayedMangaTrackingStore @@ -38,8 +35,8 @@ import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager -import eu.kanade.tachiyomi.source.manga.MangaSourceManager +import eu.kanade.tachiyomi.source.anime.AndroidAnimeSourceManager +import eu.kanade.tachiyomi.source.manga.AndroidMangaSourceManager import eu.kanade.tachiyomi.ui.player.ExternalIntents import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences @@ -61,14 +58,15 @@ import tachiyomi.data.handlers.manga.AndroidMangaDatabaseHandler import tachiyomi.data.handlers.manga.MangaDatabaseHandler import tachiyomi.data.listOfStringsAdapter import tachiyomi.data.updateStrategyAdapter +import tachiyomi.domain.backup.service.BackupPreferences +import tachiyomi.domain.download.service.DownloadPreferences +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.mi.data.AnimeDatabase -import tachiyomi.source.local.image.anime.AndroidLocalAnimeCoverManager import tachiyomi.source.local.image.anime.LocalAnimeCoverManager -import tachiyomi.source.local.image.manga.AndroidLocalMangaCoverManager import tachiyomi.source.local.image.manga.LocalMangaCoverManager -import tachiyomi.source.local.io.anime.AndroidLocalAnimeSourceFileSystem import tachiyomi.source.local.io.anime.LocalAnimeSourceFileSystem -import tachiyomi.source.local.io.manga.AndroidLocalMangaSourceFileSystem import tachiyomi.source.local.io.manga.LocalMangaSourceFileSystem import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar @@ -186,8 +184,8 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { NetworkHelper(app, get()) } addSingletonFactory { JavaScriptEngine(app) } - addSingletonFactory { MangaSourceManager(app, get(), get()) } - addSingletonFactory { AnimeSourceManager(app, get(), get()) } + addSingletonFactory { AndroidMangaSourceManager(app, get(), get()) } + addSingletonFactory { AndroidAnimeSourceManager(app, get(), get()) } addSingletonFactory { MangaExtensionManager(app) } addSingletonFactory { AnimeExtensionManager(app) } @@ -206,11 +204,11 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { ImageSaver(app) } - addSingletonFactory { AndroidLocalMangaSourceFileSystem(app) } - addSingletonFactory { AndroidLocalMangaCoverManager(app, get()) } + addSingletonFactory { LocalMangaSourceFileSystem(app) } + addSingletonFactory { LocalMangaCoverManager(app, get()) } - addSingletonFactory { AndroidLocalAnimeSourceFileSystem(app) } - addSingletonFactory { AndroidLocalAnimeCoverManager(app, get()) } + addSingletonFactory { LocalAnimeSourceFileSystem(app) } + addSingletonFactory { LocalAnimeCoverManager(app, get()) } addSingletonFactory { ExternalIntents() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 0721156d63..70b61ae093 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -4,16 +4,13 @@ import android.content.Context import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.work.WorkManager -import eu.kanade.domain.backup.service.BackupPreferences import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.network.NetworkPreferences @@ -27,7 +24,10 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.toast import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.getEnum +import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.entries.TriStateFilter +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index 3db18a29da..13d63cb9fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -14,11 +14,11 @@ import androidx.work.WorkManager import androidx.work.WorkerParameters import androidx.work.workDataOf import com.hippo.unifile.UniFile -import eu.kanade.domain.backup.service.BackupPreferences import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.notificationManager import logcat.LogPriority import tachiyomi.core.util.system.logcat +import tachiyomi.domain.backup.service.BackupPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt index 14f0cb356d..6eecdfdf9b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt @@ -4,9 +4,9 @@ import android.content.Context import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.util.BackupUtil +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 97ba3f8947..dc172cae7c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt @@ -11,8 +11,8 @@ import data.Manga_sync import data.Mangas import dataanime.Anime_sync import dataanime.Animes -import eu.kanade.domain.backup.service.BackupPreferences -import eu.kanade.domain.library.service.LibraryPreferences +import eu.kanade.domain.items.chapter.model.copyFrom +import eu.kanade.domain.items.episode.model.copyFrom import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_CATEGORY_MASK @@ -51,18 +51,10 @@ import eu.kanade.tachiyomi.data.backup.models.backupCategoryMapper import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper import eu.kanade.tachiyomi.data.backup.models.backupEpisodeMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper -import eu.kanade.tachiyomi.data.database.models.anime.Anime -import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack -import eu.kanade.tachiyomi.data.database.models.anime.Episode -import eu.kanade.tachiyomi.data.database.models.manga.Chapter -import eu.kanade.tachiyomi.data.database.models.manga.Manga -import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import eu.kanade.tachiyomi.source.anime.getPreferenceKey import eu.kanade.tachiyomi.source.anime.model.copyFrom -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.manga.getPreferenceKey import eu.kanade.tachiyomi.source.manga.model.copyFrom import eu.kanade.tachiyomi.util.system.hasPermission @@ -76,21 +68,25 @@ import tachiyomi.core.util.system.logcat import tachiyomi.data.handlers.anime.AnimeDatabaseHandler import tachiyomi.data.handlers.manga.MangaDatabaseHandler import tachiyomi.data.updateStrategyAdapter +import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.category.model.Category import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites +import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.manga.interactor.GetMangaFavorites +import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.history.anime.model.AnimeHistoryUpdate import tachiyomi.domain.history.manga.model.MangaHistoryUpdate +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File import java.io.FileOutputStream import java.util.Date import kotlin.math.max -import tachiyomi.domain.entries.anime.model.Anime as DomainAnime -import tachiyomi.domain.entries.manga.model.Manga as DomainManga class BackupManager( private val context: Context, @@ -191,22 +187,22 @@ class BackupManager( } } - private fun backupExtensionInfo(mangas: List): List { + private fun backupExtensionInfo(mangas: List): List { return mangas .asSequence() - .map { it.source } + .map(Manga::source) .distinct() - .map { mangaSourceManager.getOrStub(it) } + .map(mangaSourceManager::getOrStub) .map { BackupSource.copyFrom(it) } .toList() } - private fun backupAnimeExtensionInfo(animes: List): List { + private fun backupAnimeExtensionInfo(animes: List): List { return animes .asSequence() - .map { it.source } + .map(Anime::source) .distinct() - .map { animeSourceManager.getOrStub(it) } + .map(animeSourceManager::getOrStub) .map { BackupAnimeSource.copyFrom(it) } .toList() } @@ -243,13 +239,13 @@ class BackupManager( } } - private suspend fun backupMangas(mangas: List, flags: Int): List { + private suspend fun backupMangas(mangas: List, flags: Int): List { return mangas.map { backupManga(it, flags) } } - private suspend fun backupAnimes(animes: List, flags: Int): List { + private suspend fun backupAnimes(animes: List, flags: Int): List { return animes.map { backupAnime(it, flags) } @@ -262,7 +258,7 @@ class BackupManager( * @param options options for the backup * @return [BackupManga] containing manga in a serializable form */ - private suspend fun backupManga(manga: DomainManga, options: Int): BackupManga { + private suspend fun backupManga(manga: Manga, options: Int): BackupManga { // Entry for this manga val mangaObject = BackupManga.copyFrom(manga) @@ -316,7 +312,7 @@ class BackupManager( * @param options options for the backup * @return [BackupAnime] containing anime in a serializable form */ - private suspend fun backupAnime(anime: DomainAnime, options: Int): BackupAnime { + private suspend fun backupAnime(anime: Anime, options: Int): BackupAnime { // Entry for this anime val animeObject = BackupAnime.copyFrom(anime) @@ -449,10 +445,11 @@ class BackupManager( return installedExtensions } - internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas) { - manga.id = dbManga._id - manga.copyFrom(dbManga) + internal suspend fun restoreExistingManga(manga: Manga, dbManga: Mangas): Manga { + var manga = manga.copy(id = dbManga._id) + manga = manga.copyFrom(dbManga) updateManga(manga) + return manga } /** @@ -462,16 +459,17 @@ class BackupManager( * @return Updated manga info. */ internal suspend fun restoreNewManga(manga: Manga): Manga { - return manga.also { - it.initialized = it.description != null - it.id = insertManga(it) - } + return manga.copy( + initialized = manga.description != null, + id = insertManga(manga), + ) } - internal suspend fun restoreExistingAnime(anime: Anime, dbAnime: Animes) { - anime.id = dbAnime._id - anime.copyFrom(dbAnime) + internal suspend fun restoreExistingAnime(anime: Anime, dbAnime: Animes): Anime { + var anime = anime.copy(id = dbAnime._id) + anime = anime.copyFrom(dbAnime) updateAnime(anime) + return anime } /** @@ -481,10 +479,10 @@ class BackupManager( * @return Updated anime info. */ internal suspend fun restoreNewAnime(anime: Anime): Anime { - return anime.also { - it.initialized = it.description != null - it.id = insertAnime(it) - } + return anime.copy( + initialized = anime.description != null, + id = insertAnime(anime), + ) } /** @@ -731,28 +729,28 @@ class BackupManager( * @param manga the manga whose sync have to be restored. * @param tracks the track list to restore. */ - internal suspend fun restoreTracking(manga: Manga, tracks: List) { + internal suspend fun restoreTracking(manga: Manga, tracks: List) { // Fix foreign keys with the current manga id - tracks.map { it.manga_id = manga.id!! } + val tracks = tracks.map { it.copy(mangaId = manga.id!!) } // Get tracks from database val dbTracks = mangaHandler.awaitList { manga_syncQueries.getTracksByMangaId(manga.id!!) } val toUpdate = mutableListOf() - val toInsert = mutableListOf() + val toInsert = mutableListOf() tracks.forEach { track -> var isInDatabase = false for (dbTrack in dbTracks) { - if (track.sync_id == dbTrack.sync_id.toInt()) { + if (track.syncId == dbTrack.sync_id) { // The sync is already in the db, only update its fields var temp = dbTrack - if (track.media_id != dbTrack.remote_id) { - temp = temp.copy(remote_id = track.media_id) + if (track.remoteId != dbTrack.remote_id) { + temp = temp.copy(remote_id = track.remoteId) } - if (track.library_id != dbTrack.library_id) { - temp = temp.copy(library_id = track.library_id) + if (track.libraryId != dbTrack.library_id) { + temp = temp.copy(library_id = track.libraryId) } - temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read.toDouble())) + temp = temp.copy(last_chapter_read = max(dbTrack.last_chapter_read, track.lastChapterRead)) isInDatabase = true toUpdate.add(temp) break @@ -760,8 +758,7 @@ class BackupManager( } if (!isInDatabase) { // Insert new sync. Let the db assign the id - track.id = null - toInsert.add(track) + toInsert.add(track.copy(id = 0)) } } // Update database @@ -790,18 +787,18 @@ class BackupManager( mangaHandler.await(true) { toInsert.forEach { track -> manga_syncQueries.insert( - track.manga_id, - track.sync_id.toLong(), - track.media_id, - track.library_id, + track.mangaId, + track.syncId, + track.remoteId, + track.libraryId, track.title, - track.last_chapter_read.toDouble(), - track.total_chapters.toLong(), + track.lastChapterRead, + track.totalChapters, track.status.toLong(), track.score, - track.tracking_url, - track.started_reading_date, - track.finished_reading_date, + track.remoteUrl, + track.startDate, + track.finishDate, ) } } @@ -814,28 +811,28 @@ class BackupManager( * @param anime the anime whose sync have to be restored. * @param tracks the track list to restore. */ - internal suspend fun restoreAnimeTracking(anime: Anime, tracks: List) { + internal suspend fun restoreAnimeTracking(anime: Anime, tracks: List) { // Fix foreign keys with the current anime id - tracks.map { it.anime_id = anime.id!! } + val tracks = tracks.map { it.copy(animeId = anime.id!!) } // Get tracks from database val dbTracks = animeHandler.awaitList { anime_syncQueries.getTracksByAnimeId(anime.id!!) } val toUpdate = mutableListOf() - val toInsert = mutableListOf() + val toInsert = mutableListOf() tracks.forEach { track -> var isInDatabase = false for (dbTrack in dbTracks) { - if (track.sync_id == dbTrack.sync_id.toInt()) { + if (track.syncId == dbTrack.sync_id) { // The sync is already in the db, only update its fields var temp = dbTrack - if (track.media_id != dbTrack.remote_id) { - temp = temp.copy(remote_id = track.media_id) + if (track.remoteId != dbTrack.remote_id) { + temp = temp.copy(remote_id = track.remoteId) } - if (track.library_id != dbTrack.library_id) { - temp = temp.copy(library_id = track.library_id) + if (track.libraryId != dbTrack.library_id) { + temp = temp.copy(library_id = track.libraryId) } - temp = temp.copy(last_episode_seen = max(dbTrack.last_episode_seen, track.last_episode_seen.toDouble())) + temp = temp.copy(last_episode_seen = max(dbTrack.last_episode_seen, track.lastEpisodeSeen)) isInDatabase = true toUpdate.add(temp) break @@ -843,8 +840,7 @@ class BackupManager( } if (!isInDatabase) { // Insert new sync. Let the db assign the id - track.id = null - toInsert.add(track) + toInsert.add(track.copy(id = 0)) } } // Update database @@ -873,74 +869,74 @@ class BackupManager( animeHandler.await(true) { toInsert.forEach { track -> anime_syncQueries.insert( - track.anime_id, - track.sync_id.toLong(), - track.media_id, - track.library_id, + track.animeId, + track.syncId, + track.remoteId, + track.libraryId, track.title, - track.last_episode_seen.toDouble(), - track.total_episodes.toLong(), + track.lastEpisodeSeen, + track.totalEpisodes, track.status.toLong(), track.score, - track.tracking_url, - track.started_watching_date, - track.finished_watching_date, + track.remoteUrl, + track.startDate, + track.finishDate, ) } } } } - internal suspend fun restoreChapters(manga: Manga, chapters: List) { + internal suspend fun restoreChapters(manga: Manga, chapters: List) { val dbChapters = mangaHandler.awaitList { chaptersQueries.getChaptersByMangaId(manga.id!!) } - chapters.forEach { chapter -> + val processed = chapters.map { chapter -> + var chapter = chapter val dbChapter = dbChapters.find { it.url == chapter.url } if (dbChapter != null) { - chapter.id = dbChapter._id - chapter.copyFrom(dbChapter) + chapter = chapter.copy(id = dbChapter._id) + chapter = chapter.copyFrom(dbChapter) if (dbChapter.read && !chapter.read) { - chapter.read = dbChapter.read - chapter.last_page_read = dbChapter.last_page_read.toInt() - } else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0L) { - chapter.last_page_read = dbChapter.last_page_read.toInt() + chapter = chapter.copy(read = dbChapter.read, lastPageRead = dbChapter.last_page_read) + } else if (chapter.lastPageRead == 0L && dbChapter.last_page_read != 0L) { + chapter = chapter.copy(lastPageRead = dbChapter.last_page_read) } if (!chapter.bookmark && dbChapter.bookmark) { - chapter.bookmark = dbChapter.bookmark + chapter = chapter.copy(bookmark = dbChapter.bookmark) } } - chapter.manga_id = manga.id + chapter.copy(mangaId = manga.id ?: -1) } - val newChapters = chapters.groupBy { it.id != null } + val newChapters = processed.groupBy { it.id > 0 } newChapters[true]?.let { updateKnownChapters(it) } newChapters[false]?.let { insertChapters(it) } } - internal suspend fun restoreEpisodes(anime: Anime, episodes: List) { + internal suspend fun restoreEpisodes(anime: Anime, episodes: List) { val dbEpisodes = animeHandler.awaitList { episodesQueries.getEpisodesByAnimeId(anime.id!!) } - episodes.forEach { episode -> + val processed = episodes.map { episode -> + var episode = episode val dbEpisode = dbEpisodes.find { it.url == episode.url } if (dbEpisode != null) { - episode.id = dbEpisode._id - episode.copyFrom(dbEpisode) + episode = episode.copy(id = dbEpisode._id) + episode = episode.copyFrom(dbEpisode) if (dbEpisode.seen && !episode.seen) { - episode.seen = dbEpisode.seen - episode.last_second_seen = dbEpisode.last_second_seen - } else if (episode.last_second_seen == 0L && dbEpisode.last_second_seen != 0L) { - episode.last_second_seen = dbEpisode.last_second_seen + episode = episode.copy(seen = dbEpisode.seen, lastSecondSeen = dbEpisode.last_second_seen) + } else if (episode.lastSecondSeen == 0L && dbEpisode.last_second_seen != 0L) { + episode = episode.copy(lastSecondSeen = dbEpisode.last_second_seen) } if (!episode.bookmark && dbEpisode.bookmark) { - episode.bookmark = dbEpisode.bookmark + episode = episode.copy(bookmark = dbEpisode.bookmark) } } - episode.anime_id = anime.id + episode.copy(animeId = anime.id ?: -1) } - val newEpisodes = episodes.groupBy { it.id != null } + val newEpisodes = processed.groupBy { it.id > 0 } newEpisodes[true]?.let { updateKnownEpisodes(it) } newEpisodes[false]?.let { insertEpisodes(it) } } @@ -976,19 +972,19 @@ class BackupManager( artist = manga.artist, author = manga.author, description = manga.description, - genre = manga.getGenres(), + genre = manga.genre, title = manga.title, status = manga.status.toLong(), - thumbnailUrl = manga.thumbnail_url, + thumbnailUrl = manga.thumbnailUrl, favorite = manga.favorite, - lastUpdate = manga.last_update, + lastUpdate = manga.lastUpdate, nextUpdate = 0L, initialized = manga.initialized, - viewerFlags = manga.viewer_flags.toLong(), - chapterFlags = manga.chapter_flags.toLong(), - coverLastModified = manga.cover_last_modified, - dateAdded = manga.date_added, - updateStrategy = manga.update_strategy, + viewerFlags = manga.viewerFlags, + chapterFlags = manga.chapterFlags, + coverLastModified = manga.coverLastModified, + dateAdded = manga.dateAdded, + updateStrategy = manga.updateStrategy, ) mangasQueries.selectLastInsertedRowId() } @@ -1007,19 +1003,19 @@ class BackupManager( artist = anime.artist, author = anime.author, description = anime.description, - genre = anime.getGenres(), + genre = anime.genre, title = anime.title, status = anime.status.toLong(), - thumbnailUrl = anime.thumbnail_url, + thumbnailUrl = anime.thumbnailUrl, favorite = anime.favorite, - lastUpdate = anime.last_update, + lastUpdate = anime.lastUpdate, nextUpdate = 0L, initialized = anime.initialized, - viewerFlags = anime.viewer_flags.toLong(), - episodeFlags = anime.episode_flags.toLong(), - coverLastModified = anime.cover_last_modified, - dateAdded = anime.date_added, - updateStrategy = anime.update_strategy, + viewerFlags = anime.viewerFlags, + episodeFlags = anime.episodeFlags, + coverLastModified = anime.coverLastModified, + dateAdded = anime.dateAdded, + updateStrategy = anime.updateStrategy, ) animesQueries.selectLastInsertedRowId() } @@ -1033,22 +1029,22 @@ class BackupManager( artist = manga.artist, author = manga.author, description = manga.description, - genre = manga.genre, + genre = manga.genre?.joinToString(separator = ", "), title = manga.title, - status = manga.status.toLong(), - thumbnailUrl = manga.thumbnail_url, + status = manga.status, + thumbnailUrl = manga.thumbnailUrl, favorite = manga.favorite.toLong(), - lastUpdate = manga.last_update, + lastUpdate = manga.lastUpdate, initialized = manga.initialized.toLong(), - viewer = manga.viewer_flags.toLong(), - chapterFlags = manga.chapter_flags.toLong(), - coverLastModified = manga.cover_last_modified, - dateAdded = manga.date_added, + viewer = manga.viewerFlags, + chapterFlags = manga.chapterFlags, + coverLastModified = manga.coverLastModified, + dateAdded = manga.dateAdded, mangaId = manga.id!!, - updateStrategy = manga.update_strategy.let(updateStrategyAdapter::encode), + updateStrategy = manga.updateStrategy.let(updateStrategyAdapter::encode), ) } - return manga.id!! + return manga.id } private suspend fun updateAnime(anime: Anime): Long { @@ -1059,42 +1055,42 @@ class BackupManager( artist = anime.artist, author = anime.author, description = anime.description, - genre = anime.genre, + genre = anime.genre?.joinToString(separator = ", "), title = anime.title, - status = anime.status.toLong(), - thumbnailUrl = anime.thumbnail_url, + status = anime.status, + thumbnailUrl = anime.thumbnailUrl, favorite = anime.favorite.toLong(), - lastUpdate = anime.last_update, + lastUpdate = anime.lastUpdate, initialized = anime.initialized.toLong(), - viewer = anime.viewer_flags.toLong(), - episodeFlags = anime.episode_flags.toLong(), - coverLastModified = anime.cover_last_modified, - dateAdded = anime.date_added, + viewer = anime.viewerFlags, + episodeFlags = anime.episodeFlags, + coverLastModified = anime.coverLastModified, + dateAdded = anime.dateAdded, animeId = anime.id!!, - updateStrategy = anime.update_strategy.let(updateStrategyAdapter::encode), + updateStrategy = anime.updateStrategy.let(updateStrategyAdapter::encode), ) } - return anime.id!! + return anime.id } /** * Inserts list of chapters */ - private suspend fun insertChapters(chapters: List) { + private suspend fun insertChapters(chapters: List) { mangaHandler.await(true) { chapters.forEach { chapter -> chaptersQueries.insert( - chapter.manga_id!!, + chapter.mangaId, chapter.url, chapter.name, chapter.scanlator, chapter.read, chapter.bookmark, - chapter.last_page_read.toLong(), - chapter.chapter_number, - chapter.source_order.toLong(), - chapter.date_fetch, - chapter.date_upload, + chapter.lastPageRead, + chapter.chapterNumber, + chapter.sourceOrder, + chapter.dateFetch, + chapter.dateUpload, ) } } @@ -1103,22 +1099,22 @@ class BackupManager( /** * Inserts list of episodes */ - private suspend fun insertEpisodes(episodes: List) { + private suspend fun insertEpisodes(episodes: List) { animeHandler.await(true) { episodes.forEach { episode -> episodesQueries.insert( - episode.anime_id!!, + episode.animeId, episode.url, episode.name, episode.scanlator, episode.seen, episode.bookmark, - episode.last_second_seen, - episode.total_seconds, - episode.episode_number, - episode.source_order.toLong(), - episode.date_fetch, - episode.date_upload, + episode.lastSecondSeen, + episode.totalSeconds, + episode.episodeNumber, + episode.sourceOrder, + episode.dateFetch, + episode.dateUpload, ) } } @@ -1127,22 +1123,22 @@ class BackupManager( /** * Updates a list of chapters */ - private suspend fun updateChapters(chapters: List) { + private suspend fun updateChapters(chapters: List) { mangaHandler.await(true) { chapters.forEach { chapter -> chaptersQueries.update( - chapter.manga_id!!, + chapter.mangaId, chapter.url, chapter.name, chapter.scanlator, chapter.read.toLong(), chapter.bookmark.toLong(), - chapter.last_page_read.toLong(), - chapter.chapter_number.toDouble(), - chapter.source_order.toLong(), - chapter.date_fetch, - chapter.date_upload, - chapter.id!!, + chapter.lastPageRead, + chapter.chapterNumber.toDouble(), + chapter.sourceOrder, + chapter.dateFetch, + chapter.dateUpload, + chapter.id, ) } } @@ -1151,23 +1147,23 @@ class BackupManager( /** * Updates a list of episodes */ - private suspend fun updateEpisodes(episodes: List) { + private suspend fun updateEpisodes(episodes: List) { animeHandler.await(true) { episodes.forEach { episode -> episodesQueries.update( - episode.anime_id!!, + episode.animeId, episode.url, episode.name, episode.scanlator, episode.seen.toLong(), episode.bookmark.toLong(), - episode.last_second_seen, - episode.total_seconds, - episode.episode_number.toDouble(), - episode.source_order.toLong(), - episode.date_fetch, - episode.date_upload, - episode.id!!, + episode.lastSecondSeen, + episode.totalSeconds, + episode.episodeNumber.toDouble(), + episode.sourceOrder, + episode.dateFetch, + episode.dateUpload, + episode.id, ) } } @@ -1176,7 +1172,7 @@ class BackupManager( /** * Updates a list of chapters with known database ids */ - private suspend fun updateKnownChapters(chapters: List) { + private suspend fun updateKnownChapters(chapters: List) { mangaHandler.await(true) { chapters.forEach { chapter -> chaptersQueries.update( @@ -1186,12 +1182,12 @@ class BackupManager( scanlator = null, read = chapter.read.toLong(), bookmark = chapter.bookmark.toLong(), - lastPageRead = chapter.last_page_read.toLong(), + lastPageRead = chapter.lastPageRead, chapterNumber = null, sourceOrder = null, dateFetch = null, dateUpload = null, - chapterId = chapter.id!!, + chapterId = chapter.id, ) } } @@ -1200,7 +1196,7 @@ class BackupManager( /** * Updates a list of episodes with known database ids */ - private suspend fun updateKnownEpisodes(episodes: List) { + private suspend fun updateKnownEpisodes(episodes: List) { animeHandler.await(true) { episodes.forEach { episode -> episodesQueries.update( @@ -1210,13 +1206,13 @@ class BackupManager( scanlator = null, seen = episode.seen.toLong(), bookmark = episode.bookmark.toLong(), - lastSecondSeen = episode.last_second_seen, - totalSeconds = episode.total_seconds, + lastSecondSeen = episode.lastSecondSeen, + totalSeconds = episode.totalSeconds, episodeNumber = null, sourceOrder = null, dateFetch = null, dateUpload = null, - episodeId = episode.id!!, + episodeId = episode.id, ) } } 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 c0015c9e3e..e5e3efe2a2 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 @@ -22,17 +22,17 @@ import eu.kanade.tachiyomi.data.backup.models.IntPreferenceValue import eu.kanade.tachiyomi.data.backup.models.LongPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringPreferenceValue import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue -import eu.kanade.tachiyomi.data.database.models.anime.Anime -import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack -import eu.kanade.tachiyomi.data.database.models.anime.Episode -import eu.kanade.tachiyomi.data.database.models.manga.Chapter -import eu.kanade.tachiyomi.data.database.models.manga.Manga -import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.createFileInCacheDir import kotlinx.coroutines.Job import tachiyomi.core.util.system.logcat +import tachiyomi.domain.entries.anime.model.Anime +import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.track.anime.model.AnimeTrack +import tachiyomi.domain.track.manga.model.MangaTrack import java.io.File import java.text.SimpleDateFormat import java.util.Date @@ -184,7 +184,7 @@ class BackupRestorer( } else { // Manga in database // Copy information from manga already in database - backupManager.restoreExistingManga(manga, dbManga) + val manga = backupManager.restoreExistingManga(manga, dbManga) // Fetch rest of manga information restoreNewManga(manga, chapters, categories, history, tracks, backupCategories) } @@ -213,8 +213,6 @@ class BackupRestorer( backupCategories: List, ) { val fetchedManga = backupManager.restoreNewManga(manga) - fetchedManga.id ?: return - backupManager.restoreChapters(fetchedManga, chapters) restoreExtras(fetchedManga, categories, history, tracks, backupCategories) } @@ -253,7 +251,7 @@ class BackupRestorer( } else { // Anime in database // Copy information from anime already in database - backupManager.restoreExistingAnime(anime, dbAnime) + val anime = backupManager.restoreExistingAnime(anime, dbAnime) // Fetch rest of anime information restoreNewAnime(anime, episodes, categories, history, tracks, backupCategories) } @@ -282,8 +280,6 @@ class BackupRestorer( backupCategories: List, ) { val fetchedAnime = backupManager.restoreNewAnime(anime) - fetchedAnime.id ?: return - backupManager.restoreEpisodes(fetchedAnime, episodes) restoreExtras(fetchedAnime, categories, history, tracks, backupCategories) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt index 9623062ee2..56be1ebcc6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt @@ -1,12 +1,11 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.anime.AnimeImpl -import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrackImpl -import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import tachiyomi.domain.entries.anime.model.Anime +import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.track.anime.model.AnimeTrack @Suppress("DEPRECATION") @Serializable @@ -39,32 +38,32 @@ data class BackupAnime( @ProtoNumber(104) var history: List = emptyList(), @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, ) { - fun getAnimeImpl(): AnimeImpl { - return AnimeImpl().apply { - url = this@BackupAnime.url - title = this@BackupAnime.title - artist = this@BackupAnime.artist - author = this@BackupAnime.author - description = this@BackupAnime.description - genre = this@BackupAnime.genre.joinToString() - status = this@BackupAnime.status - thumbnail_url = this@BackupAnime.thumbnailUrl - favorite = this@BackupAnime.favorite - source = this@BackupAnime.source - date_added = this@BackupAnime.dateAdded - viewer_flags = this@BackupAnime.viewer_flags - episode_flags = this@BackupAnime.episodeFlags - update_strategy = this@BackupAnime.updateStrategy - } + fun getAnimeImpl(): Anime { + return Anime.create().copy( + url = this@BackupAnime.url, + title = this@BackupAnime.title, + artist = this@BackupAnime.artist, + author = this@BackupAnime.author, + description = this@BackupAnime.description, + genre = this@BackupAnime.genre, + status = this@BackupAnime.status.toLong(), + thumbnailUrl = this@BackupAnime.thumbnailUrl, + favorite = this@BackupAnime.favorite, + source = this@BackupAnime.source, + dateAdded = this@BackupAnime.dateAdded, + viewerFlags = this@BackupAnime.viewer_flags.toLong(), + episodeFlags = this@BackupAnime.episodeFlags.toLong(), + updateStrategy = this@BackupAnime.updateStrategy, + ) } - fun getEpisodesImpl(): List { + fun getEpisodesImpl(): List { return episodes.map { it.toEpisodeImpl() } } - fun getTrackingImpl(): List { + fun getTrackingImpl(): List { return tracking.map { it.getTrackingImpl() } 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 98e17de6b4..050631691a 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 @@ -1,9 +1,8 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack -import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrackImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber +import tachiyomi.domain.track.anime.model.AnimeTrack @Serializable data class BackupAnimeTracking( @@ -29,43 +28,44 @@ data class BackupAnimeTracking( @ProtoNumber(11) var finishedWatchingDate: Long = 0, @ProtoNumber(100) var mediaId: Long = 0, ) { - fun getTrackingImpl(): AnimeTrackImpl { - return AnimeTrackImpl().apply { - sync_id = this@BackupAnimeTracking.syncId - @Suppress("DEPRECATION") - media_id = if (this@BackupAnimeTracking.mediaIdInt != 0) { + fun getTrackingImpl(): AnimeTrack { + return AnimeTrack( + id = -1, + animeId = -1, + syncId = this@BackupAnimeTracking.syncId.toLong(), + remoteId = if (this@BackupAnimeTracking.mediaIdInt != 0) { this@BackupAnimeTracking.mediaIdInt.toLong() } else { this@BackupAnimeTracking.mediaId - } - library_id = this@BackupAnimeTracking.libraryId - title = this@BackupAnimeTracking.title - last_episode_seen = this@BackupAnimeTracking.lastEpisodeSeen - total_episodes = this@BackupAnimeTracking.totalEpisodes - score = this@BackupAnimeTracking.score - status = this@BackupAnimeTracking.status - started_watching_date = this@BackupAnimeTracking.startedWatchingDate - finished_watching_date = this@BackupAnimeTracking.finishedWatchingDate - tracking_url = this@BackupAnimeTracking.trackingUrl - } + }, + libraryId = this@BackupAnimeTracking.libraryId, + title = this@BackupAnimeTracking.title, + lastEpisodeSeen = this@BackupAnimeTracking.lastEpisodeSeen.toDouble(), + totalEpisodes = this@BackupAnimeTracking.totalEpisodes.toLong(), + score = this@BackupAnimeTracking.score, + status = this@BackupAnimeTracking.status.toLong(), + startDate = this@BackupAnimeTracking.startedWatchingDate, + finishDate = this@BackupAnimeTracking.finishedWatchingDate, + remoteUrl = this@BackupAnimeTracking.trackingUrl, + ) } companion object { fun copyFrom(track: AnimeTrack): BackupAnimeTracking { return BackupAnimeTracking( - syncId = track.sync_id, - mediaId = track.media_id, + syncId = track.syncId.toInt(), + mediaId = track.remoteId, // forced not null so its compatible with 1.x backup system - libraryId = track.library_id!!, + libraryId = track.libraryId!!, title = track.title, // convert to float for 1.x - lastEpisodeSeen = track.last_episode_seen, - totalEpisodes = track.total_episodes, + lastEpisodeSeen = track.lastEpisodeSeen.toFloat(), + totalEpisodes = track.totalEpisodes.toInt(), score = track.score, - status = track.status, - startedWatchingDate = track.started_watching_date, - finishedWatchingDate = track.finished_watching_date, - trackingUrl = track.tracking_url, + status = track.status.toInt(), + startedWatchingDate = track.startDate, + finishedWatchingDate = track.finishDate, + trackingUrl = track.remoteUrl, ) } } 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 9430d69861..4728130192 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 @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber +import tachiyomi.domain.items.chapter.model.Chapter @Serializable data class BackupChapter( @@ -21,19 +21,19 @@ data class BackupChapter( @ProtoNumber(9) var chapterNumber: Float = 0F, @ProtoNumber(10) var sourceOrder: Long = 0, ) { - fun toChapterImpl(): ChapterImpl { - return ChapterImpl().apply { - url = this@BackupChapter.url - name = this@BackupChapter.name - chapter_number = this@BackupChapter.chapterNumber - scanlator = this@BackupChapter.scanlator - read = this@BackupChapter.read - bookmark = this@BackupChapter.bookmark - last_page_read = this@BackupChapter.lastPageRead.toInt() - date_fetch = this@BackupChapter.dateFetch - date_upload = this@BackupChapter.dateUpload - source_order = this@BackupChapter.sourceOrder.toInt() - } + fun toChapterImpl(): Chapter { + return Chapter.create().copy( + url = this@BackupChapter.url, + name = this@BackupChapter.name, + chapterNumber = this@BackupChapter.chapterNumber, + scanlator = this@BackupChapter.scanlator, + read = this@BackupChapter.read, + bookmark = this@BackupChapter.bookmark, + lastPageRead = this@BackupChapter.lastPageRead, + dateFetch = this@BackupChapter.dateFetch, + dateUpload = this@BackupChapter.dateUpload, + sourceOrder = this@BackupChapter.sourceOrder, + ) } } 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 a158a45d65..e534a245d9 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 @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber +import tachiyomi.domain.items.episode.model.Episode @Serializable data class BackupEpisode( @@ -22,20 +22,20 @@ data class BackupEpisode( @ProtoNumber(9) var episodeNumber: Float = 0F, @ProtoNumber(10) var sourceOrder: Long = 0, ) { - fun toEpisodeImpl(): EpisodeImpl { - return EpisodeImpl().apply { - url = this@BackupEpisode.url - name = this@BackupEpisode.name - episode_number = this@BackupEpisode.episodeNumber - scanlator = this@BackupEpisode.scanlator - seen = this@BackupEpisode.seen - bookmark = this@BackupEpisode.bookmark - last_second_seen = this@BackupEpisode.lastSecondSeen - total_seconds = this@BackupEpisode.totalSeconds - date_fetch = this@BackupEpisode.dateFetch - date_upload = this@BackupEpisode.dateUpload - source_order = this@BackupEpisode.sourceOrder.toInt() - } + fun toEpisodeImpl(): Episode { + return Episode.create().copy( + url = this@BackupEpisode.url, + name = this@BackupEpisode.name, + episodeNumber = this@BackupEpisode.episodeNumber, + scanlator = this@BackupEpisode.scanlator, + seen = this@BackupEpisode.seen, + bookmark = this@BackupEpisode.bookmark, + lastSecondSeen = this@BackupEpisode.lastSecondSeen, + totalSeconds = this@BackupEpisode.totalSeconds, + dateFetch = this@BackupEpisode.dateFetch, + dateUpload = this@BackupEpisode.dateUpload, + sourceOrder = this@BackupEpisode.sourceOrder, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 19966f70aa..c896067c1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -1,13 +1,12 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl -import eu.kanade.tachiyomi.data.database.models.manga.MangaImpl -import eu.kanade.tachiyomi.data.database.models.manga.MangaTrackImpl import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.track.manga.model.MangaTrack @Suppress("DEPRECATION") @Serializable @@ -41,32 +40,32 @@ data class BackupManga( @ProtoNumber(104) var history: List = emptyList(), @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, ) { - fun getMangaImpl(): MangaImpl { - return MangaImpl().apply { - url = this@BackupManga.url - title = this@BackupManga.title - artist = this@BackupManga.artist - author = this@BackupManga.author - description = this@BackupManga.description - genre = this@BackupManga.genre.joinToString() - status = this@BackupManga.status - thumbnail_url = this@BackupManga.thumbnailUrl - favorite = this@BackupManga.favorite - source = this@BackupManga.source - date_added = this@BackupManga.dateAdded - viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer - chapter_flags = this@BackupManga.chapterFlags - update_strategy = this@BackupManga.updateStrategy - } + fun getMangaImpl(): Manga { + return Manga.create().copy( + url = this@BackupManga.url, + title = this@BackupManga.title, + artist = this@BackupManga.artist, + author = this@BackupManga.author, + description = this@BackupManga.description, + genre = this@BackupManga.genre, + status = this@BackupManga.status.toLong(), + thumbnailUrl = this@BackupManga.thumbnailUrl, + favorite = this@BackupManga.favorite, + source = this@BackupManga.source, + dateAdded = this@BackupManga.dateAdded, + viewerFlags = (this@BackupManga.viewer_flags ?: this@BackupManga.viewer).toLong(), + chapterFlags = this@BackupManga.chapterFlags.toLong(), + updateStrategy = this@BackupManga.updateStrategy, + ) } - fun getChaptersImpl(): List { + fun getChaptersImpl(): List { return chapters.map { it.toChapterImpl() } } - fun getTrackingImpl(): List { + fun getTrackingImpl(): List { return tracking.map { it.getTrackingImpl() } 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 36527ab4a4..28115d33ab 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 @@ -1,8 +1,8 @@ package eu.kanade.tachiyomi.data.backup.models -import eu.kanade.tachiyomi.data.database.models.manga.MangaTrackImpl import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber +import tachiyomi.domain.track.manga.model.MangaTrack @Serializable data class BackupTracking( @@ -29,25 +29,26 @@ data class BackupTracking( @ProtoNumber(100) var mediaId: Long = 0, ) { - fun getTrackingImpl(): MangaTrackImpl { - return MangaTrackImpl().apply { - sync_id = this@BackupTracking.syncId - @Suppress("DEPRECATION") - media_id = if (this@BackupTracking.mediaIdInt != 0) { + fun getTrackingImpl(): MangaTrack { + return MangaTrack( + id = -1, + mangaId = -1, + syncId = this@BackupTracking.syncId.toLong(), + remoteId = if (this@BackupTracking.mediaIdInt != 0) { this@BackupTracking.mediaIdInt.toLong() } else { this@BackupTracking.mediaId - } - library_id = this@BackupTracking.libraryId - title = this@BackupTracking.title - last_chapter_read = this@BackupTracking.lastChapterRead - total_chapters = this@BackupTracking.totalChapters - score = this@BackupTracking.score - status = this@BackupTracking.status - started_reading_date = this@BackupTracking.startedReadingDate - finished_reading_date = this@BackupTracking.finishedReadingDate - tracking_url = this@BackupTracking.trackingUrl - } + }, + libraryId = this@BackupTracking.libraryId, + title = this@BackupTracking.title, + lastChapterRead = this@BackupTracking.lastChapterRead.toDouble(), + totalChapters = this@BackupTracking.totalChapters.toLong(), + score = this@BackupTracking.score, + status = this@BackupTracking.status.toLong(), + startDate = this@BackupTracking.startedReadingDate, + finishDate = this@BackupTracking.finishedReadingDate, + remoteUrl = this@BackupTracking.trackingUrl, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt index 666a2e74ba..a43b5ea84a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt @@ -13,9 +13,7 @@ import coil.request.Parameters import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import eu.kanade.tachiyomi.data.coil.AnimeCoverFetcher.Companion.USE_CUSTOM_COVER -import eu.kanade.tachiyomi.data.database.models.anime.Anime import eu.kanade.tachiyomi.network.await -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import logcat.LogPriority import okhttp3.CacheControl import okhttp3.Call @@ -27,10 +25,11 @@ import okio.Source import okio.buffer import okio.sink import tachiyomi.core.util.system.logcat +import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.AnimeCover +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.injectLazy import java.io.File -import tachiyomi.domain.entries.anime.model.Anime as DomainAnime /** * A [Fetcher] that fetches cover image for [Anime] object. @@ -261,7 +260,7 @@ class AnimeCoverFetcher( File, URL } - class Factory( + class AnimeFactory( private val callFactoryLazy: Lazy, private val diskCacheLazy: Lazy, ) : Fetcher.Factory { @@ -270,36 +269,13 @@ class AnimeCoverFetcher( private val sourceManager: AnimeSourceManager by injectLazy() override fun create(data: Anime, options: Options, imageLoader: ImageLoader): Fetcher { - return AnimeCoverFetcher( - url = data.thumbnail_url, - isLibraryAnime = data.favorite, - options = options, - coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnail_url) }, - customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) }, - diskCacheKeyLazy = lazy { AnimeKeyer().key(data, options) }, - sourceLazy = lazy { sourceManager.get(data.source) as? AnimeHttpSource }, - callFactoryLazy = callFactoryLazy, - diskCacheLazy = diskCacheLazy, - ) - } - } - - class DomainAnimeFactory( - private val callFactoryLazy: Lazy, - private val diskCacheLazy: Lazy, - ) : Fetcher.Factory { - - private val coverCache: AnimeCoverCache by injectLazy() - private val sourceManager: AnimeSourceManager by injectLazy() - - override fun create(data: DomainAnime, options: Options, imageLoader: ImageLoader): Fetcher { return AnimeCoverFetcher( url = data.thumbnailUrl, isLibraryAnime = data.favorite, options = options, coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) }, customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) }, - diskCacheKeyLazy = lazy { DomainAnimeKeyer().key(data, options) }, + diskCacheKeyLazy = lazy { AnimeKeyer().key(data, options) }, sourceLazy = lazy { sourceManager.get(data.source) as? AnimeHttpSource }, callFactoryLazy = callFactoryLazy, diskCacheLazy = diskCacheLazy, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverKeyer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverKeyer.kt index dabb52b106..8f2e909742 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverKeyer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverKeyer.kt @@ -4,24 +4,12 @@ import coil.key.Keyer import coil.request.Options import eu.kanade.domain.entries.anime.model.hasCustomCover import eu.kanade.tachiyomi.data.cache.AnimeCoverCache -import eu.kanade.tachiyomi.data.database.models.anime.Anime -import eu.kanade.tachiyomi.data.database.models.anime.toDomainAnime import tachiyomi.domain.entries.anime.model.AnimeCover import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import tachiyomi.domain.entries.anime.model.Anime as DomainAnime -class AnimeKeyer : Keyer { - override fun key(data: Anime, options: Options): String { - return if (data.toDomainAnime()!!.hasCustomCover()) { - "anime;${data.id};${data.cover_last_modified}" - } else { - "anime;${data.thumbnail_url};${data.cover_last_modified}" - } - } -} - -class DomainAnimeKeyer : Keyer { +class AnimeKeyer : Keyer { override fun key(data: DomainAnime, options: Options): String { return if (data.hasCustomCover()) { "anime;${data.id};${data.coverLastModified}" @@ -31,9 +19,11 @@ class DomainAnimeKeyer : Keyer { } } -class AnimeCoverKeyer : Keyer { +class AnimeCoverKeyer( + private val coverCache: AnimeCoverCache = Injekt.get(), +) : Keyer { override fun key(data: AnimeCover, options: Options): String { - return if (Injekt.get().getCustomCoverFile(data.animeId).exists()) { + return if (coverCache.getCustomCoverFile(data.animeId).exists()) { "anime;${data.animeId};${data.lastModified}" } else { "anime;${data.url};${data.lastModified}" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt index d4d4486aa4..3a0579ef45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt @@ -12,9 +12,7 @@ import coil.request.Options import coil.request.Parameters import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER -import eu.kanade.tachiyomi.data.database.models.manga.Manga import eu.kanade.tachiyomi.network.await -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.online.HttpSource import logcat.LogPriority import okhttp3.CacheControl @@ -27,10 +25,11 @@ import okio.Source import okio.buffer import okio.sink import tachiyomi.core.util.system.logcat +import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.MangaCover +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.injectLazy import java.io.File -import tachiyomi.domain.entries.manga.model.Manga as DomainManga /** * A [Fetcher] that fetches cover image for [Manga] object. @@ -261,7 +260,7 @@ class MangaCoverFetcher( File, URL } - class Factory( + class MangaFactory( private val callFactoryLazy: Lazy, private val diskCacheLazy: Lazy, ) : Fetcher.Factory { @@ -270,36 +269,13 @@ class MangaCoverFetcher( private val sourceManager: MangaSourceManager by injectLazy() override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher { - return MangaCoverFetcher( - url = data.thumbnail_url, - isLibraryManga = data.favorite, - options = options, - coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnail_url) }, - customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) }, - diskCacheKeyLazy = lazy { MangaKeyer().key(data, options) }, - sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource }, - callFactoryLazy = callFactoryLazy, - diskCacheLazy = diskCacheLazy, - ) - } - } - - class DomainMangaFactory( - private val callFactoryLazy: Lazy, - private val diskCacheLazy: Lazy, - ) : Fetcher.Factory { - - private val coverCache: MangaCoverCache by injectLazy() - private val sourceManager: MangaSourceManager by injectLazy() - - override fun create(data: DomainManga, options: Options, imageLoader: ImageLoader): Fetcher { return MangaCoverFetcher( url = data.thumbnailUrl, isLibraryManga = data.favorite, options = options, coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnailUrl) }, customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data.id) }, - diskCacheKeyLazy = lazy { DomainMangaKeyer().key(data, options) }, + diskCacheKeyLazy = lazy { MangaKeyer().key(data, options) }, sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource }, callFactoryLazy = callFactoryLazy, diskCacheLazy = diskCacheLazy, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt index d332efb093..650cf5ba95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt @@ -4,24 +4,12 @@ import coil.key.Keyer import coil.request.Options import eu.kanade.domain.entries.manga.model.hasCustomCover import eu.kanade.tachiyomi.data.cache.MangaCoverCache -import eu.kanade.tachiyomi.data.database.models.manga.Manga -import eu.kanade.tachiyomi.data.database.models.manga.toDomainManga import tachiyomi.domain.entries.manga.model.MangaCover import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import tachiyomi.domain.entries.manga.model.Manga as DomainManga -class MangaKeyer : Keyer { - override fun key(data: Manga, options: Options): String { - return if (data.toDomainManga()!!.hasCustomCover()) { - "manga;${data.id};${data.cover_last_modified}" - } else { - "manga;${data.thumbnail_url};${data.cover_last_modified}" - } - } -} - -class DomainMangaKeyer : Keyer { +class MangaKeyer : Keyer { override fun key(data: DomainManga, options: Options): String { return if (data.hasCustomCover()) { "manga;${data.id};${data.coverLastModified}" @@ -31,9 +19,11 @@ class DomainMangaKeyer : Keyer { } } -class MangaCoverKeyer : Keyer { +class MangaCoverKeyer( + private val coverCache: MangaCoverCache = Injekt.get(), +) : Keyer { override fun key(data: MangaCover, options: Options): String { - return if (Injekt.get().getCustomCoverFile(data.mangaId).exists()) { + return if (coverCache.getCustomCoverFile(data.mangaId).exists()) { "manga;${data.mangaId};${data.lastModified}" } else { "manga;${data.url};${data.lastModified}" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/Anime.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/Anime.kt deleted file mode 100644 index eacc22ba57..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/Anime.kt +++ /dev/null @@ -1,56 +0,0 @@ -package eu.kanade.tachiyomi.data.database.models.anime - -import eu.kanade.tachiyomi.animesource.model.SAnime -import tachiyomi.domain.entries.anime.model.Anime as DomainAnime - -interface Anime : SAnime { - - var id: Long? - - var source: Long - - var favorite: Boolean - - // last time the episode list changed in any way - var last_update: Long - - var date_added: Long - - var viewer_flags: Int - - var episode_flags: Int - - var cover_last_modified: Long - - private fun setViewerFlags(flag: Int, mask: Int) { - viewer_flags = viewer_flags and mask.inv() or (flag and mask) - } - - var skipIntroLength: Int - get() = viewer_flags and DomainAnime.ANIME_INTRO_MASK.toInt() - set(flag) = setViewerFlags(flag, DomainAnime.ANIME_INTRO_MASK.toInt()) -} - -fun Anime.toDomainAnime(): DomainAnime? { - val mangaId = id ?: return null - return DomainAnime( - id = mangaId, - source = source, - favorite = favorite, - lastUpdate = last_update, - dateAdded = date_added, - viewerFlags = viewer_flags.toLong(), - episodeFlags = episode_flags.toLong(), - coverLastModified = cover_last_modified, - url = url, - title = title, - artist = artist, - author = author, - description = description, - genre = getGenres(), - status = status.toLong(), - thumbnailUrl = thumbnail_url, - initialized = initialized, - updateStrategy = update_strategy, - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/AnimeImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/AnimeImpl.kt deleted file mode 100644 index 0d09d1064c..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/anime/AnimeImpl.kt +++ /dev/null @@ -1,55 +0,0 @@ -package eu.kanade.tachiyomi.data.database.models.anime - -import eu.kanade.tachiyomi.source.model.UpdateStrategy - -open class AnimeImpl : Anime { - - override var id: Long? = null - - override var source: Long = -1 - - override lateinit var url: String - - override lateinit var title: String - - override var artist: String? = null - - override var author: String? = null - - override var description: String? = null - - override var genre: String? = null - - override var status: Int = 0 - - override var thumbnail_url: String? = null - - override var favorite: Boolean = false - - override var last_update: Long = 0 - - override var date_added: Long = 0 - - override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE - - override var initialized: Boolean = false - - override var viewer_flags: Int = 0 - - override var episode_flags: Int = 0 - - override var cover_last_modified: Long = 0 - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || javaClass != other.javaClass) return false - - val anime = other as Anime - if (url != anime.url) return false - return id == anime.id - } - - override fun hashCode(): Int { - return url.hashCode() + id.hashCode() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/Manga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/Manga.kt deleted file mode 100644 index 1b4315ba4f..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/Manga.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.data.database.models.manga - -import eu.kanade.tachiyomi.source.model.SManga -import eu.kanade.tachiyomi.ui.reader.setting.OrientationType -import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType -import tachiyomi.domain.entries.manga.model.Manga as DomainManga - -interface Manga : SManga { - - var id: Long? - - var source: Long - - var favorite: Boolean - - // last time the chapter list changed in any way - var last_update: Long - - var date_added: Long - - var viewer_flags: Int - - var chapter_flags: Int - - var cover_last_modified: Long - - private fun setViewerFlags(flag: Int, mask: Int) { - viewer_flags = viewer_flags and mask.inv() or (flag and mask) - } - - var readingModeType: Int - get() = viewer_flags and ReadingModeType.MASK - set(readingMode) = setViewerFlags(readingMode, ReadingModeType.MASK) - - var orientationType: Int - get() = viewer_flags and OrientationType.MASK - set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK) -} - -fun Manga.toDomainManga(): DomainManga? { - val mangaId = id ?: return null - return DomainManga( - id = mangaId, - source = source, - favorite = favorite, - lastUpdate = last_update, - dateAdded = date_added, - viewerFlags = viewer_flags.toLong(), - chapterFlags = chapter_flags.toLong(), - coverLastModified = cover_last_modified, - url = url, - title = title, - artist = artist, - author = author, - description = description, - genre = getGenres(), - status = status.toLong(), - thumbnailUrl = thumbnail_url, - updateStrategy = update_strategy, - initialized = initialized, - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/MangaImpl.kt deleted file mode 100644 index 9cd484cb23..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/manga/MangaImpl.kt +++ /dev/null @@ -1,55 +0,0 @@ -package eu.kanade.tachiyomi.data.database.models.manga - -import eu.kanade.tachiyomi.source.model.UpdateStrategy - -open class MangaImpl : Manga { - - override var id: Long? = null - - override var source: Long = -1 - - override lateinit var url: String - - override lateinit var title: String - - override var artist: String? = null - - override var author: String? = null - - override var description: String? = null - - override var genre: String? = null - - override var status: Int = 0 - - override var thumbnail_url: String? = null - - override var favorite: Boolean = false - - override var last_update: Long = 0 - - override var date_added: Long = 0 - - override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE - - override var initialized: Boolean = false - - override var viewer_flags: Int = 0 - - override var chapter_flags: Int = 0 - - override var cover_last_modified: Long = 0 - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || javaClass != other.javaClass) return false - - val manga = other as Manga - if (url != manga.url) return false - return id == manga.id - } - - override fun hashCode(): Int { - return url.hashCode() + id.hashCode() - } -} 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 51edb34d28..bb7f0b4352 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 @@ -4,10 +4,8 @@ import android.content.Context import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.core.util.mapNotNullKeys -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -30,8 +28,10 @@ import logcat.LogPriority import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.ConcurrentHashMap diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt index 34cd5958e0..0453480306 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt @@ -1,21 +1,28 @@ package eu.kanade.tachiyomi.data.download.anime import android.content.Context -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload -import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownloadQueue -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.runBlocking import logcat.LogPriority import rx.Observable import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.anime.interactor.GetAnimeCategories +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -43,11 +50,8 @@ class AnimeDownloadManager( */ private val pendingDeleter = AnimeDownloadPendingDeleter(context) - /** - * Downloads queue, where the pending episodes are stored. - */ - val queue: AnimeDownloadQueue - get() = downloader.queue + val queueState + get() = downloader.queueState // For use by DownloadService only fun downloaderStart() = downloader.start() @@ -86,7 +90,7 @@ class AnimeDownloadManager( * @param episodeId the episode to check. */ fun getQueuedDownloadOrNull(episodeId: Long): AnimeDownload? { - return queue.find { it.episode.id == episodeId } + return queueState.value.find { it: AnimeDownload -> it.episode.id == episodeId } } fun startDownloadNow(episodeId: Long?) { @@ -94,7 +98,7 @@ class AnimeDownloadManager( val download = getQueuedDownloadOrNull(episodeId) // If not in queue try to start a new download val toAdd = download ?: runBlocking { AnimeDownload.fromEpisodeId(episodeId) } ?: return - val queue = queue.toMutableList() + val queue = queueState.value.toMutableList() download?.let { queue.remove(it) } queue.add(0, toAdd) reorderQueue(queue) @@ -113,22 +117,7 @@ class AnimeDownloadManager( * @param downloads value to set the download queue to */ fun reorderQueue(downloads: List) { - if (downloader.queue.state == downloads) return - val wasRunning = downloader.isRunning - - if (downloads.isEmpty()) { - downloader.clearQueue() - downloader.stop() - return - } - - downloader.pause() - queue.clear() - queue.addAll(downloads) - - if (wasRunning) { - downloader.start() - } + downloader.updateQueue(downloads) } /** @@ -150,7 +139,7 @@ class AnimeDownloadManager( */ fun addDownloadsToStartOfQueue(downloads: List) { if (downloads.isEmpty()) return - queue.toMutableList().apply { + queueState.value.toMutableList().apply { addAll(0, downloads) reorderQueue(this) } @@ -254,7 +243,7 @@ class AnimeDownloadManager( fun deleteAnime(anime: Anime, source: AnimeSource, removeQueued: Boolean = true) { launchIO { if (removeQueued) { - queue.remove(anime) + downloader.removeFromQueue(anime) } provider.findAnimeDir(anime.title, source)?.delete() cache.removeAnime(anime) @@ -273,12 +262,12 @@ class AnimeDownloadManager( downloader.pause() } - queue.remove(episodes) + downloader.removeFromQueue(episodes) if (wasRunning) { - if (queue.isEmpty()) { + if (queueState.value.isEmpty()) { downloader.stop() - } else if (queue.isNotEmpty()) { + } else if (queueState.value.isNotEmpty()) { downloader.start() } } @@ -372,4 +361,33 @@ class AnimeDownloadManager( episodes } } + + fun statusFlow(): Flow = queueState + .flatMapLatest { downloads -> + downloads + .map { download -> + download.statusFlow.drop(1).map { download } + } + .merge() + } + .onStart { + emitAll( + queueState.value.filter { download -> download.status == AnimeDownload.State.DOWNLOADING }.asFlow(), + ) + } + + fun progressFlow(): Flow = queueState + .flatMapLatest { downloads -> + downloads + .map { download -> + download.progressFlow.drop(1).map { download } + } + .merge() + } + .onStart { + emitAll( + queueState.value.filter { download -> download.status == AnimeDownload.State.DOWNLOADING } + .asFlow(), + ) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt index f76d8df2c4..fa3f97ecad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download.anime import android.content.Context import androidx.core.net.toUri import com.hippo.unifile.UniFile -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.util.storage.DiskUtil @@ -12,6 +11,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import logcat.LogPriority import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt index 06af0063df..a5ff3e863d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt @@ -9,7 +9,6 @@ import android.os.IBinder import android.os.PowerManager import androidx.annotation.StringRes import androidx.core.content.ContextCompat -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.acquireWakeLock @@ -31,6 +30,7 @@ import logcat.LogPriority import ru.beryukhov.reactivenetwork.ReactiveNetwork import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import uy.kohesive.injekt.injectLazy /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadStore.kt index 5dd32e181a..44daa7e3cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadStore.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.core.content.edit import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString @@ -13,6 +12,7 @@ import kotlinx.serialization.json.Json import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.interactor.GetEpisode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get 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 61abb7ff28..708bd17d9f 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 @@ -14,7 +14,6 @@ import com.arthenica.ffmpegkit.SessionState import com.arthenica.ffmpegkit.StatisticsCallback import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.PublishRelay -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.items.episode.model.toSEpisode import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.model.Video @@ -22,28 +21,32 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.fetchUrlFromVideo import eu.kanade.tachiyomi.data.cache.EpisodeCache import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload -import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownloadQueue import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateNotifier import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.source.UnmeteredSource -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.toFFmpegString import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import logcat.LogPriority import okhttp3.HttpUrl.Companion.toHttpUrl import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers +import rx.subjects.PublishSubject import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -54,7 +57,7 @@ import java.util.concurrent.TimeUnit /** * This class is the one in charge of downloading episodes. * - * Its [queue] contains the list of episodes to download. In order to download them, the downloader + * Its queue contains the list of episodes to download. In order to download them, the downloader * subscription must be running and the list of episodes must be sent to them by [downloadsRelay]. * * The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected @@ -82,7 +85,8 @@ class AnimeDownloader( /** * Queue where active downloads are kept. */ - val queue = AnimeDownloadQueue(store) + val _queueState = MutableStateFlow>(emptyList()) + val queueState = _queueState.asStateFlow() /** * Notifier for the downloader state and progress. @@ -125,7 +129,7 @@ class AnimeDownloader( init { launchNow { val episodes = async { store.restore() } - queue.addAll(episodes.await()) + addAllToQueue(episodes.await()) } } @@ -136,13 +140,13 @@ class AnimeDownloader( * @return true if the downloader is started, false otherwise. */ fun start(): Boolean { - if (subscription != null || queue.isEmpty()) { + if (subscription != null || queueState.value.isEmpty()) { return false } initializeSubscription() - val pending = queue.filter { it.status != AnimeDownload.State.DOWNLOADED } + val pending = queueState.value.filter { it: AnimeDownload -> it.status != AnimeDownload.State.DOWNLOADED } pending.forEach { if (it.status != AnimeDownload.State.QUEUE) it.status = AnimeDownload.State.QUEUE } isPaused = false @@ -156,23 +160,23 @@ class AnimeDownloader( */ fun stop(reason: String? = null) { destroySubscription() - queue + queueState.value .filter { it.status == AnimeDownload.State.DOWNLOADING } .forEach { it.status = AnimeDownload.State.ERROR } if (reason != null) { - queue.state.value.forEach { + queueState.value.forEach { notifier.onWarning(reason) return } } - if (isPaused && queue.isNotEmpty()) { - queue.state.value.forEach { + if (isPaused && queueState.value.isNotEmpty()) { + queueState.value.forEach { notifier.onPaused(it) } } else { - queue.state.value.forEach { + queueState.value.forEach { notifier.onComplete(it) } } @@ -190,7 +194,7 @@ class AnimeDownloader( */ fun pause() { destroySubscription() - queue + queueState.value .filter { it.status == AnimeDownload.State.DOWNLOADING } .forEach { it.status = AnimeDownload.State.QUEUE } isPaused = true @@ -202,10 +206,10 @@ class AnimeDownloader( fun clearQueue() { destroySubscription() - queue.state.value.forEach { + queueState.value.forEach { notifier.dismissProgress(it) } - queue.clear() + _clearQueue() } /** @@ -237,7 +241,7 @@ class AnimeDownloader( }, { error -> logcat(LogPriority.ERROR, error) - queue.state.value.forEach { + queueState.value.forEach { notifier.onError(it, error.message, it.episode.name, it.anime.title) } stop() @@ -273,7 +277,7 @@ class AnimeDownloader( } val source = sourceManager.get(anime.source) as? AnimeHttpSource ?: return@launchIO - val wasEmpty = queue.isEmpty() + val wasEmpty = queueState.value.isEmpty() // Called in background thread, the operation can be slow with SAF. val episodesWithoutDir = async { episodes @@ -286,12 +290,12 @@ class AnimeDownloader( // Runs in main thread (synchronization needed). val episodesToQueue = episodesWithoutDir.await() // Filter out those already enqueued. - .filter { episode -> queue.none { it.episode.id == episode.id } } + .filter { episode -> queueState.value.none { it: AnimeDownload -> it.episode.id == episode.id } } // Create a download for each one. .map { AnimeDownload(source, anime, it, changeDownloader, video) } if (episodesToQueue.isNotEmpty()) { - queue.addAll(episodesToQueue) + addAllToQueue(episodesToQueue) if (isRunning) { // Send the list of downloads to the downloader. @@ -300,8 +304,8 @@ class AnimeDownloader( // Start downloader if needed if (autoStart && wasEmpty) { - val queuedDownloads = queue.filter { it.source !is UnmeteredSource }.count() - val maxDownloadsFromSource = queue + val queuedDownloads = queueState.value.filter { it: AnimeDownload -> it.source !is UnmeteredSource }.count() + val maxDownloadsFromSource = queueState.value .groupBy { it.source } .filterKeys { it !is UnmeteredSource } .maxOfOrNull { it.value.size } @@ -401,7 +405,7 @@ class AnimeDownloader( .doOnNext { ensureSuccessfulAnimeDownload(download, animeDir, tmpDir, episodeDirname) - queue.state.value.forEach { + queueState.value.forEach { if (download.status == AnimeDownload.State.DOWNLOADED) notifier.dismissProgress(it) } } @@ -615,7 +619,7 @@ class AnimeDownloader( file.renameTo("$filename.mp4") } catch (e: Exception) { response.close() - if (!queue.equals(download)) file.delete() + if (!queueState.value.equals(download)) file.delete() // file.delete() throw e } @@ -669,7 +673,7 @@ class AnimeDownloader( } it.delete() tmpDir.delete() - queue.find { Anime -> Anime.video == video }?.let { Anime -> + queueState.value.find { Anime -> Anime.video == video }?.let { Anime -> Anime.status = AnimeDownload.State.DOWNLOADED completeAnimeDownload(Anime) } @@ -750,7 +754,7 @@ class AnimeDownloader( // Delete successful downloads from queue if (download.status == AnimeDownload.State.DOWNLOADED) { // Remove downloaded episode from queue - queue.remove(download) + removeFromQueue(download) } if (areAllAnimeDownloadsFinished()) { stop() @@ -761,7 +765,86 @@ class AnimeDownloader( * Returns true if all the queued downloads are in DOWNLOADED or ERROR state. */ private fun areAllAnimeDownloadsFinished(): Boolean { - return queue.none { it.status.value <= AnimeDownload.State.DOWNLOADING.value } + return queueState.value.none { it: AnimeDownload -> it.status.value <= AnimeDownload.State.DOWNLOADING.value } + } + + private val progressSubject = PublishSubject.create() + + private fun setProgressFor(download: AnimeDownload) { + if (download.status == AnimeDownload.State.DOWNLOADED || download.status == AnimeDownload.State.ERROR) { + setProgressSubject(download.video, null) + } + } + + private fun setProgressSubject(video: Video?, subject: PublishSubject?) { + video?.progressSubject = subject + } + + fun addAllToQueue(downloads: List) { + _queueState.update { + downloads.forEach { download -> + download.progressSubject = progressSubject + download.progressCallback = ::setProgressFor + download.status = AnimeDownload.State.QUEUE + } + store.addAll(downloads) + it + downloads + } + } + + fun removeFromQueue(download: AnimeDownload) { + _queueState.update { + store.remove(download) + download.progressSubject = null + download.progressCallback = null + if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { + download.status = AnimeDownload.State.NOT_DOWNLOADED + } + it - download + } + } + + fun removeFromQueue(episodes: List) { + episodes.forEach { episode -> + queueState.value.find { it.episode.id == episode.id }?.let { removeFromQueue(it) } + } + } + + fun removeFromQueue(anime: Anime) { + queueState.value.filter { it.anime.id == anime.id }.forEach { removeFromQueue(it) } + } + + fun _clearQueue() { + _queueState.update { + it.forEach { download -> + download.progressSubject = null + download.progressCallback = null + if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { + download.status = AnimeDownload.State.NOT_DOWNLOADED + } + } + store.clear() + emptyList() + } + } + + fun updateQueue(downloads: List) { + if (queueState == downloads) return + val wasRunning = isRunning + + if (downloads.isEmpty()) { + clearQueue() + stop() + return + } + + pause() + _clearQueue() + addAllToQueue(downloads) + + if (wasRunning) { + start() + } } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownload.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownload.kt index 16503f1da3..50821efc9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownload.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownload.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.download.anime.model import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -16,6 +15,7 @@ import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.interactor.GetEpisode import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownloadQueue.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownloadQueue.kt deleted file mode 100644 index e175d13488..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/model/AnimeDownloadQueue.kt +++ /dev/null @@ -1,120 +0,0 @@ -package eu.kanade.tachiyomi.data.download.anime.model - -import eu.kanade.core.util.asFlow -import eu.kanade.tachiyomi.animesource.model.Video -import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadStore -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.update -import rx.subjects.PublishSubject -import tachiyomi.domain.entries.anime.model.Anime -import tachiyomi.domain.items.episode.model.Episode - -class AnimeDownloadQueue( - private val store: AnimeDownloadStore, -) { - private val _state = MutableStateFlow>(emptyList()) - val state = _state.asStateFlow() - - private val progressSubject = PublishSubject.create() - - fun addAll(downloads: List) { - _state.update { - downloads.forEach { download -> - download.progressSubject = progressSubject - download.progressCallback = ::setProgressFor - download.status = AnimeDownload.State.QUEUE - } - store.addAll(downloads) - it + downloads - } - } - - fun remove(download: AnimeDownload) { - _state.update { - store.remove(download) - download.progressSubject = null - download.progressCallback = null - if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { - download.status = AnimeDownload.State.NOT_DOWNLOADED - } - it - download - } - } - - fun remove(episode: Episode) { - _state.value.find { it.episode.id == episode.id }?.let { remove(it) } - } - - fun remove(episodes: List) { - episodes.forEach(::remove) - } - - fun remove(anime: Anime) { - _state.value.filter { it.anime.id == anime.id }.forEach { remove(it) } - } - - fun clear() { - _state.update { - it.forEach { download -> - download.progressSubject = null - download.progressCallback = null - if (download.status == AnimeDownload.State.DOWNLOADING || download.status == AnimeDownload.State.QUEUE) { - download.status = AnimeDownload.State.NOT_DOWNLOADED - } - } - store.clear() - emptyList() - } - } - - fun statusFlow(): Flow = state - .flatMapLatest { downloads -> - downloads - .map { download -> - download.statusFlow.drop(1).map { download } - } - .merge() - } - .onStart { emitAll(getActiveDownloads()) } - - fun progressFlow(): Flow = state - .flatMapLatest { downloads -> - downloads - .map { download -> - download.progressFlow.drop(1).map { download } - } - .merge() - } - .onStart { emitAll(getActiveDownloads()) } - - private fun getActiveDownloads(): Flow = - _state.value.filter { download -> download.status == AnimeDownload.State.DOWNLOADING }.asFlow() - - fun count(predicate: (AnimeDownload) -> Boolean) = _state.value.count(predicate) - fun filter(predicate: (AnimeDownload) -> Boolean) = _state.value.filter(predicate) - fun find(predicate: (AnimeDownload) -> Boolean) = _state.value.find(predicate) - fun groupBy(keySelector: (AnimeDownload) -> K) = _state.value.groupBy(keySelector) - fun isEmpty() = _state.value.isEmpty() - fun isNotEmpty() = _state.value.isNotEmpty() - fun none(predicate: (AnimeDownload) -> Boolean) = _state.value.none(predicate) - fun toMutableList() = _state.value.toMutableList() - - private fun setProgressFor(download: AnimeDownload) { - if (download.status == AnimeDownload.State.DOWNLOADED || download.status == AnimeDownload.State.ERROR) { - setProgressSubject(download.video, null) - } - } - - private fun setProgressSubject(video: Video?, subject: PublishSubject?) { - video?.progressSubject = subject - } -} 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 32240b5d78..1733391c5d 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 @@ -1,13 +1,13 @@ package eu.kanade.tachiyomi.data.download.manga +import android.app.Application import android.content.Context +import android.net.Uri import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.core.util.mapNotNullKeys -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.source.MangaSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -16,6 +16,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.debounce @@ -25,15 +26,31 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeoutOrNull +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.protobuf.ProtoBuf import logcat.LogPriority import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds @@ -76,7 +93,11 @@ class MangaDownloadCache( .debounce(1000L) // Don't notify if it finishes quickly enough .stateIn(scope, SharingStarted.WhileSubscribed(), false) - private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference()) + private val diskCacheFile: File + get() = File(context.cacheDir, "dl_index_cache") + + private val rootDownloadsDirLock = Mutex() + private var rootDownloadsDir: RootDirectory init { downloadPreferences.downloadsDirectory().changes() @@ -85,6 +106,21 @@ class MangaDownloadCache( invalidateCache() } .launchIn(scope) + + rootDownloadsDir = runBlocking(Dispatchers.IO) { + try { + val diskCache = diskCacheFile.inputStream().use { + ProtoBuf.decodeFromByteArray(it.readBytes()) + } + lastRenew = 1 // Just so that the banner won't show up + diskCache + } catch (e: Throwable) { + diskCacheFile.delete() + null + } + } ?: RootDirectory(getDirectoryFromPreference()) + + notifyChanges() } /** @@ -158,28 +194,28 @@ class MangaDownloadCache( * @param mangaUniFile the directory of the manga. * @param manga the manga of the chapter. */ - @Synchronized - fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { - // Retrieve the cached source directory or cache a new one - var sourceDir = rootDownloadsDir.sourceDirs[manga.source] - if (sourceDir == null) { - val source = sourceManager.get(manga.source) ?: return - val sourceUniFile = provider.findSourceDir(source) ?: return - sourceDir = SourceDirectory(sourceUniFile) - rootDownloadsDir.sourceDirs += manga.source to sourceDir - } - - // Retrieve the cached manga directory or cache a new one - val mangaDirName = provider.getMangaDirName(manga.title) - var mangaDir = sourceDir.mangaDirs[mangaDirName] - if (mangaDir == null) { - mangaDir = MangaDirectory(mangaUniFile) - sourceDir.mangaDirs += mangaDirName to mangaDir - } + suspend fun addChapter(chapterDirName: String, mangaUniFile: UniFile, manga: Manga) { + rootDownloadsDirLock.withLock { + // Retrieve the cached source directory or cache a new one + var sourceDir = rootDownloadsDir.sourceDirs[manga.source] + if (sourceDir == null) { + val source = sourceManager.get(manga.source) ?: return + val sourceUniFile = provider.findSourceDir(source) ?: return + sourceDir = SourceDirectory(sourceUniFile) + rootDownloadsDir.sourceDirs += manga.source to sourceDir + } - // Save the chapter directory - mangaDir.chapterDirs += chapterDirName + // Retrieve the cached manga directory or cache a new one + val mangaDirName = provider.getMangaDirName(manga.title) + var mangaDir = sourceDir.mangaDirs[mangaDirName] + if (mangaDir == null) { + mangaDir = MangaDirectory(mangaUniFile) + sourceDir.mangaDirs += mangaDirName to mangaDir + } + // Save the chapter directory + mangaDir.chapterDirs += chapterDirName + } notifyChanges() } @@ -189,13 +225,14 @@ class MangaDownloadCache( * @param chapter the chapter to remove. * @param manga the manga of the chapter. */ - @Synchronized - fun removeChapter(chapter: Chapter, manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return - provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { - if (it in mangaDir.chapterDirs) { - mangaDir.chapterDirs -= it + suspend fun removeChapter(chapter: Chapter, manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { + if (it in mangaDir.chapterDirs) { + mangaDir.chapterDirs -= it + } } } @@ -208,14 +245,16 @@ class MangaDownloadCache( * @param chapters the list of chapter to remove. * @param manga the manga of the chapter. */ - @Synchronized - fun removeChapters(chapters: List, manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return - chapters.forEach { chapter -> - provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { - if (it in mangaDir.chapterDirs) { - mangaDir.chapterDirs -= it + + suspend fun removeChapters(chapters: List, manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + chapters.forEach { chapter -> + provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { + if (it in mangaDir.chapterDirs) { + mangaDir.chapterDirs -= it + } } } } @@ -228,20 +267,22 @@ class MangaDownloadCache( * * @param manga the manga to remove. */ - @Synchronized - fun removeManga(manga: Manga) { - val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDirName = provider.getMangaDirName(manga.title) - if (sourceDir.mangaDirs.containsKey(mangaDirName)) { - sourceDir.mangaDirs -= mangaDirName + suspend fun removeManga(manga: Manga) { + rootDownloadsDirLock.withLock { + val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return + val mangaDirName = provider.getMangaDirName(manga.title) + if (sourceDir.mangaDirs.containsKey(mangaDirName)) { + sourceDir.mangaDirs -= mangaDirName + } } notifyChanges() } - @Synchronized - fun removeSource(source: MangaSource) { - rootDownloadsDir.sourceDirs -= source.id + suspend fun removeSource(source: MangaSource) { + rootDownloadsDirLock.withLock { + rootDownloadsDir.sourceDirs -= source.id + } notifyChanges() } @@ -287,46 +328,50 @@ class MangaDownloadCache( } } - val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty() - .associate { it.name to SourceDirectory(it) } - .mapNotNullKeys { entry -> - sources.find { - provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) - }?.id - } + rootDownloadsDirLock.withLock { + val sourceDirs = rootDownloadsDir.dir.listFiles().orEmpty() + .associate { it.name to SourceDirectory(it) } + .mapNotNullKeys { entry -> + sources.find { + provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) + }?.id + } - rootDownloadsDir.sourceDirs = sourceDirs - - sourceDirs.values - .map { sourceDir -> - async { - val mangaDirs = sourceDir.dir.listFiles().orEmpty() - .filterNot { it.name.isNullOrBlank() } - .associate { it.name!! to MangaDirectory(it) } - - sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs) - - mangaDirs.values.forEach { mangaDir -> - val chapterDirs = mangaDir.dir.listFiles().orEmpty() - .mapNotNull { - when { - // Ignore incomplete downloads - it.name?.endsWith(MangaDownloader.TMP_DIR_SUFFIX) == true -> null - // Folder of images - it.isDirectory -> it.name - // CBZ files - it.isFile && it.name?.endsWith(".cbz") == true -> it.name!!.substringBeforeLast(".cbz") - // Anything else is irrelevant - else -> null + rootDownloadsDir.sourceDirs = sourceDirs + + sourceDirs.values + .map { sourceDir -> + async { + val mangaDirs = sourceDir.dir.listFiles().orEmpty() + .filterNot { it.name.isNullOrBlank() } + .associate { it.name!! to MangaDirectory(it) } + + sourceDir.mangaDirs = ConcurrentHashMap(mangaDirs) + + mangaDirs.values.forEach { mangaDir -> + val chapterDirs = mangaDir.dir.listFiles().orEmpty() + .mapNotNull { + when { + // Ignore incomplete downloads + it.name?.endsWith(MangaDownloader.TMP_DIR_SUFFIX) == true -> null + // Folder of images + it.isDirectory -> it.name + // CBZ files + it.isFile && it.name?.endsWith(".cbz") == true -> it.name!!.substringBeforeLast( + ".cbz", + ) + // Anything else is irrelevant + else -> null + } } - } - .toMutableSet() + .toMutableSet() - mangaDir.chapterDirs = chapterDirs + mangaDir.chapterDirs = chapterDirs + } } } - } - .awaitAll() + .awaitAll() + } _isInitializing.emit(false) }.also { @@ -335,6 +380,7 @@ class MangaDownloadCache( logcat(LogPriority.ERROR, exception) { "Failed to create download cache" } } lastRenew = System.currentTimeMillis() + notifyChanges() } } @@ -351,29 +397,67 @@ class MangaDownloadCache( scope.launchNonCancellable { _changes.send(Unit) } + updateDiskCache() + } + + private var updateDiskCacheJob: Job? = null + private fun updateDiskCache() { + updateDiskCacheJob?.cancel() + updateDiskCacheJob = scope.launchIO { + delay(1000) + ensureActive() + val bytes = ProtoBuf.encodeToByteArray(rootDownloadsDir) + ensureActive() + try { + diskCacheFile.writeBytes(bytes) + } catch (e: Throwable) { + logcat( + priority = LogPriority.ERROR, + throwable = e, + message = { "Failed to write disk cache file" }, + ) + } + } } } /** * Class to store the files under the root downloads directory. */ +@Serializable private class RootDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, - var sourceDirs: ConcurrentHashMap = ConcurrentHashMap(), + var sourceDirs: Map = mapOf(), ) /** * Class to store the files under a source directory. */ +@Serializable private class SourceDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, - var mangaDirs: ConcurrentHashMap = ConcurrentHashMap(), + var mangaDirs: Map = mapOf(), ) /** * Class to store the files under a manga directory. */ +@Serializable private class MangaDirectory( + @Serializable(with = UniFileAsStringSerializer::class) val dir: UniFile, var chapterDirs: MutableSet = mutableSetOf(), ) + +private object UniFileAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UniFile", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: UniFile) { + return encoder.encodeString(value.uri.toString()) + } + override fun deserialize(decoder: Decoder): UniFile { + return UniFile.fromUri(Injekt.get(), Uri.parse(decoder.decodeString())) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt index 8939b0c8ae..1ec8689ebb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt @@ -1,20 +1,27 @@ package eu.kanade.tachiyomi.data.download.manga import android.content.Context -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload -import eu.kanade.tachiyomi.data.download.manga.model.MangaDownloadQueue import eu.kanade.tachiyomi.source.MangaSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.model.Page +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.drop +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.runBlocking import logcat.LogPriority import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.manga.interactor.GetMangaCategories +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -42,11 +49,8 @@ class MangaDownloadManager( */ private val pendingDeleter = MangaDownloadPendingDeleter(context) - /** - * Downloads queue, where the pending chapters are stored. - */ - val queue: MangaDownloadQueue - get() = downloader.queue + val queueState + get() = downloader.queueState // For use by DownloadService only fun downloaderStart() = downloader.start() @@ -85,7 +89,7 @@ class MangaDownloadManager( * @param chapterId the chapter to check. */ fun getQueuedDownloadOrNull(chapterId: Long): MangaDownload? { - return queue.find { it.chapter.id == chapterId } + return queueState.value.find { it: MangaDownload -> it.chapter.id == chapterId } } fun startDownloadNow(chapterId: Long?) { @@ -93,7 +97,7 @@ class MangaDownloadManager( val download = getQueuedDownloadOrNull(chapterId) // If not in queue try to start a new download val toAdd = download ?: runBlocking { MangaDownload.fromChapterId(chapterId) } ?: return - val queue = queue.toMutableList() + val queue = queueState.value.toMutableList() download?.let { queue.remove(it) } queue.add(0, toAdd) reorderQueue(queue) @@ -112,21 +116,7 @@ class MangaDownloadManager( * @param downloads value to set the download queue to */ fun reorderQueue(downloads: List) { - val wasRunning = downloader.isRunning - - if (downloads.isEmpty()) { - downloader.clearQueue() - downloader.stop() - return - } - - downloader.pause() - queue.clear() - queue.addAll(downloads) - - if (wasRunning) { - downloader.start() - } + downloader.updateQueue(downloads) } /** @@ -147,7 +137,7 @@ class MangaDownloadManager( */ fun addDownloadsToStartOfQueue(downloads: List) { if (downloads.isEmpty()) return - queue.toMutableList().apply { + queueState.value.toMutableList().apply { addAll(0, downloads) reorderQueue(this) } @@ -250,7 +240,7 @@ class MangaDownloadManager( fun deleteManga(manga: Manga, source: MangaSource, removeQueued: Boolean = true) { launchIO { if (removeQueued) { - queue.remove(manga) + downloader.removeFromQueue(manga) } provider.findMangaDir(manga.title, source)?.delete() cache.removeManga(manga) @@ -270,12 +260,12 @@ class MangaDownloadManager( downloader.pause() } - queue.remove(chapters) + downloader.removeFromQueue(chapters) if (wasRunning) { - if (queue.isEmpty()) { + if (queueState.value.isEmpty()) { downloader.stop() - } else if (queue.isNotEmpty()) { + } else if (queueState.value.isNotEmpty()) { downloader.start() } } @@ -334,7 +324,7 @@ class MangaDownloadManager( * @param oldChapter the existing chapter with the old name. * @param newChapter the target chapter with the new name. */ - fun renameChapter(source: MangaSource, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { + suspend fun renameChapter(source: MangaSource, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator) val mangaDir = provider.getMangaDir(manga.title, source) @@ -373,4 +363,33 @@ class MangaDownloadManager( chapters } } + + fun statusFlow(): Flow = queueState + .flatMapLatest { downloads -> + downloads + .map { download -> + download.statusFlow.drop(1).map { download } + } + .merge() + } + .onStart { + emitAll( + queueState.value.filter { download -> download.status == MangaDownload.State.DOWNLOADING }.asFlow(), + ) + } + + fun progressFlow(): Flow = queueState + .flatMapLatest { downloads -> + downloads + .map { download -> + download.progressFlow.drop(1).map { download } + } + .merge() + } + .onStart { + emitAll( + queueState.value.filter { download -> download.status == MangaDownload.State.DOWNLOADING } + .asFlow(), + ) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt index 2e8273be51..19f90a45a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download.manga import android.content.Context import androidx.core.net.toUri import com.hippo.unifile.UniFile -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.MangaSource import eu.kanade.tachiyomi.util.storage.DiskUtil @@ -12,6 +11,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import logcat.LogPriority import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt index 455bb9da02..36a43679ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt @@ -8,7 +8,6 @@ import android.os.IBinder import android.os.PowerManager import androidx.annotation.StringRes import androidx.core.content.ContextCompat -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.acquireWakeLock @@ -30,6 +29,7 @@ import logcat.LogPriority import ru.beryukhov.reactivenetwork.ReactiveNetwork import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import uy.kohesive.injekt.injectLazy /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadStore.kt index d8dc759259..af21a771e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadStore.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download.manga import android.content.Context import androidx.core.content.edit import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable @@ -13,6 +12,7 @@ import kotlinx.serialization.json.Json import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.interactor.GetChapter +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get 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 bbbdcb214f..2f3a69d5c1 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 @@ -3,17 +3,14 @@ package eu.kanade.tachiyomi.data.download.manga import android.content.Context import com.hippo.unifile.UniFile import com.jakewharton.rxrelay.PublishRelay -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.entries.manga.model.getComicInfo import eu.kanade.domain.items.chapter.model.toSChapter import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload -import eu.kanade.tachiyomi.data.download.manga.model.MangaDownloadQueue import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateNotifier import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.source.UnmeteredSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.storage.DiskUtil @@ -23,12 +20,15 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.retryWhen +import kotlinx.coroutines.flow.update import kotlinx.coroutines.runBlocking import logcat.LogPriority import nl.adaptivity.xmlutil.serialization.XML @@ -46,8 +46,10 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.logcat +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.BufferedOutputStream @@ -59,7 +61,7 @@ import java.util.zip.ZipOutputStream /** * This class is the one in charge of downloading chapters. * - * Its [queue] contains the list of chapters to download. In order to download them, the downloader + * Its queue contains the list of chapters to download. In order to download them, the downloader * subscription must be running and the list of chapters must be sent to them by [downloadsRelay]. * * The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected @@ -88,7 +90,8 @@ class MangaDownloader( /** * Queue where active downloads are kept. */ - val queue = MangaDownloadQueue(store) + val _queueState = MutableStateFlow>(emptyList()) + val queueState = _queueState.asStateFlow() /** * Notifier for the downloader state and progress. @@ -120,7 +123,7 @@ class MangaDownloader( init { launchNow { val chapters = async { store.restore() } - queue.addAll(chapters.await()) + addAllToQueue(chapters.await()) } } @@ -131,13 +134,13 @@ class MangaDownloader( * @return true if the downloader is started, false otherwise. */ fun start(): Boolean { - if (subscription != null || queue.isEmpty()) { + if (subscription != null || queueState.value.isEmpty()) { return false } initializeSubscription() - val pending = queue.filter { it.status != MangaDownload.State.DOWNLOADED } + val pending = queueState.value.filter { it: MangaDownload -> it.status != MangaDownload.State.DOWNLOADED } pending.forEach { if (it.status != MangaDownload.State.QUEUE) it.status = MangaDownload.State.QUEUE } isPaused = false @@ -151,7 +154,7 @@ class MangaDownloader( */ fun stop(reason: String? = null) { destroySubscription() - queue + queueState.value .filter { it.status == MangaDownload.State.DOWNLOADING } .forEach { it.status = MangaDownload.State.ERROR } @@ -160,7 +163,7 @@ class MangaDownloader( return } - if (isPaused && queue.isNotEmpty()) { + if (isPaused && queueState.value.isNotEmpty()) { notifier.onPaused() } else { notifier.onComplete() @@ -179,7 +182,7 @@ class MangaDownloader( */ fun pause() { destroySubscription() - queue + queueState.value .filter { it.status == MangaDownload.State.DOWNLOADING } .forEach { it.status = MangaDownload.State.QUEUE } isPaused = true @@ -191,7 +194,7 @@ class MangaDownloader( fun clearQueue() { destroySubscription() - queue.clear() + _clearQueue() notifier.dismissProgress() } @@ -250,7 +253,7 @@ class MangaDownloader( } val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO - val wasEmpty = queue.isEmpty() + val wasEmpty = queueState.value.isEmpty() // Called in background thread, the operation can be slow with SAF. val chaptersWithoutDir = async { chapters @@ -263,12 +266,12 @@ class MangaDownloader( // Runs in main thread (synchronization needed). val chaptersToQueue = chaptersWithoutDir.await() // Filter out those already enqueued. - .filter { chapter -> queue.none { it.chapter.id == chapter.id } } + .filter { chapter -> queueState.value.none { it: MangaDownload -> it.chapter.id == chapter.id } } // Create a download for each one. .map { MangaDownload(source, manga, it) } if (chaptersToQueue.isNotEmpty()) { - queue.addAll(chaptersToQueue) + addAllToQueue(chaptersToQueue) if (isRunning) { // Send the list of downloads to the downloader. @@ -277,8 +280,8 @@ class MangaDownloader( // Start downloader if needed if (autoStart && wasEmpty) { - val queuedDownloads = queue.count { it.source !is UnmeteredSource } - val maxDownloadsFromSource = queue + val queuedDownloads = queueState.value.count { it: MangaDownload -> it.source !is UnmeteredSource } + val maxDownloadsFromSource = queueState.value .groupBy { it.source } .filterKeys { it !is UnmeteredSource } .maxOfOrNull { it.value.size } @@ -407,10 +410,7 @@ class MangaDownloader( } // When the page is ready, set page path, progress (just in case) and status - val success = splitTallImageIfNeeded(page, tmpDir) - if (!success) { - notifier.onError(context.getString(R.string.download_notifier_split_failed), download.chapter.name, download.manga.title) - } + splitTallImageIfNeeded(page, tmpDir) page.uri = file.uri page.progress = 100 page.status = Page.State.READY @@ -498,21 +498,18 @@ class MangaDownloader( return ImageUtil.getExtensionFromMimeType(mime) } - private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile): Boolean { - if (!downloadPreferences.splitTallImages().get()) return true - - val filenamePrefix = String.format("%03d", page.number) - val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } - ?: throw Error(context.getString(R.string.download_notifier_split_page_not_found, page.number)) + private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { + try { + val filenamePrefix = String.format("%03d", page.number) + val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } + ?: error(context.getString(R.string.download_notifier_split_page_not_found, page.number)) - // If the original page was previously split, then skip - if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return true + // If the original page was previously split, then skip + if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return - return try { ImageUtil.splitTallImage(tmpDir, imageFile, filenamePrefix) } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - false + logcat(LogPriority.ERROR, e) { "Failed to split downloaded image" } } } @@ -524,7 +521,7 @@ class MangaDownloader( * @param tmpDir the directory where the download is currently stored. * @param dirname the real (non temporary) directory name of the download. */ - private fun ensureSuccessfulDownload( + private suspend fun ensureSuccessfulDownload( download: MangaDownload, mangaDir: UniFile, tmpDir: UniFile, @@ -547,14 +544,12 @@ class MangaDownloader( } download.status = if (downloadedImagesCount == downloadPageCount) { - // TODO: Uncomment when #8537 is resolved -// val chapterUrl = download.source.getChapterUrl(download.chapter) -// createComicInfoFile( -// tmpDir, -// download.manga, -// download.chapter.toDomainChapter()!!, -// chapterUrl, -// ) + createComicInfoFile( + tmpDir, + download.manga, + download.chapter, + download.source, + ) // Only rename the directory if it's downloaded if (downloadPreferences.saveChaptersAsCBZ().get()) { @@ -608,23 +603,19 @@ class MangaDownloader( /** * Creates a ComicInfo.xml file inside the given directory. - * - * @param dir the directory in which the ComicInfo file will be generated. - * @param manga the manga. - * @param chapter the chapter. - * @param chapterUrl the resolved URL for the chapter. */ private fun createComicInfoFile( dir: UniFile, manga: Manga, chapter: Chapter, - chapterUrl: String, + source: HttpSource, ) { + val chapterUrl = source.getChapterUrl(chapter.toSChapter()) val comicInfo = getComicInfo(manga, chapter, chapterUrl) - val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo) // Remove the old file dir.findFile(COMIC_INFO_FILE)?.delete() dir.createFile(COMIC_INFO_FILE).openOutputStream().use { + val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo) it.write(comicInfoString.toByteArray()) } } @@ -636,7 +627,7 @@ class MangaDownloader( // Delete successful downloads from queue if (download.status == MangaDownload.State.DOWNLOADED) { // Remove downloaded chapter from queue - queue.remove(download) + removeFromQueue(download) } if (areAllDownloadsFinished()) { stop() @@ -647,7 +638,67 @@ class MangaDownloader( * Returns true if all the queued downloads are in DOWNLOADED or ERROR state. */ private fun areAllDownloadsFinished(): Boolean { - return queue.none { it.status.value <= MangaDownload.State.DOWNLOADING.value } + return queueState.value.none { it: MangaDownload -> it.status.value <= MangaDownload.State.DOWNLOADING.value } + } + + fun addAllToQueue(downloads: List) { + _queueState.update { + downloads.forEach { download -> + download.status = MangaDownload.State.QUEUE + } + store.addAll(downloads) + it + downloads + } + } + + fun removeFromQueue(download: MangaDownload) { + _queueState.update { + store.remove(download) + if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { + download.status = MangaDownload.State.NOT_DOWNLOADED + } + it - download + } + } + + fun removeFromQueue(chapters: List) { + chapters.forEach { chapter -> + queueState.value.find { it.chapter.id == chapter.id }?.let { removeFromQueue(it) } + } + } + + fun removeFromQueue(manga: Manga) { + queueState.value.filter { it.manga.id == manga.id }.forEach { removeFromQueue(it) } + } + + fun _clearQueue() { + _queueState.update { + it.forEach { download -> + if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { + download.status = MangaDownload.State.NOT_DOWNLOADED + } + } + store.clear() + emptyList() + } + } + + fun updateQueue(downloads: List) { + val wasRunning = isRunning + + if (downloads.isEmpty()) { + clearQueue() + stop() + return + } + + pause() + _clearQueue() + addAllToQueue(downloads) + + if (wasRunning) { + start() + } } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownload.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownload.kt index f3961acdb4..3af7dc0eb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownload.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownload.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.download.manga.model -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.delay @@ -15,6 +14,7 @@ import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.interactor.GetChapter import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownloadQueue.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownloadQueue.kt deleted file mode 100644 index c37127ccd6..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/model/MangaDownloadQueue.kt +++ /dev/null @@ -1,100 +0,0 @@ -package eu.kanade.tachiyomi.data.download.manga.model - -import eu.kanade.core.util.asFlow -import eu.kanade.tachiyomi.data.download.manga.MangaDownloadStore -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.update -import tachiyomi.domain.entries.manga.model.Manga -import tachiyomi.domain.items.chapter.model.Chapter - -class MangaDownloadQueue( - private val store: MangaDownloadStore, -) { - private val _state = MutableStateFlow>(emptyList()) - val state = _state.asStateFlow() - - fun addAll(downloads: List) { - _state.update { - downloads.forEach { download -> - download.status = MangaDownload.State.QUEUE - } - store.addAll(downloads) - it + downloads - } - } - - fun remove(download: MangaDownload) { - _state.update { - store.remove(download) - if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { - download.status = MangaDownload.State.NOT_DOWNLOADED - } - it - download - } - } - - fun remove(chapter: Chapter) { - _state.value.find { it.chapter.id == chapter.id }?.let { remove(it) } - } - - fun remove(chapters: List) { - chapters.forEach(::remove) - } - - fun remove(manga: Manga) { - _state.value.filter { it.manga.id == manga.id }.forEach { remove(it) } - } - - fun clear() { - _state.update { - it.forEach { download -> - if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { - download.status = MangaDownload.State.NOT_DOWNLOADED - } - } - store.clear() - emptyList() - } - } - - fun statusFlow(): Flow = state - .flatMapLatest { downloads -> - downloads - .map { download -> - download.statusFlow.drop(1).map { download } - } - .merge() - } - .onStart { emitAll(getActiveDownloads()) } - - fun progressFlow(): Flow = state - .flatMapLatest { downloads -> - downloads - .map { download -> - download.progressFlow.drop(1).map { download } - } - .merge() - } - .onStart { emitAll(getActiveDownloads()) } - - private fun getActiveDownloads(): Flow = - _state.value.filter { download -> download.status == MangaDownload.State.DOWNLOADING }.asFlow() - - fun count(predicate: (MangaDownload) -> Boolean) = _state.value.count(predicate) - fun filter(predicate: (MangaDownload) -> Boolean) = _state.value.filter(predicate) - fun find(predicate: (MangaDownload) -> Boolean) = _state.value.find(predicate) - fun groupBy(keySelector: (MangaDownload) -> K) = _state.value.groupBy(keySelector) - fun isEmpty() = _state.value.isEmpty() - fun isNotEmpty() = _state.value.isNotEmpty() - fun none(predicate: (MangaDownload) -> Boolean) = _state.value.none(predicate) - fun toMutableList() = _state.value.toMutableList() -} 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 f11523ae3f..f0e4258339 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 @@ -15,13 +15,11 @@ import androidx.work.WorkManager import androidx.work.WorkQuery import androidx.work.WorkerParameters import androidx.work.workDataOf -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.entries.anime.interactor.UpdateAnime import eu.kanade.domain.entries.anime.model.copyFrom import eu.kanade.domain.entries.anime.model.toSAnime import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.track.anime.model.toDbTrack import eu.kanade.domain.track.anime.model.toDomainTrack import eu.kanade.tachiyomi.R @@ -29,18 +27,10 @@ import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW -import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING -import eu.kanade.tachiyomi.data.preference.DEVICE_NETWORK_NOT_METERED -import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.ENTRY_HAS_UNVIEWED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_COMPLETED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_VIEWED import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.source.UnmeteredSource -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes @@ -64,6 +54,7 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.model.Category +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime import tachiyomi.domain.entries.anime.model.Anime @@ -72,6 +63,16 @@ import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId import tachiyomi.domain.items.episode.model.Episode import tachiyomi.domain.items.episode.model.NoEpisodesException import tachiyomi.domain.library.anime.LibraryAnime +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED +import tachiyomi.domain.source.anime.model.AnimeSourceNotInstalledException +import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack import uy.kohesive.injekt.Injekt @@ -284,7 +285,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa val errorMessage = when (e) { is NoEpisodesException -> context.getString(R.string.no_episodes_error) // failedUpdates will already have the source, don't need to copy it into the message - is AnimeSourceManager.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) 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 273cccd7e2..f473b40186 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 @@ -15,31 +15,21 @@ import androidx.work.WorkManager import androidx.work.WorkQuery import androidx.work.WorkerParameters import androidx.work.workDataOf -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.entries.manga.interactor.UpdateManga import eu.kanade.domain.entries.manga.model.copyFrom import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.track.manga.model.toDbTrack import eu.kanade.domain.track.manga.model.toDomainTrack import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW -import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING -import eu.kanade.tachiyomi.data.preference.DEVICE_NETWORK_NOT_METERED -import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.ENTRY_HAS_UNVIEWED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_COMPLETED -import eu.kanade.tachiyomi.data.preference.ENTRY_NON_VIEWED import eu.kanade.tachiyomi.data.track.EnhancedMangaTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.source.UnmeteredSource -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.util.prepUpdateCover @@ -64,6 +54,7 @@ import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.category.model.Category +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.interactor.GetLibraryManga import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.model.Manga @@ -72,6 +63,16 @@ import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.items.chapter.model.NoChaptersException import tachiyomi.domain.library.manga.LibraryManga +import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_BATTERY_NOT_LOW +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_NETWORK_NOT_METERED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED +import tachiyomi.domain.source.manga.model.SourceNotInstalledException +import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.domain.track.manga.interactor.GetMangaTracks import tachiyomi.domain.track.manga.interactor.InsertMangaTrack import uy.kohesive.injekt.Injekt @@ -284,7 +285,7 @@ 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 MangaSourceManager.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) 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 8fab957e81..6518eee1d3 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 @@ -8,7 +8,6 @@ import android.net.Uri import android.os.Build import androidx.core.content.ContextCompat import androidx.core.net.toUri -import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.Constants import eu.kanade.tachiyomi.data.backup.BackupRestoreService @@ -17,8 +16,6 @@ import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob import eu.kanade.tachiyomi.data.updater.AppUpdateService -import eu.kanade.tachiyomi.source.anime.AnimeSourceManager -import eu.kanade.tachiyomi.source.manga.MangaSourceManager import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity @@ -30,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.runBlocking import tachiyomi.core.util.lang.launchIO +import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.manga.interactor.GetManga @@ -42,6 +40,8 @@ import tachiyomi.domain.items.episode.interactor.GetEpisode import tachiyomi.domain.items.episode.interactor.UpdateEpisode import tachiyomi.domain.items.episode.model.Episode import tachiyomi.domain.items.episode.model.toEpisodeUpdate +import tachiyomi.domain.source.anime.service.AnimeSourceManager +import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 7ce1b39a60..667c2061fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -2,16 +2,6 @@ package eu.kanade.tachiyomi.data.preference import eu.kanade.tachiyomi.R -const val DEVICE_ONLY_ON_WIFI = "wifi" -const val DEVICE_NETWORK_NOT_METERED = "network_not_metered" -const val DEVICE_CHARGING = "ac" -const val DEVICE_BATTERY_NOT_LOW = "battery_not_low" - -// Not to be touched -const val ENTRY_NON_COMPLETED = "manga_ongoing" -const val ENTRY_HAS_UNVIEWED = "manga_fully_read" -const val ENTRY_NON_VIEWED = "manga_started" - const val FLAG_CATEGORIES = "1" const val FLAG_CHAPTERS = "2" const val FLAG_HISTORY = "4" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt index 21d75a72b1..27a75bac15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt @@ -27,16 +27,16 @@ class TrackManager(context: Context) { const val SIMKL = 101L } - val myAnimeList = MyAnimeList(context, MYANIMELIST) - val aniList = Anilist(context, ANILIST) - val kitsu = Kitsu(context, KITSU) - val shikimori = Shikimori(context, SHIKIMORI) - val bangumi = Bangumi(context, BANGUMI) + val myAnimeList = MyAnimeList(MYANIMELIST) + val aniList = Anilist(ANILIST) + val kitsu = Kitsu(KITSU) + val shikimori = Shikimori(SHIKIMORI) + val bangumi = Bangumi(BANGUMI) val komga = Komga(context, KOMGA) - val mangaUpdates = MangaUpdates(context, MANGA_UPDATES) + val mangaUpdates = MangaUpdates(MANGA_UPDATES) val kavita = Kavita(context, KAVITA) - val suwayomi = Suwayomi(context, SUWAYOMI) - val simkl = Simkl(context, SIMKL) + val suwayomi = Suwayomi(SUWAYOMI) + val simkl = Simkl(SIMKL) val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi, komga, mangaUpdates, kavita, suwayomi, simkl) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt index 9ef5faf8eb..e8add8ccb4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt @@ -32,7 +32,8 @@ abstract class TrackService(val id: Long) { @ColorInt abstract fun getLogoColor(): Int - abstract fun getStatus(status: Int): String + @StringRes + abstract fun getStatus(status: Int): Int? abstract suspend fun login(username: String, password: String) 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 dbd73b3cef..66e4ffc38b 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 @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.anilist -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -18,7 +17,7 @@ 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(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Anilist(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -73,19 +72,18 @@ class Anilist(private val context: Context, id: Long) : TrackService(id), MangaT return listOf(WATCHING, PLANNING_ANIME, COMPLETED, REPEATING_ANIME, PAUSED, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - WATCHING -> getString(R.string.watching) - READING -> getString(R.string.reading) - PLANNING -> getString(R.string.plan_to_read) - PLANNING_ANIME -> getString(R.string.plan_to_watch) - COMPLETED -> getString(R.string.completed) - REPEATING -> getString(R.string.repeating) - REPEATING_ANIME -> getString(R.string.repeating_anime) - PAUSED -> getString(R.string.paused) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + WATCHING -> R.string.watching + READING -> R.string.reading + PLANNING -> R.string.plan_to_read + PLANNING_ANIME -> R.string.plan_to_watch + COMPLETED -> R.string.completed + REPEATING -> R.string.repeating + REPEATING_ANIME -> R.string.repeating_anime + PAUSED -> R.string.paused + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index c00441f0b6..6f267d0b9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.bangumi -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Bangumi(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Bangumi(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { private val json: Json by injectLazy() @@ -166,15 +165,14 @@ class Bangumi(private val context: Context, id: Long) : TrackService(id), MangaT return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - PLAN_TO_READ -> getString(R.string.plan_to_read) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + PLAN_TO_READ -> R.string.plan_to_read + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt index c6d91eed97..616b7f217a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kavita/Kavita.kt @@ -37,13 +37,12 @@ class Kavita(private val context: Context, id: Long) : TrackService(id), Enhance override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING 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 8fb84a1750..f1b395470b 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 @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.kitsu -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -17,7 +16,7 @@ import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat -class Kitsu(private val context: Context, id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { +class Kitsu(id: Long) : TrackService(id), AnimeTrackService, MangaTrackService { companion object { const val READING = 1 @@ -52,17 +51,16 @@ class Kitsu(private val context: Context, id: Long) : TrackService(id), AnimeTra return listOf(WATCHING, PLAN_TO_WATCH, COMPLETED, ON_HOLD, DROPPED) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.currently_reading) - WATCHING -> getString(R.string.currently_watching) - PLAN_TO_READ -> getString(R.string.want_to_read) - PLAN_TO_WATCH -> getString(R.string.want_to_watch) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.currently_reading + WATCHING -> R.string.currently_watching + PLAN_TO_READ -> R.string.want_to_read + PLAN_TO_WATCH -> R.string.want_to_watch + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt index 3e2352f896..040f319813 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/komga/Komga.kt @@ -39,13 +39,12 @@ class Komga(private val context: Context, id: Long) : TrackService(id), Enhanced override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index ae7ece0b60..0db8baf025 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.mangaupdates -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -11,7 +10,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch -class MangaUpdates(private val context: Context, id: Long) : TrackService(id), MangaTrackService { +class MangaUpdates(id: Long) : TrackService(id), MangaTrackService { companion object { const val READING_LIST = 0 @@ -36,15 +35,14 @@ class MangaUpdates(private val context: Context, id: Long) : TrackService(id), M return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING_LIST -> getString(R.string.reading_list) - WISH_LIST -> getString(R.string.wish_list) - COMPLETE_LIST -> getString(R.string.complete_list) - ON_HOLD_LIST -> getString(R.string.on_hold_list) - UNFINISHED_LIST -> getString(R.string.unfinished_list) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING_LIST -> R.string.reading_list + WISH_LIST -> R.string.wish_list + COMPLETE_LIST -> R.string.complete_list + ON_HOLD_LIST -> R.string.on_hold_list + UNFINISHED_LIST -> R.string.unfinished_list + else -> null } override fun getReadingStatus(): Int = READING_LIST 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 48a8ff9513..8aa555ab3f 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 @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.myanimelist -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class MyAnimeList(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class MyAnimeList(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -55,19 +54,18 @@ class MyAnimeList(private val context: Context, id: Long) : TrackService(id), Ma return listOf(WATCHING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_WATCH, REWATCHING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - WATCHING -> getString(R.string.watching) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - PLAN_TO_READ -> getString(R.string.plan_to_read) - PLAN_TO_WATCH -> getString(R.string.plan_to_watch) - REREADING -> getString(R.string.repeating) - REWATCHING -> getString(R.string.repeating_anime) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + WATCHING -> R.string.watching + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + PLAN_TO_READ -> R.string.plan_to_read + PLAN_TO_WATCH -> R.string.plan_to_watch + REREADING -> R.string.repeating + REWATCHING -> R.string.repeating_anime + else -> null } override fun getReadingStatus(): Int = READING 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 2e95511234..5a91a59990 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 @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.shikimori -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -16,7 +15,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Shikimori(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { +class Shikimori(id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { companion object { const val READING = 1 @@ -164,16 +163,15 @@ class Shikimori(private val context: Context, id: Long) : TrackService(id), Mang return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - READING -> getString(R.string.reading) - PLAN_TO_READ -> getString(R.string.plan_to_read) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - DROPPED -> getString(R.string.dropped) - REREADING -> getString(R.string.repeating) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + READING -> R.string.reading + PLAN_TO_READ -> R.string.plan_to_read + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + DROPPED -> R.string.dropped + REREADING -> R.string.repeating + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt index 33218d4e84..bf7cd854f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/simkl/Simkl.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.simkl -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -13,7 +12,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -class Simkl(private val context: Context, id: Long) : TrackService(id), AnimeTrackService { +class Simkl(id: Long) : TrackService(id), AnimeTrackService { companion object { const val WATCHING = 1 @@ -99,15 +98,14 @@ class Simkl(private val context: Context, id: Long) : TrackService(id), AnimeTra return listOf(WATCHING, COMPLETED, ON_HOLD, NOT_INTERESTING, PLAN_TO_WATCH) } - override fun getStatus(status: Int): String = with(context) { - when (status) { - WATCHING -> getString(R.string.watching) - PLAN_TO_WATCH -> getString(R.string.plan_to_watch) - COMPLETED -> getString(R.string.completed) - ON_HOLD -> getString(R.string.on_hold) - NOT_INTERESTING -> getString(R.string.not_interesting) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + WATCHING -> R.string.watching + PLAN_TO_WATCH -> R.string.plan_to_watch + COMPLETED -> R.string.completed + ON_HOLD -> R.string.on_hold + NOT_INTERESTING -> R.string.not_interesting + else -> null } override fun getWatchingStatus(): Int = WATCHING diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt index 69f8b9b6d4..28d05a89e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/suwayomi/Suwayomi.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.track.suwayomi -import android.content.Context import android.graphics.Color import androidx.annotation.StringRes import eu.kanade.tachiyomi.R @@ -13,7 +12,7 @@ import eu.kanade.tachiyomi.source.MangaSource import tachiyomi.domain.entries.manga.model.Manga as DomainManga import tachiyomi.domain.track.manga.model.MangaTrack as DomainTrack -class Suwayomi(private val context: Context, id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService { +class Suwayomi(id: Long) : TrackService(id), EnhancedMangaTrackService, MangaTrackService { val api by lazy { TachideskApi() } @StringRes @@ -31,13 +30,12 @@ class Suwayomi(private val context: Context, id: Long) : TrackService(id), Enhan override fun getStatusListManga() = listOf(UNREAD, READING, COMPLETED) - override fun getStatus(status: Int): String = with(context) { - when (status) { - UNREAD -> getString(R.string.unread) - READING -> getString(R.string.reading) - COMPLETED -> getString(R.string.completed) - else -> "" - } + @StringRes + override fun getStatus(status: Int): Int? = when (status) { + UNREAD -> R.string.unread + READING -> R.string.reading + COMPLETED -> R.string.completed + else -> null } override fun getReadingStatus(): Int = READING diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt similarity index 66% rename from app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceManager.kt rename to app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt index e8e6828bc0..ebcdb44edf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AndroidAnimeSourceManager.kt @@ -1,12 +1,8 @@ package eu.kanade.tachiyomi.source.anime import android.content.Context -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeSource -import eu.kanade.tachiyomi.animesource.model.SAnime -import eu.kanade.tachiyomi.animesource.model.SEpisode -import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -20,18 +16,20 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import tachiyomi.domain.source.anime.model.AnimeSourceData +import tachiyomi.domain.source.anime.model.StubAnimeSource import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository +import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.source.local.entries.anime.LocalAnimeSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.util.concurrent.ConcurrentHashMap -class AnimeSourceManager( +class AndroidAnimeSourceManager( private val context: Context, private val extensionManager: AnimeExtensionManager, private val sourceRepository: AnimeSourceDataRepository, -) { +) : AnimeSourceManager { private val downloadManager: AnimeDownloadManager by injectLazy() private val scope = CoroutineScope(Job() + Dispatchers.IO) @@ -40,8 +38,7 @@ class AnimeSourceManager( private val stubSourcesMap = ConcurrentHashMap() - val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } - val onlineSources: Flow> = catalogueSources.map { it.filterIsInstance() } + override val catalogueSources: Flow> = sourcesMapFlow.map { it.values.filterIsInstance() } init { scope.launch { @@ -77,21 +74,21 @@ class AnimeSourceManager( } } - fun get(sourceKey: Long): AnimeSource? { + override fun get(sourceKey: Long): AnimeSource? { return sourcesMapFlow.value[sourceKey] } - fun getOrStub(sourceKey: Long): AnimeSource { + override fun getOrStub(sourceKey: Long): AnimeSource { return sourcesMapFlow.value[sourceKey] ?: stubSourcesMap.getOrPut(sourceKey) { runBlocking { createStubSource(sourceKey) } } } - fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance() + override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance() - fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance() + override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance() - fun getStubSources(): List { + override fun getStubSources(): List { val onlineSourceIds = getOnlineSources().map { it.id } return stubSourcesMap.values.filterNot { it.id in onlineSourceIds } } @@ -117,37 +114,4 @@ class AnimeSourceManager( } return StubAnimeSource(AnimeSourceData(id, "", "")) } - - @Suppress("OverridingDeprecatedMember") - inner class StubAnimeSource(private val sourceData: AnimeSourceData) : AnimeSource { - - override val id: Long = sourceData.id - - override val name: String = sourceData.name.ifBlank { id.toString() } - - override val lang: String = sourceData.lang - - override suspend fun getAnimeDetails(anime: SAnime): SAnime { - throw getSourceNotInstalledException() - } - - override suspend fun getEpisodeList(anime: SAnime): List { - throw getSourceNotInstalledException() - } - - override suspend fun getVideoList(episode: SEpisode): List