From 5e8c406f7ad0e38e24e34f94f4a1eacb3dea5d0d Mon Sep 17 00:00:00 2001 From: Quickdesh Date: Fri, 17 Feb 2023 04:48:34 -0500 Subject: [PATCH] FIX IT ALL AAAAAAAAAAAAAAA --- .../GetLanguagesWithAnimeSources.kt | 2 +- .../domain/episode/model/EpisodeFilter.kt | 2 +- .../library/service/LibraryPreferences.kt | 12 + .../source/service/SourcePreferences.kt | 2 +- .../track/service/DelayedTrackingUpdateJob.kt | 1 - .../kanade/presentation/anime/AnimeScreen.kt | 3 +- .../anime/AnimeTrackInfoDialogHome.kt | 11 - .../anime/AnimeTrackServiceSearch.kt | 3 - .../animebrowse/AnimeExtensionFilterScreen.kt | 20 +- .../animebrowse/AnimeSourcesFilterScreen.kt | 2 +- .../animebrowse/AnimeSourcesScreen.kt | 2 - .../animebrowse/GlobalAnimeSearchScreen.kt | 2 +- .../animebrowse/MigrateAnimeScreen.kt | 1 - .../animebrowse/MigrateAnimeSearchScreen.kt | 2 +- .../animebrowse/MigrateAnimeSourceScreen.kt | 4 +- .../BrowseAnimeSourceComfortableGrid.kt | 2 +- .../BrowseAnimeSourceCompactGrid.kt | 2 +- .../components/BrowseAnimeSourceList.kt | 2 +- .../animehistory/AnimeHistoryScreen.kt | 31 +- .../animelib/components/AnimelibContent.kt | 1 - .../animelib/components/AnimelibList.kt | 1 + .../animelib/components/AnimelibPager.kt | 8 - .../animeupdates/AnimeUpdatesScreen.kt | 36 +- .../animeupdates/AnimeUpdatesUiItem.kt | 6 +- .../category/AnimeCategoryScreen.kt | 19 +- .../presentation/category/CategoryScreen.kt | 19 +- .../kanade/presentation/components/AppBar.kt | 45 +- .../presentation/components/TabbedScreen.kt | 6 +- .../presentation/history/HistoryScreen.kt | 35 +- .../history/components/HistoryItem.kt | 4 +- .../manga/TrackInfoDialogSelector.kt | 2 +- .../eu/kanade/presentation/more/MoreScreen.kt | 20 +- .../settings/screen/SettingsAdvancedScreen.kt | 6 +- .../settings/screen/SettingsBrowseScreen.kt | 9 + .../settings/screen/SettingsDownloadScreen.kt | 16 + .../settings/screen/SettingsLibraryScreen.kt | 9 +- .../settings/screen/SettingsPlayerScreen.kt | 36 +- .../more/stats/AnimeStatsScreenContent.kt | 159 ++++ ...nContent.kt => MangaStatsScreenContent.kt} | 8 +- .../more/stats/StatsScreenState.kt | 14 +- .../presentation/more/stats/data/StatsData.kt | 22 +- .../presentation/updates/UpdatesScreen.kt | 15 +- .../presentation/updates/UpdatesUiItem.kt | 2 +- .../java/eu/kanade/tachiyomi/AppModule.kt | 5 +- .../java/eu/kanade/tachiyomi/Migrations.kt | 2 +- .../data/animedownload/AnimeDownloadCache.kt | 53 +- .../animedownload/AnimeDownloadManager.kt | 1 - .../data/animedownload/AnimeDownloader.kt | 11 +- .../data/backup/BackupFileValidator.kt | 2 +- .../tachiyomi/data/backup/BackupManager.kt | 42 +- .../tachiyomi/data/backup/BackupRestorer.kt | 30 +- .../data/backup/{full => }/models/Backup.kt | 2 +- .../backup/{full => }/models/BackupAnime.kt | 2 +- .../{full => }/models/BackupAnimeHistory.kt | 2 +- .../{full => }/models/BackupAnimeSource.kt | 2 +- .../{full => }/models/BackupAnimeTracking.kt | 2 +- .../{full => }/models/BackupCategory.kt | 2 +- .../backup/{full => }/models/BackupChapter.kt | 2 +- .../backup/{full => }/models/BackupEpisode.kt | 2 +- .../backup/{full => }/models/BackupHistory.kt | 2 +- .../backup/{full => }/models/BackupManga.kt | 2 +- .../{full => }/models/BackupPreference.kt | 2 +- .../{full => }/models/BackupSerializer.kt | 2 +- .../backup/{full => }/models/BackupSource.kt | 2 +- .../{full => }/models/BackupTracking.kt | 2 +- .../tachiyomi/data/database/models/Anime.kt | 1 - .../data/track/EnhancedAnimeTrackService.kt | 4 - .../tachiyomi/data/track/MangaTrackService.kt | 1 - .../tachiyomi/data/track/anilist/Anilist.kt | 2 +- .../tachiyomi/data/track/kitsu/KitsuModels.kt | 1 + .../glance/AnimeUpdatesGridGlanceWidget.kt | 2 +- .../tachiyomi/ui/HistoryTabsPresenter.kt | 28 - .../tachiyomi/ui/UpdatesTabsController.kt | 63 -- .../tachiyomi/ui/UpdatesTabsPresenter.kt | 28 - .../kanade/tachiyomi/ui/anime/AnimeScreen.kt | 72 +- .../tachiyomi/ui/anime/AnimeScreenModel.kt | 29 +- .../ui/anime/track/AnimeTrackInfoDialog.kt | 6 +- .../ui/animecategory/AnimeCategoryScreen.kt | 82 --- .../ui/animehistory/AnimeHistoryTab.kt | 132 ---- .../ui/animelib/AnimelibScreenModel.kt | 41 +- .../ui/animelib/AnimelibSettingsSheet.kt | 24 +- .../tachiyomi/ui/animelib/AnimelibTab.kt | 48 +- .../ui/animeupdates/AnimeUpdatesTab.kt | 116 --- .../kanade/tachiyomi/ui/browse/BrowseTab.kt | 3 +- .../AnimeExtensionsScreenModel.kt | 1 - .../details/AnimeExtensionDetailsScreen.kt | 184 +---- .../details/SourcePreferencesScreen.kt | 5 +- .../AnimeSourcesFilterScreenModel.kt | 4 +- .../browse/BrowseAnimeSourceScreen.kt | 6 +- .../browse/BrowseAnimeSourceScreenModel.kt | 14 +- .../globalsearch/AnimeSearchScreenModel.kt | 8 +- .../globalsearch/GlobalAnimeSearchScreen.kt | 2 +- .../GlobalAnimeSearchScreenModel.kt | 2 +- .../migration/anime/MigrationAnimeScreen.kt | 4 +- .../MigrateAnimeSourceScreenModel.kt | 2 +- .../search/AnimeSourceSearchScreen.kt | 2 +- .../search/MigrateAnimeSearchScreen.kt | 18 +- .../search/MigrateAnimeSearchScreenModel.kt | 4 +- .../migration/sources/MigrateSourceTab.kt | 2 +- .../source/browse/BrowseSourceScreen.kt | 4 +- .../tachiyomi/ui/category/CategoriesTab.kt | 52 ++ .../tachiyomi/ui/category/CategoryScreen.kt | 82 --- .../anime}/AnimeCategoryScreenModel.kt | 2 +- .../ui/category/anime/AnimeCategoryTab.kt | 84 +++ .../MangaCategoryScreenModel.kt} | 4 +- .../ui/category/manga/MangaCategoryTab.kt | 85 +++ .../tachiyomi/ui/download/DownloadsTab.kt | 55 ++ .../anime/AnimeDownloadQueueScreen.kt | 364 ++++------ .../download/anime/AnimeDownloadQueueTab.kt | 43 ++ .../ui/download/manga/DownloadQueueScreen.kt | 294 -------- ...loadAdapter.kt => MangaDownloadAdapter.kt} | 2 +- ...Holder.kt => MangaDownloadHeaderHolder.kt} | 4 +- ...aderItem.kt => MangaDownloadHeaderItem.kt} | 12 +- ...wnloadHolder.kt => MangaDownloadHolder.kt} | 2 +- .../{DownloadItem.kt => MangaDownloadItem.kt} | 14 +- .../manga/MangaDownloadQueueScreen.kt | 180 +++++ ...el.kt => MangaDownloadQueueScreenModel.kt} | 34 +- .../download/manga/MangaDownloadQueueTab.kt | 43 ++ .../HistoriesTab.kt} | 49 +- .../kanade/tachiyomi/ui/history/HistoryTab.kt | 127 ---- .../anime}/AnimeHistoryScreenModel.kt | 2 +- .../ui/history/anime/AnimeHistoryTab.kt | 133 ++++ .../MangaHistoryScreenModel.kt} | 6 +- .../ui/history/manga/MangaHistoryTab.kt | 124 ++++ .../eu/kanade/tachiyomi/ui/home/HomeScreen.kt | 45 +- .../ui/library/LibraryScreenModel.kt | 3 +- .../kanade/tachiyomi/ui/library/LibraryTab.kt | 15 +- .../kanade/tachiyomi/ui/main/MainActivity.kt | 73 +- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 4 +- .../tachiyomi/ui/manga/MangaScreenModel.kt | 2 +- .../eu/kanade/tachiyomi/ui/more/MoreTab.kt | 56 +- .../tachiyomi/ui/player/ExternalIntents.kt | 322 ++++---- .../eu/kanade/tachiyomi/ui/player/Gestures.kt | 2 +- .../tachiyomi/ui/player/PlayerActivity.kt | 30 +- .../tachiyomi/ui/player/PlayerControlsView.kt | 3 +- .../tachiyomi/ui/player/PlayerViewModel.kt | 77 +- .../PlayerPreferences.kt | 2 +- .../kanade/tachiyomi/ui/stats/StatsScreen.kt | 50 -- .../eu/kanade/tachiyomi/ui/stats/StatsTab.kt | 53 ++ .../ui/stats/anime/AnimeStatsScreenModel.kt | 168 +++++ .../tachiyomi/ui/stats/anime/AnimeStatsTab.kt | 42 ++ .../MangaStatsScreenModel.kt} | 15 +- .../tachiyomi/ui/stats/manga/MangaStatsTab.kt | 42 ++ .../kanade/tachiyomi/ui/updates/UpdatesTab.kt | 101 +-- .../anime}/AnimeUpdatesScreenModel.kt | 42 +- .../ui/updates/anime/AnimeUpdatesTab.kt | 164 +++++ .../MangaUpdatesScreenModel.kt} | 8 +- .../ui/updates/manga/MangaUpdatesTab.kt | 139 ++++ .../widget/TachiyomiBottomNavigationView.kt | 189 ----- .../widget/TachiyomiNavigationRailView.kt | 24 - .../res/drawable/ic_updates_outline_24dp.xml | 9 + .../main/res/layout-sw720dp/main_activity.xml | 80 -- app/src/main/res/menu/main_nav.xml | 8 +- app/src/main/res/menu/main_nav_history.xml | 23 - app/src/main/res/menu/main_nav_no_manga.xml | 23 - app/src/main/res/values-iw/strings.xml | 687 ------------------ i18n/src/main/res/values-bg/strings.xml | 3 - i18n/src/main/res/values-kk/strings.xml | 2 +- i18n/src/main/res/values-lt/strings.xml | 2 +- i18n/src/main/res/values-sk/strings.xml | 30 +- i18n/src/main/res/values-sq/strings.xml | 2 +- i18n/src/main/res/values/strings.xml | 13 +- .../online/AnimeHttpSourceFetcher.kt | 4 +- 163 files changed, 2654 insertions(+), 3423 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/more/stats/AnimeStatsScreenContent.kt rename app/src/main/java/eu/kanade/presentation/more/stats/{StatsScreenContent.kt => MangaStatsScreenContent.kt} (97%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/Backup.kt (95%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupAnime.kt (98%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupAnimeHistory.kt (89%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupAnimeSource.kt (92%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupAnimeTracking.kt (98%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupCategory.kt (94%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupChapter.kt (97%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupEpisode.kt (97%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupHistory.kt (90%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupManga.kt (98%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupPreference.kt (93%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupSerializer.kt (66%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupSource.kt (92%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{full => }/models/BackupTracking.kt (98%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsPresenter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsController.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsPresenter.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreen.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryTab.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesTab.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/{animecategory => category/anime}/AnimeCategoryScreenModel.kt (99%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/category/{CategoryScreenModel.kt => manga/MangaCategoryScreenModel.kt} (98%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryTab.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadsTab.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueTab.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreen.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadAdapter.kt => MangaDownloadAdapter.kt} (86%) rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadHeaderHolder.kt => MangaDownloadHeaderHolder.kt} (87%) rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadHeaderItem.kt => MangaDownloadHeaderItem.kt} (84%) rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadHolder.kt => MangaDownloadHolder.kt} (97%) rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadItem.kt => MangaDownloadItem.kt} (83%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/{DownloadQueueScreenModel.kt => MangaDownloadQueueScreenModel.kt} (88%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/{HistoryTabsController.kt => history/HistoriesTab.kt} (65%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/{animehistory => history/anime}/AnimeHistoryScreenModel.kt (99%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/history/{HistoryScreenModel.kt => manga/MangaHistoryScreenModel.kt} (97%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/player/{setting => settings}/PlayerPreferences.kt (98%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsTab.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/stats/{StatsScreenModel.kt => manga/MangaStatsScreenModel.kt} (93%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/{animeupdates => updates/anime}/AnimeUpdatesScreenModel.kt (92%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt rename app/src/main/java/eu/kanade/tachiyomi/ui/updates/{UpdatesScreenModel.kt => manga/MangaUpdatesScreenModel.kt} (98%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiNavigationRailView.kt create mode 100644 app/src/main/res/drawable/ic_updates_outline_24dp.xml delete mode 100644 app/src/main/res/layout-sw720dp/main_activity.xml delete mode 100644 app/src/main/res/menu/main_nav_history.xml delete mode 100644 app/src/main/res/menu/main_nav_no_manga.xml delete mode 100644 app/src/main/res/values-iw/strings.xml diff --git a/app/src/main/java/eu/kanade/domain/animesource/interactor/GetLanguagesWithAnimeSources.kt b/app/src/main/java/eu/kanade/domain/animesource/interactor/GetLanguagesWithAnimeSources.kt index 5ae3caad1..d36f96301 100644 --- a/app/src/main/java/eu/kanade/domain/animesource/interactor/GetLanguagesWithAnimeSources.kt +++ b/app/src/main/java/eu/kanade/domain/animesource/interactor/GetLanguagesWithAnimeSources.kt @@ -15,7 +15,7 @@ class GetLanguagesWithAnimeSources( fun subscribe(): Flow>> { return combine( preferences.enabledLanguages().changes(), - preferences.disabledSources().changes(), + preferences.disabledAnimeSources().changes(), repository.getOnlineSources(), ) { enabledLanguage, disabledSource, onlineSources -> val sortedSources = onlineSources.sortedWith( diff --git a/app/src/main/java/eu/kanade/domain/episode/model/EpisodeFilter.kt b/app/src/main/java/eu/kanade/domain/episode/model/EpisodeFilter.kt index 60fd274d1..2405c994f 100644 --- a/app/src/main/java/eu/kanade/domain/episode/model/EpisodeFilter.kt +++ b/app/src/main/java/eu/kanade/domain/episode/model/EpisodeFilter.kt @@ -1,8 +1,8 @@ package eu.kanade.domain.episode.model import eu.kanade.domain.anime.model.Anime -import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.anime.model.isLocal +import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.ui.anime.EpisodeItem diff --git a/app/src/main/java/eu/kanade/domain/library/service/LibraryPreferences.kt b/app/src/main/java/eu/kanade/domain/library/service/LibraryPreferences.kt index acd7057b5..00f44c7d0 100644 --- a/app/src/main/java/eu/kanade/domain/library/service/LibraryPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/library/service/LibraryPreferences.kt @@ -50,6 +50,18 @@ class LibraryPreferences( fun filterTracking(name: Int) = preferenceStore.getInt("pref_filter_library_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + fun filterDownloadedAnime() = preferenceStore.getInt("pref_filter_animelib_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + + fun filterUnseen() = preferenceStore.getInt("pref_filter_animelib_unread", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + + fun filterStartedAnime() = preferenceStore.getInt("pref_filter_animelib_started", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + + fun filterBookmarkedAnime() = preferenceStore.getInt("pref_filter_animelib_bookmarked", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + + fun filterCompletedAnime() = preferenceStore.getInt("pref_filter_animelib_completed", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + + fun filterTrackingAnime(name: Int) = preferenceStore.getInt("pref_filter_animelib_tracked_$name", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) + // endregion // region Badges diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 1c3aa1b40..b34657ae0 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -42,5 +42,5 @@ class SourcePreferences( fun searchPinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false) - fun searchAnimePinnedSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false) + fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false) } diff --git a/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt index bf3c14185..e9c634a18 100644 --- a/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt @@ -9,7 +9,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters -import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.model.toDbTrack diff --git a/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt index 6e50286a8..27b02cc17 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt @@ -71,7 +71,7 @@ import eu.kanade.tachiyomi.animesource.getNameForAnimeInfo import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.ui.anime.AnimeScreenState import eu.kanade.tachiyomi.ui.anime.EpisodeItem -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -252,6 +252,7 @@ private fun AnimeScreenSmallImpl( onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, onClickMigrate = onMigrateClicked, + changeAnimeSkipIntro = changeAnimeSkipIntro, actionModeCounter = episodes.count { it.selected }, onSelectAll = { onAllEpisodeSelected(true) }, onInvertSelection = { onInvertSelection() }, diff --git a/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackInfoDialogHome.kt index e1c95e101..56e0cda6a 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackInfoDialogHome.kt @@ -2,7 +2,6 @@ package eu.kanade.presentation.anime import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -10,27 +9,19 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.MoreVert -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -39,11 +30,9 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.components.Divider -import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.TrackLogoIcon import eu.kanade.presentation.components.VerticalDivider import eu.kanade.presentation.manga.TrackDetailsItem diff --git a/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackServiceSearch.kt b/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackServiceSearch.kt index 2fa62bfef..348d2c243 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackServiceSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/AnimeTrackServiceSearch.kt @@ -5,10 +5,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeExtensionFilterScreen.kt index 4f12c650b..eb3de734c 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeExtensionFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeExtensionFilterScreen.kt @@ -13,13 +13,13 @@ import eu.kanade.presentation.components.FastScrollLazyColumn import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.browse.animeextension.AnimeExtensionFilterState +import eu.kanade.tachiyomi.ui.browse.extension.ExtensionFilterState import eu.kanade.tachiyomi.util.system.LocaleHelper @Composable fun AnimeExtensionFilterScreen( navigateUp: () -> Unit, - state: AnimeExtensionFilterState.Success, + state: ExtensionFilterState.Success, onClickToggle: (String) -> Unit, ) { Scaffold( @@ -31,13 +31,13 @@ fun AnimeExtensionFilterScreen( ) }, ) { contentPadding -> - if (state.isEmpty) { - EmptyScreen( - textResource = R.string.empty_screen, - modifier = Modifier.padding(contentPadding), - ) - return@Scaffold - } + if (state.isEmpty) { + EmptyScreen( + textResource = R.string.empty_screen, + modifier = Modifier.padding(contentPadding), + ) + return@Scaffold + } AnimeExtensionFilterContent( contentPadding = contentPadding, state = state, @@ -49,7 +49,7 @@ fun AnimeExtensionFilterScreen( @Composable private fun AnimeExtensionFilterContent( contentPadding: PaddingValues, - state: AnimeExtensionFilterState.Success, + state: ExtensionFilterState.Success, onClickLang: (String) -> Unit, ) { val context = LocalContext.current diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesFilterScreen.kt index 70c70be8f..47fa688f1 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesFilterScreen.kt @@ -83,7 +83,7 @@ private fun AnimeSourcesFilterContent( AnimeSourcesFilterItem( modifier = Modifier.animateItemPlacement(), source = source, - enabled = "${source.id}" !in state.disabledSources, + isEnabled = "${source.id}" !in state.disabledSources, onClickItem = onClickSource, ) } diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesScreen.kt index 4b5113f6a..bae6b0f37 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/AnimeSourcesScreen.kt @@ -36,8 +36,6 @@ import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.ui.browse.animesource.AnimeSourcesState import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing import eu.kanade.tachiyomi.util.system.LocaleHelper -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.collectLatest @Composable fun AnimeSourcesScreen( diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/GlobalAnimeSearchScreen.kt index 77286e734..890f6b0b2 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/GlobalAnimeSearchScreen.kt @@ -19,8 +19,8 @@ import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.util.padding import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource -import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchState import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult +import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchState import eu.kanade.tachiyomi.util.system.LocaleHelper @Composable diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeScreen.kt index ae9bf20de..5631f9572 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeScreen.kt @@ -10,7 +10,6 @@ import eu.kanade.presentation.anime.components.BaseAnimeListItem import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.FastScrollLazyColumn -import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.browse.migration.anime.MigrateAnimeState diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSearchScreen.kt index 22383ebb0..d4c98cbc8 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSearchScreen.kt @@ -13,8 +13,8 @@ import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.Scaffold import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchState import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.AnimeSearchItemResult +import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchState import eu.kanade.tachiyomi.util.system.LocaleHelper @Composable diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSourceScreen.kt index 6d0df9598..f61c85405 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/MigrateAnimeSourceScreen.kt @@ -39,12 +39,12 @@ import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.secondaryItemAlpha import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.browse.migration.animesources.MigrateAnimeSourcesState +import eu.kanade.tachiyomi.ui.browse.migration.animesources.MigrateAnimeSourceState import eu.kanade.tachiyomi.util.system.copyToClipboard @Composable fun MigrateAnimeSourceScreen( - state: MigrateAnimeSourcesState, + state: MigrateAnimeSourceState, contentPadding: PaddingValues, onClickItem: (AnimeSource) -> Unit, onToggleSortingDirection: () -> Unit, diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceComfortableGrid.kt index 7a7af50a2..af838c764 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceComfortableGrid.kt @@ -13,8 +13,8 @@ import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.AnimeCover -import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.browse.InLibraryBadge +import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.MangaComfortableGridItem import eu.kanade.presentation.util.plus diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceCompactGrid.kt index 6a016e17d..4e046cf4a 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceCompactGrid.kt @@ -13,8 +13,8 @@ import androidx.paging.LoadState import androidx.paging.compose.LazyPagingItems import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.AnimeCover -import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.browse.InLibraryBadge +import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.MangaCompactGridItem import eu.kanade.presentation.util.plus diff --git a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceList.kt b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceList.kt index 6d69122d6..4b38daa84 100644 --- a/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceList.kt +++ b/app/src/main/java/eu/kanade/presentation/animebrowse/components/BrowseAnimeSourceList.kt @@ -10,8 +10,8 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.items import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.AnimeCover -import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.browse.InLibraryBadge +import eu.kanade.presentation.browse.components.BrowseSourceLoadingItem import eu.kanade.presentation.components.CommonMangaItemDefaults import eu.kanade.presentation.components.LazyColumn import eu.kanade.presentation.components.MangaListItem diff --git a/app/src/main/java/eu/kanade/presentation/animehistory/AnimeHistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/animehistory/AnimeHistoryScreen.kt index 2b2caec0f..9e53eb00b 100644 --- a/app/src/main/java/eu/kanade/presentation/animehistory/AnimeHistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animehistory/AnimeHistoryScreen.kt @@ -2,55 +2,32 @@ package eu.kanade.presentation.animehistory import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.DeleteSweep -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import eu.kanade.domain.animehistory.model.AnimeHistoryWithRelations import eu.kanade.presentation.animehistory.components.AnimeHistoryContent -import eu.kanade.presentation.components.AppBarTitle import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.components.SearchToolbar import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryScreenModel -import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryState +import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryScreenModel +import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryState import java.util.Date @Composable fun AnimeHistoryScreen( state: AnimeHistoryState, + contentPadding: PaddingValues, snackbarHostState: SnackbarHostState, - onSearchQueryChange: (String?) -> Unit, onClickCover: (animeId: Long) -> Unit, onClickResume: (animeId: Long, episodeId: Long) -> Unit, onDialogChange: (AnimeHistoryScreenModel.Dialog?) -> Unit, ) { Scaffold( - topBar = { scrollBehavior -> - SearchToolbar( - titleContent = { AppBarTitle(stringResource(R.string.history)) }, - searchQuery = state.searchQuery, - onChangeSearchQuery = onSearchQueryChange, - actions = { - IconButton(onClick = { onDialogChange(AnimeHistoryScreenModel.Dialog.DeleteAll) }) { - Icon( - Icons.Outlined.DeleteSweep, - contentDescription = stringResource(R.string.pref_clear_history), - ) - } - }, - scrollBehavior = scrollBehavior, - ) - }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { contentPadding -> + ) { _ -> state.list.let { if (it == null) { LoadingScreen(modifier = Modifier.padding(contentPadding)) diff --git a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibContent.kt b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibContent.kt index 8d23aa5e4..7d31ee176 100644 --- a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibContent.kt +++ b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibContent.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember diff --git a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibList.kt b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibList.kt index f6263026c..f9e00064e 100644 --- a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibList.kt +++ b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibList.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.animelib.components import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibPager.kt b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibPager.kt index 8c2bb94c2..912adc791 100644 --- a/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibPager.kt +++ b/app/src/main/java/eu/kanade/presentation/animelib/components/AnimelibPager.kt @@ -1,13 +1,8 @@ package eu.kanade.presentation.animelib.components import android.content.res.Configuration -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -15,16 +10,13 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.unit.dp import eu.kanade.core.prefs.PreferenceMutableState import eu.kanade.domain.animelib.model.AnimelibAnime import eu.kanade.domain.library.model.LibraryDisplayMode -import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.HorizontalPager import eu.kanade.presentation.components.PagerState import eu.kanade.presentation.library.components.LibraryPagerEmptyScreen import eu.kanade.presentation.util.plus -import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.animelib.AnimelibItem @Composable diff --git a/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesScreen.kt index 8dfec4f59..0b601b0fb 100644 --- a/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesScreen.kt @@ -1,18 +1,11 @@ package eu.kanade.presentation.animeupdates -import android.content.Context import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.FlipToBack -import androidx.compose.material.icons.outlined.Refresh -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -30,12 +23,11 @@ import eu.kanade.presentation.components.FastScrollLazyColumn import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.PullRefresh import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.updates.updatesLastUpdatedItem import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload -import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesItem -import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesState -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem +import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesState import kotlinx.coroutines.delay import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt @@ -46,6 +38,7 @@ import kotlin.time.Duration.Companion.seconds fun AnimeUpdateScreen( state: AnimeUpdatesState, snackbarHostState: SnackbarHostState, + contentPadding: PaddingValues, lastUpdated: Long, relativeTime: Int, onClickCover: (AnimeUpdatesItem) -> Unit, @@ -54,10 +47,10 @@ fun AnimeUpdateScreen( onUpdateLibrary: () -> Boolean, onDownloadEpisode: (List, EpisodeDownloadAction) -> Unit, onMultiBookmarkClicked: (List, bookmark: Boolean) -> Unit, - onMultiMarkAsReadClicked: (List, read: Boolean) -> Unit, + onMultiMarkAsSeenClicked: (List, seen: Boolean) -> Unit, onMultiDeleteClicked: (List) -> Unit, onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit, - onOpenEpisode: (List, context: Context, altPlayer: Boolean) -> Unit, + onOpenEpisode: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit, ) { BackHandler(enabled = state.selectionMode, onBack = { onSelectAll(false) }) @@ -69,13 +62,13 @@ fun AnimeUpdateScreen( selected = state.selected, onDownloadEpisode = onDownloadEpisode, onMultiBookmarkClicked = onMultiBookmarkClicked, - onMultiMarkAsSeenClicked = onMultiMarkAsReadClicked, + onMultiMarkAsSeenClicked = onMultiMarkAsSeenClicked, onMultiDeleteClicked = onMultiDeleteClicked, onOpenEpisode = onOpenEpisode, ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { contentPadding -> + ) { when { state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( @@ -105,14 +98,14 @@ fun AnimeUpdateScreen( contentPadding = contentPadding, ) { if (lastUpdated > 0L) { - updatesLastUpdatedItem(lastUpdated) + animeupdatesLastUpdatedItem(lastUpdated) } animeupdatesUiItems( uiModels = state.getUiModel(context, relativeTime), selectionMode = state.selectionMode, onUpdateSelected = onUpdateSelected, onClickCover = onClickCover, - onClickUpdate = { onOpenEpisode }, + onClickUpdate = onOpenEpisode, onDownloadEpisode = onDownloadEpisode, ) } @@ -129,10 +122,9 @@ private fun AnimeUpdatesBottomBar( onMultiBookmarkClicked: (List, bookmark: Boolean) -> Unit, onMultiMarkAsSeenClicked: (List, seen: Boolean) -> Unit, onMultiDeleteClicked: (List) -> Unit, - onOpenEpisode: (List, context: Context, altPlayer: Boolean) -> Unit, + onOpenEpisode: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit, ) { val playerPreferences: PlayerPreferences = Injekt.get() - val context = LocalContext.current AnimeBottomActionMenu( visible = selected.isNotEmpty(), modifier = Modifier.fillMaxWidth(), @@ -157,10 +149,10 @@ private fun AnimeUpdatesBottomBar( onMultiDeleteClicked(selected) }.takeIf { selected.fastAny { it.downloadStateProvider() == AnimeDownload.State.DOWNLOADED } }, onExternalClicked = { - onOpenEpisode(selected, context, true) + onOpenEpisode(selected[0], true) }.takeIf { !playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 }, onInternalClicked = { - onOpenEpisode(selected, context,false) + onOpenEpisode(selected[0], true) }.takeIf { playerPreferences.alwaysUseExternalPlayer().get() && selected.size == 1 }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesUiItem.kt index a8ff91922..e31fe7868 100644 --- a/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/animeupdates/AnimeUpdatesUiItem.kt @@ -43,7 +43,7 @@ import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.selectedBackground import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload -import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesItem +import eu.kanade.tachiyomi.ui.updates.anime.AnimeUpdatesItem import java.util.Date import kotlin.time.Duration.Companion.minutes @@ -82,7 +82,7 @@ fun LazyListScope.animeupdatesUiItems( selectionMode: Boolean, onUpdateSelected: (AnimeUpdatesItem, Boolean, Boolean, Boolean) -> Unit, onClickCover: (AnimeUpdatesItem) -> Unit, - onClickUpdate: (AnimeUpdatesItem) -> Unit, + onClickUpdate: (AnimeUpdatesItem, altPlayer: Boolean) -> Unit, onDownloadEpisode: (List, EpisodeDownloadAction) -> Unit, ) { items( @@ -119,7 +119,7 @@ fun LazyListScope.animeupdatesUiItems( onClick = { when { selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false) - else -> onClickUpdate(updatesItem) + else -> onClickUpdate(updatesItem, false) } }, onClickCover = { onClickCover(updatesItem) }.takeIf { !selectionMode }, diff --git a/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt index 0dc726685..bac62b077 100644 --- a/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt @@ -6,49 +6,40 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import eu.kanade.domain.category.model.Category import eu.kanade.presentation.category.components.CategoryContent import eu.kanade.presentation.category.components.CategoryFloatingActionButton -import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreenState +import eu.kanade.tachiyomi.ui.category.anime.AnimeCategoryScreenState @Composable fun AnimeCategoryScreen( state: AnimeCategoryScreenState.Success, + contentPadding: PaddingValues, onClickCreate: () -> Unit, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, onClickMoveUp: (Category) -> Unit, onClickMoveDown: (Category) -> Unit, - navigateUp: () -> Unit, ) { val lazyListState = rememberLazyListState() Scaffold( - topBar = { scrollBehavior -> - AppBar( - title = stringResource(R.string.action_edit_categories), - navigateUp = navigateUp, - scrollBehavior = scrollBehavior, - ) - }, floatingActionButton = { CategoryFloatingActionButton( lazyListState = lazyListState, onCreate = onClickCreate, ) }, - ) { paddingValues -> + ) { if (state.isEmpty) { EmptyScreen( textResource = R.string.information_empty_category, - modifier = Modifier.padding(paddingValues), + modifier = Modifier.padding(contentPadding), ) return@Scaffold } @@ -56,7 +47,7 @@ fun AnimeCategoryScreen( CategoryContent( categories = state.categories, lazyListState = lazyListState, - paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), + paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), onClickRename = onClickRename, onClickDelete = onClickDelete, onMoveUp = onClickMoveUp, diff --git a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt index 53d53ab2d..5c800260b 100644 --- a/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/CategoryScreen.kt @@ -6,49 +6,40 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import eu.kanade.domain.category.model.Category import eu.kanade.presentation.category.components.CategoryContent import eu.kanade.presentation.category.components.CategoryFloatingActionButton -import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.plus import eu.kanade.presentation.util.topSmallPaddingValues import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.category.CategoryScreenState +import eu.kanade.tachiyomi.ui.category.manga.CategoryScreenState @Composable fun CategoryScreen( state: CategoryScreenState.Success, + contentPadding: PaddingValues, onClickCreate: () -> Unit, onClickRename: (Category) -> Unit, onClickDelete: (Category) -> Unit, onClickMoveUp: (Category) -> Unit, onClickMoveDown: (Category) -> Unit, - navigateUp: () -> Unit, ) { val lazyListState = rememberLazyListState() Scaffold( - topBar = { scrollBehavior -> - AppBar( - title = stringResource(R.string.action_edit_categories), - navigateUp = navigateUp, - scrollBehavior = scrollBehavior, - ) - }, floatingActionButton = { CategoryFloatingActionButton( lazyListState = lazyListState, onCreate = onClickCreate, ) }, - ) { paddingValues -> + ) { if (state.isEmpty) { EmptyScreen( textResource = R.string.information_empty_category, - modifier = Modifier.padding(paddingValues), + modifier = Modifier.padding(contentPadding), ) return@Scaffold } @@ -56,7 +47,7 @@ fun CategoryScreen( CategoryContent( categories = state.categories, lazyListState = lazyListState, - paddingValues = paddingValues + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), + paddingValues = contentPadding + topSmallPaddingValues + PaddingValues(horizontal = MaterialTheme.padding.medium), onClickRename = onClickRename, onClickDelete = onClickDelete, onMoveUp = onClickMoveUp, diff --git a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt index 587b77e74..436d72141 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -1,9 +1,12 @@ package eu.kanade.presentation.components import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -30,6 +33,7 @@ import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -149,22 +153,41 @@ fun AppBar( fun AppBarTitle( title: String?, subtitle: String? = null, + count: Int = 0, ) { - Column { - title?.let { + if (count > 0) { + Row(verticalAlignment = Alignment.CenterVertically) { Text( - text = it, + text = title!!, maxLines = 1, + modifier = Modifier.weight(1f, false), overflow = TextOverflow.Ellipsis, ) + val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f + Pill( + text = "$count", + modifier = Modifier.padding(start = 4.dp), + color = MaterialTheme.colorScheme.onBackground.copy(alpha = pillAlpha), + fontSize = 14.sp, + ) } - subtitle?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) + } else { + Column { + title?.let { + Text( + text = it, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + subtitle?.let { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } } } } @@ -317,8 +340,6 @@ fun SearchToolbar( key("actions") { actions() } }, isActionMode = false, - downloadedOnlyMode = downloadedOnlyMode, - incognitoMode = incognitoMode, scrollBehavior = scrollBehavior, onCancelActionMode = cancelAction, ) 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 f1f46e464..23d7d0705 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt @@ -32,15 +32,12 @@ fun TabbedScreen( startIndex: Int? = null, searchQuery: String? = null, onChangeSearchQuery: (String?) -> Unit = {}, - incognitoMode: Boolean = false, - downloadedOnlyMode: Boolean = false, state: PagerState = rememberPagerState(), scrollable: Boolean = false, searchQueryAnime: String? = null, onChangeSearchQueryAnime: (String?) -> Unit = {}, ) { val scope = rememberCoroutineScope() - val state = rememberPagerState() val snackbarHostState = remember { SnackbarHostState() } LaunchedEffect(startIndex) { @@ -65,9 +62,8 @@ fun TabbedScreen( else -> onChangeSearchQueryAnime } - val appBarTitleText = if (tab.numberTitle == 0) stringResource(titleRes) else tab.numberTitle.toString() SearchToolbar( - titleContent = { AppBarTitle(appBarTitleText) }, + titleContent = { AppBarTitle(stringResource(titleRes), null, tab.numberTitle) }, searchEnabled = searchEnabled, searchQuery = if (searchEnabled) actualQuery else null, onChangeSearchQuery = actualOnChange, diff --git a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt index 210598051..59aebf47b 100644 --- a/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/HistoryScreen.kt @@ -2,55 +2,32 @@ package eu.kanade.presentation.history import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.DeleteSweep -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import eu.kanade.domain.history.model.HistoryWithRelations -import eu.kanade.presentation.components.AppBarTitle import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.components.SearchToolbar import eu.kanade.presentation.history.components.HistoryContent import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.history.HistoryScreenModel -import eu.kanade.tachiyomi.ui.history.HistoryState +import eu.kanade.tachiyomi.ui.history.manga.HistoryState +import eu.kanade.tachiyomi.ui.history.manga.MangaHistoryScreenModel import java.util.Date @Composable fun HistoryScreen( state: HistoryState, + contentPadding: PaddingValues, snackbarHostState: SnackbarHostState, - onSearchQueryChange: (String?) -> Unit, onClickCover: (mangaId: Long) -> Unit, onClickResume: (mangaId: Long, chapterId: Long) -> Unit, - onDialogChange: (HistoryScreenModel.Dialog?) -> Unit, + onDialogChange: (MangaHistoryScreenModel.Dialog?) -> Unit, ) { Scaffold( - topBar = { scrollBehavior -> - SearchToolbar( - titleContent = { AppBarTitle(stringResource(R.string.history)) }, - searchQuery = state.searchQuery, - onChangeSearchQuery = onSearchQueryChange, - actions = { - IconButton(onClick = { onDialogChange(HistoryScreenModel.Dialog.DeleteAll) }) { - Icon( - Icons.Outlined.DeleteSweep, - contentDescription = stringResource(R.string.pref_clear_history), - ) - } - }, - scrollBehavior = scrollBehavior, - ) - }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { contentPadding -> + ) { _ -> state.list.let { if (it == null) { LoadingScreen(modifier = Modifier.padding(contentPadding)) @@ -70,7 +47,7 @@ fun HistoryScreen( contentPadding = contentPadding, onClickCover = { history -> onClickCover(history.mangaId) }, onClickResume = { history -> onClickResume(history.mangaId, history.chapterId) }, - onClickDelete = { item -> onDialogChange(HistoryScreenModel.Dialog.Delete(item)) }, + onClickDelete = { item -> onDialogChange(MangaHistoryScreenModel.Dialog.Delete(item)) }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt b/app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt index 7404f187e..ec25c56b1 100644 --- a/app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt +++ b/app/src/main/java/eu/kanade/presentation/history/components/HistoryItem.kt @@ -102,7 +102,7 @@ fun AnimeHistoryItem( modifier = modifier .clickable(onClick = onClickResume) .height(HISTORY_ITEM_HEIGHT) - .padding(horizontal = horizontalPadding, vertical = 8.dp), + .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), verticalAlignment = Alignment.CenterVertically, ) { MangaCover.Book( @@ -113,7 +113,7 @@ fun AnimeHistoryItem( Column( modifier = Modifier .weight(1f) - .padding(start = horizontalPadding, end = 8.dp), + .padding(start = MaterialTheme.padding.medium, end = MaterialTheme.padding.small), ) { val textStyle = MaterialTheme.typography.bodyMedium Text( diff --git a/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogSelector.kt index c41ac21bb..551d2f713 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/TrackInfoDialogSelector.kt @@ -98,7 +98,7 @@ fun TrackChapterSelector( onDismissRequest: () -> Unit, isAnime: Boolean, ) { - val titleText = when(isAnime) { + val titleText = when (isAnime) { true -> R.string.chapters false -> R.string.episodes } 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 cbee0ff37..f666d025d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/MoreScreen.kt @@ -26,6 +26,7 @@ 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.Divider import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.ScrollbarLazyColumn @@ -33,9 +34,9 @@ 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.ui.more.AnimeDownloadQueueState import eu.kanade.tachiyomi.ui.more.DownloadQueueState import eu.kanade.tachiyomi.util.Constants +import uy.kohesive.injekt.injectLazy @Composable fun MoreScreen( @@ -45,9 +46,8 @@ fun MoreScreen( incognitoMode: Boolean, onIncognitoModeChange: (Boolean) -> Unit, isFDroid: Boolean, - onClickHistory: () -> Unit, + onClickAlt: () -> Unit, onClickDownloadQueue: () -> Unit, - onClickAnimeCategories: () -> Unit, onClickCategories: () -> Unit, onClickStats: () -> Unit, onClickBackupAndRestore: () -> Unit, @@ -101,22 +101,24 @@ fun MoreScreen( item { Divider() } + val libraryPreferences: LibraryPreferences by injectLazy() + item { val bottomNavStyle = libraryPreferences.bottomNavStyle().get() val titleRes = when (bottomNavStyle) { + 0 -> R.string.label_recent_manga 1 -> R.string.label_recent_updates - 2 -> R.string.label_manga - else -> R.string.label_recent_manga + else -> R.string.label_manga } val icon = when (bottomNavStyle) { + 0 -> Icons.Outlined.History 1 -> ImageVector.vectorResource(id = R.drawable.ic_updates_outline_24dp) - 2 -> Icons.Outlined.CollectionsBookmark - else -> Icons.Outlined.History + else -> Icons.Outlined.CollectionsBookmark } TextPreferenceWidget( title = stringResource(titleRes), icon = icon, - onPreferenceClick = onClickHistory, + onPreferenceClick = onClickAlt, ) } @@ -151,7 +153,7 @@ fun MoreScreen( } item { TextPreferenceWidget( - title = stringResource(R.string.categories), + title = stringResource(R.string.general_categories), icon = Icons.Outlined.Label, onPreferenceClick = onClickCategories, ) 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 361c993c0..f528af77a 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 @@ -199,8 +199,10 @@ object SettingsAdvancedScreen : SearchableSettings { Preference.PreferenceItem.TextPreference( title = stringResource(R.string.pref_invalidate_download_cache), subtitle = stringResource(R.string.pref_invalidate_download_cache_summary), - onClick = { Injekt.get().invalidateCache() - Injekt.get().invalidateCache() }, + onClick = { + Injekt.get().invalidateCache() + Injekt.get().invalidateCache() + }, ), Preference.PreferenceItem.TextPreference( title = stringResource(R.string.pref_clear_database), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt index 2e0c8f9cf..ba677273e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt @@ -51,6 +51,15 @@ object SettingsBrowseScreen : SearchableSettings { ), ), ), + Preference.PreferenceGroup( + title = stringResource(R.string.action_global_search), + preferenceItems = listOf( + Preference.PreferenceItem.SwitchPreference( + pref = sourcePreferences.searchPinnedAnimeSourcesOnly(), + title = stringResource(R.string.pref_search_pinned_sources_only), + ), + ), + ), Preference.PreferenceGroup( title = stringResource(R.string.pref_category_nsfw_content), preferenceItems = listOf( 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 e72a20e9f..dad6bd904 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 @@ -103,6 +103,7 @@ object SettingsDownloadScreen : SearchableSettings { } val defaultDirPair = rememberDefaultDownloadDir() + val externalDownloaderDirPair = rememberExternalDownloaderDownloadDir() val customDirEntryKey = currentDir.takeIf { it != defaultDirPair.first } ?: "custom" return Preference.PreferenceItem.ListPreference( @@ -115,6 +116,7 @@ object SettingsDownloadScreen : SearchableSettings { }, entries = mapOf( defaultDirPair, + externalDownloaderDirPair, customDirEntryKey to stringResource(R.string.custom_dir), ), onValueChanged = { @@ -129,6 +131,20 @@ object SettingsDownloadScreen : SearchableSettings { @Composable private fun rememberDefaultDownloadDir(): Pair { + val appName = stringResource(R.string.app_name) + return remember { + val file = UniFile.fromFile( + File( + "${Environment.getExternalStorageDirectory().absolutePath}${File.separator}$appName", + "downloads", + ), + )!! + file.uri.toString() + "\n" to file.filePath!! + } + } + + @Composable + private fun rememberExternalDownloaderDownloadDir(): Pair { val appName = stringResource(R.string.app_name) return remember { val file = UniFile.fromFile( 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 e30f63c19..0887faf88 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 @@ -35,8 +35,8 @@ 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.interactor.GetAnimeCategories import com.commandiron.wheel_picker_compose.WheelPicker +import eu.kanade.domain.category.interactor.GetAnimeCategories import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.ResetCategoryFlags import eu.kanade.domain.category.model.Category @@ -55,8 +55,7 @@ import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen -import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import uy.kohesive.injekt.Injekt @@ -155,7 +154,7 @@ object SettingsLibraryScreen : SearchableSettings { count = userAnimeCategoriesCount, userAnimeCategoriesCount, ), - onClick = { navigator.push(AnimeCategoryScreen()) }, + onClick = { navigator.push(CategoriesTab(false)) }, ), Preference.PreferenceItem.ListPreference( pref = libraryPreferences.defaultAnimeCategory(), @@ -170,7 +169,7 @@ object SettingsLibraryScreen : SearchableSettings { count = userCategoriesCount, userCategoriesCount, ), - onClick = { navigator.push(CategoryScreen()) }, + onClick = { navigator.push(CategoriesTab(true)) }, ), Preference.PreferenceItem.ListPreference( pref = libraryPreferences.defaultCategory(), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt index a91ce27c1..16470640c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsPlayerScreen.kt @@ -6,10 +6,7 @@ import android.os.Build import androidx.annotation.StringRes import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.AlertDialog -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -23,14 +20,13 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.res.stringResource -import com.chargemap.compose.numberpicker.NumberPicker +import com.commandiron.wheel_picker_compose.WheelTextPicker import eu.kanade.domain.base.BasePreferences import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.util.preference.asState import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -124,7 +120,7 @@ object SettingsPlayerScreen : SearchableSettings { @Composable private fun getInternalPlayerGroup(playerPreferences: PlayerPreferences, basePreferences: BasePreferences): Preference.PreferenceGroup { val scope = rememberCoroutineScope() - val defaultSkipIntroLength by playerPreferences.skipLengthPreference().stateIn(scope).collectAsState() + val defaultSkipIntroLength by playerPreferences.defaultIntroLength().stateIn(scope).collectAsState() val skipLengthPreference = playerPreferences.skipLengthPreference() val playerSmoothSeek = playerPreferences.playerSmoothSeek() val playerFullscreen = playerPreferences.playerFullscreen() @@ -142,7 +138,7 @@ object SettingsPlayerScreen : SearchableSettings { initialSkipIntroLength = defaultSkipIntroLength, onDismissRequest = { showDialog = false }, onValueChanged = { skipIntroLength -> - playerPreferences.skipLengthPreference().set(skipIntroLength) + playerPreferences.defaultIntroLength().set(skipIntroLength) showDialog = false }, ) @@ -290,15 +286,14 @@ object SettingsPlayerScreen : SearchableSettings { ) } - // TODO: chnage to new wheel Picker @Composable private fun SkipIntroLengthDialog( initialSkipIntroLength: Int, onDismissRequest: () -> Unit, onValueChanged: (skipIntroLength: Int) -> Unit, ) { - var skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) } - + val skipIntroLengthValue by rememberSaveable { mutableStateOf(initialSkipIntroLength) } + var newLength = 0 AlertDialog( onDismissRequest = onDismissRequest, title = { Text(text = stringResource(R.string.pref_intro_length)) }, @@ -308,16 +303,13 @@ object SettingsPlayerScreen : SearchableSettings { modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally, ) { - NumberPicker( - modifier = Modifier - .fillMaxWidth() - .clipToBounds(), - value = skipIntroLengthValue, - onValueChange = { skipIntroLengthValue = it }, - range = 1..255, - label = { it.toString() }, - dividersColor = MaterialTheme.colorScheme.primary, - textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface), + WheelTextPicker( + texts = remember { 1..255 }.map { "$it" }, + onScrollFinished = { + newLength = it + null + }, + startIndex = skipIntroLengthValue, ) } } @@ -328,7 +320,7 @@ object SettingsPlayerScreen : SearchableSettings { } }, confirmButton = { - TextButton(onClick = { onValueChanged(skipIntroLengthValue) }) { + TextButton(onClick = { onValueChanged(newLength) }) { Text(text = stringResource(android.R.string.ok)) } }, diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/AnimeStatsScreenContent.kt b/app/src/main/java/eu/kanade/presentation/more/stats/AnimeStatsScreenContent.kt new file mode 100644 index 000000000..c325f1d94 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/stats/AnimeStatsScreenContent.kt @@ -0,0 +1,159 @@ +package eu.kanade.presentation.more.stats + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CollectionsBookmark +import androidx.compose.material.icons.outlined.LocalLibrary +import androidx.compose.material.icons.outlined.Schedule +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import eu.kanade.core.util.toDurationString +import eu.kanade.presentation.components.LazyColumn +import eu.kanade.presentation.more.stats.components.StatsItem +import eu.kanade.presentation.more.stats.components.StatsOverviewItem +import eu.kanade.presentation.more.stats.components.StatsSection +import eu.kanade.presentation.more.stats.data.StatsData +import eu.kanade.presentation.util.padding +import eu.kanade.tachiyomi.R +import java.util.Locale +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +@Composable +fun AnimeStatsScreenContent( + state: StatsScreenState.SuccessAnime, + paddingValues: PaddingValues, +) { + val statListState = rememberLazyListState() + LazyColumn( + state = statListState, + contentPadding = paddingValues, + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + ) { + item { + OverviewSection(state.overview) + } + item { + TitlesStats(state.titles) + } + item { + EpisodeStats(state.episodes) + } + item { + TrackerStats(state.trackers) + } + } +} + +@Composable +private fun OverviewSection( + data: StatsData.AnimeOverview, +) { + val none = stringResource(R.string.none) + val context = LocalContext.current + val readDurationString = remember(data.totalSeenDuration) { + data.totalSeenDuration + .toDuration(DurationUnit.MILLISECONDS) + .toDurationString(context, fallback = none) + } + StatsSection(R.string.label_overview_section) { + Row { + StatsOverviewItem( + title = data.libraryAnimeCount.toString(), + subtitle = stringResource(R.string.in_library), + icon = Icons.Outlined.CollectionsBookmark, + ) + StatsOverviewItem( + title = data.completedAnimeCount.toString(), + subtitle = stringResource(R.string.label_completed_titles), + icon = Icons.Outlined.LocalLibrary, + ) + StatsOverviewItem( + title = readDurationString, + subtitle = stringResource(R.string.label_watched_duration), + icon = Icons.Outlined.Schedule, + ) + } + } +} + +@Composable +private fun TitlesStats( + data: StatsData.AnimeTitles, +) { + StatsSection(R.string.label_titles_section) { + Row { + StatsItem( + data.globalUpdateItemCount.toString(), + stringResource(R.string.label_titles_in_global_update), + ) + StatsItem( + data.startedAnimeCount.toString(), + stringResource(R.string.label_started), + ) + StatsItem( + data.localAnimeCount.toString(), + stringResource(R.string.label_local), + ) + } + } +} + +@Composable +private fun EpisodeStats( + data: StatsData.Episodes, +) { + StatsSection(R.string.episodes) { + Row { + StatsItem( + data.totalEpisodeCount.toString(), + stringResource(R.string.label_total_chapters), + ) + StatsItem( + data.readEpisodeCount.toString(), + stringResource(R.string.label_watched_episodes), + ) + StatsItem( + data.downloadCount.toString(), + stringResource(R.string.label_downloaded), + ) + } + } +} + +@Composable +private fun TrackerStats( + data: StatsData.Trackers, +) { + val notApplicable = stringResource(R.string.not_applicable) + val meanScoreStr = remember(data.trackedTitleCount, data.meanScore) { + if (data.trackedTitleCount > 0 && !data.meanScore.isNaN()) { + // All other numbers are localized in English + String.format(Locale.ENGLISH, "%.2f โ˜…", data.meanScore) + } else { + notApplicable + } + } + StatsSection(R.string.label_tracker_section) { + Row { + StatsItem( + data.trackedTitleCount.toString(), + stringResource(R.string.label_tracked_titles), + ) + StatsItem( + meanScoreStr, + stringResource(R.string.label_mean_score), + ) + StatsItem( + data.trackerCount.toString(), + stringResource(R.string.label_used), + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt b/app/src/main/java/eu/kanade/presentation/more/stats/MangaStatsScreenContent.kt similarity index 97% rename from app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt rename to app/src/main/java/eu/kanade/presentation/more/stats/MangaStatsScreenContent.kt index 1b89882b2..a95a58689 100644 --- a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/more/stats/MangaStatsScreenContent.kt @@ -26,8 +26,8 @@ import kotlin.time.DurationUnit import kotlin.time.toDuration @Composable -fun StatsScreenContent( - state: StatsScreenState.Success, +fun MangaStatsScreenContent( + state: StatsScreenState.SuccessManga, paddingValues: PaddingValues, ) { val statListState = rememberLazyListState() @@ -53,7 +53,7 @@ fun StatsScreenContent( @Composable private fun OverviewSection( - data: StatsData.Overview, + data: StatsData.MangaOverview, ) { val none = stringResource(R.string.none) val context = LocalContext.current @@ -85,7 +85,7 @@ private fun OverviewSection( @Composable private fun TitlesStats( - data: StatsData.Titles, + data: StatsData.MangaTitles, ) { StatsSection(R.string.label_titles_section) { Row { diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenState.kt b/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenState.kt index ffc770589..cbb776d0e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenState.kt +++ b/app/src/main/java/eu/kanade/presentation/more/stats/StatsScreenState.kt @@ -8,10 +8,18 @@ sealed class StatsScreenState { object Loading : StatsScreenState() @Immutable - data class Success( - val overview: StatsData.Overview, - val titles: StatsData.Titles, + data class SuccessManga( + val overview: StatsData.MangaOverview, + val titles: StatsData.MangaTitles, val chapters: StatsData.Chapters, val trackers: StatsData.Trackers, ) : StatsScreenState() + + @Immutable + data class SuccessAnime( + val overview: StatsData.AnimeOverview, + val titles: StatsData.AnimeTitles, + val episodes: StatsData.Episodes, + val trackers: StatsData.Trackers, + ) : StatsScreenState() } diff --git a/app/src/main/java/eu/kanade/presentation/more/stats/data/StatsData.kt b/app/src/main/java/eu/kanade/presentation/more/stats/data/StatsData.kt index 98143f887..9518a73b7 100644 --- a/app/src/main/java/eu/kanade/presentation/more/stats/data/StatsData.kt +++ b/app/src/main/java/eu/kanade/presentation/more/stats/data/StatsData.kt @@ -2,24 +2,42 @@ package eu.kanade.presentation.more.stats.data sealed class StatsData { - data class Overview( + data class MangaOverview( val libraryMangaCount: Int, val completedMangaCount: Int, val totalReadDuration: Long, ) : StatsData() - data class Titles( + data class AnimeOverview( + val libraryAnimeCount: Int, + val completedAnimeCount: Int, + val totalSeenDuration: Long, + ) : StatsData() + + data class MangaTitles( val globalUpdateItemCount: Int, val startedMangaCount: Int, val localMangaCount: Int, ) : StatsData() + data class AnimeTitles( + val globalUpdateItemCount: Int, + val startedAnimeCount: Int, + val localAnimeCount: Int, + ) : StatsData() + data class Chapters( val totalChapterCount: Int, val readChapterCount: Int, val downloadCount: Int, ) : StatsData() + data class Episodes( + val totalEpisodeCount: Int, + val readEpisodeCount: Int, + val downloadCount: Int, + ) : StatsData() + data class Trackers( val trackedTitleCount: Int, val meanScore: Double, diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index ec8181153..ee0a8358e 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -1,17 +1,11 @@ package eu.kanade.presentation.updates import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.FlipToBack -import androidx.compose.material.icons.outlined.Refresh -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,8 +25,8 @@ import eu.kanade.presentation.components.PullRefresh import eu.kanade.presentation.components.Scaffold import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.ui.updates.UpdatesItem -import eu.kanade.tachiyomi.ui.updates.UpdatesState +import eu.kanade.tachiyomi.ui.updates.manga.UpdatesItem +import eu.kanade.tachiyomi.ui.updates.manga.UpdatesState import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlin.time.Duration.Companion.seconds @@ -41,6 +35,7 @@ import kotlin.time.Duration.Companion.seconds fun UpdateScreen( state: UpdatesState, snackbarHostState: SnackbarHostState, + contentPadding: PaddingValues, lastUpdated: Long, relativeTime: Int, onClickCover: (UpdatesItem) -> Unit, @@ -69,7 +64,7 @@ fun UpdateScreen( ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, - ) { contentPadding -> + ) { when { state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding)) state.items.isEmpty() -> EmptyScreen( diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt index 7f7dee501..642a42b5e 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt @@ -43,7 +43,7 @@ import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.selectedBackground import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.ui.updates.UpdatesItem +import eu.kanade.tachiyomi.ui.updates.manga.UpdatesItem import java.util.Date import kotlin.time.Duration.Companion.minutes diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 43a94ee35..304f1b42c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt @@ -50,7 +50,8 @@ import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.ExternalIntents +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.system.isDevFlavor import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory @@ -194,6 +195,8 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { ImageSaver(app) } + addSingletonFactory { ExternalIntents() } + // Asynchronously init expensive components for a faster cold start ContextCompat.getMainExecutor(app).execute { get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 444b31221..75714fee9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -22,7 +22,7 @@ import eu.kanade.tachiyomi.data.updater.AppUpdateJob import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.preference.minusAssign diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadCache.kt index 7329f63a2..630f8f5db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadCache.kt @@ -60,7 +60,6 @@ class AnimeDownloadCache( .onStart { emit(Unit) } .shareIn(scope, SharingStarted.Eagerly, 1) - /** * The interval after which this cache should be invalidated. 1 hour shouldn't cause major * issues, as the cache is only used for UI feedback. @@ -272,38 +271,38 @@ class AnimeDownloadCache( }?.id } - sourceDirs.values - .map { sourceDir -> - async { - val animeDirs = sourceDir.dir.listFiles().orEmpty() - .filterNot { it.name.isNullOrBlank() } - .associate { it.name!! to AnimeDirectory(it) } + sourceDirs.values + .map { sourceDir -> + async { + val animeDirs = sourceDir.dir.listFiles().orEmpty() + .filterNot { it.name.isNullOrBlank() } + .associate { it.name!! to AnimeDirectory(it) } - sourceDir.animeDirs = ConcurrentHashMap(animeDirs) + sourceDir.animeDirs = ConcurrentHashMap(animeDirs) - animeDirs.values.forEach { animeDir -> - val episodeDirs = animeDir.dir.listFiles().orEmpty() - .mapNotNull { - when { - // Ignore incomplete downloads - it.name?.endsWith(AnimeDownloader.TMP_DIR_SUFFIX) == true -> null - // Folder of images - it.isDirectory -> it.name - // MP4 files - it.isFile && it.name?.endsWith(".mp4") == true -> it.name!!.substringBeforeLast(".mp4") - // MKV files - it.isFile && it.name?.endsWith(".mkv") == true -> it.name!!.substringBeforeLast(".mkv") - // Anything else is irrelevant - else -> null - } + animeDirs.values.forEach { animeDir -> + val episodeDirs = animeDir.dir.listFiles().orEmpty() + .mapNotNull { + when { + // Ignore incomplete downloads + it.name?.endsWith(AnimeDownloader.TMP_DIR_SUFFIX) == true -> null + // Folder of images + it.isDirectory -> it.name + // MP4 files + it.isFile && it.name?.endsWith(".mp4") == true -> it.name!!.substringBeforeLast(".mp4") + // MKV files + it.isFile && it.name?.endsWith(".mkv") == true -> it.name!!.substringBeforeLast(".mkv") + // Anything else is irrelevant + else -> null } - .toMutableSet() + } + .toMutableSet() - animeDir.episodeDirs = episodeDirs - } + animeDir.episodeDirs = episodeDirs } } - .awaitAll() + } + .awaitAll() }.also { it.invokeOnCompletion(onCancelling = true) { exception -> if (exception != null && exception !is CancellationException) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadManager.kt index 66e88445a..2a43d29aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloadManager.kt @@ -93,7 +93,6 @@ class AnimeDownloadManager( return queue.find { it.episode.id == episodeId } } - fun startDownloadNow(episodeId: Long?) { if (episodeId == null) return val download = getQueuedDownloadOrNull(episodeId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloader.kt index 7711bd568..2ab3c7438 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/animedownload/AnimeDownloader.kt @@ -33,7 +33,6 @@ import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.storage.DiskUtil -import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.toFFmpegString import eu.kanade.tachiyomi.util.system.ImageUtil @@ -346,7 +345,7 @@ class AnimeDownloader( val videoObservable = if (download.video == null) { // Pull video from network and add them to download object - download.source.fetchVideoList(download.episode).map { it.first() } + download.source.fetchVideoList(download.episode.toSEpisode()).map { it.first() } .doOnNext { video -> if (video == null) { throw Exception(context.getString(R.string.video_list_empty_error)) @@ -458,13 +457,13 @@ class AnimeDownloader( video.videoUrl = file.uri.path video.progress = 100 download.downloadedImages++ - video.status = Video.READY + video.status = Video.State.READY } .map { video } // Mark this video as error and allow to download the remaining .onErrorReturn { video.progress = 0 - video.status = Video.ERROR + video.status = Video.State.ERROR notifier.onError(it.message, download.episode.name, download.anime.title) video } @@ -479,7 +478,7 @@ class AnimeDownloader( * @param filename the filename of the video. */ private fun downloadVideo(video: Video, download: AnimeDownload, tmpDir: UniFile, filename: String): Observable { - video.status = Video.DOWNLOAD_IMAGE + video.status = Video.State.DOWNLOAD_IMAGE video.progress = 0 var tries = 0 return newObservable(video, download, tmpDir, filename) @@ -632,7 +631,7 @@ class AnimeDownloader( * @param filename the filename of the video. */ private fun downloadVideoExternal(video: Video, source: AnimeHttpSource, tmpDir: UniFile, filename: String): Observable { - video.status = Video.DOWNLOAD_IMAGE + video.status = Video.State.DOWNLOAD_IMAGE video.progress = 0 return Observable.just(tmpDir.createFile("$filename.mp4")).map { try { 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 8d8b39856..69c0446e1 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,7 +4,7 @@ import android.content.Context import android.net.Uri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeSourceManager -import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer +import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.SourceManager import okio.buffer 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 fa69eb639..6c1f7f14f 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 @@ -35,27 +35,27 @@ import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_PREFS import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_PREFS_MASK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupConst.BACKUP_TRACK_MASK -import eu.kanade.tachiyomi.data.backup.full.models.Backup -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnime -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeHistory -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeSource -import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory -import eu.kanade.tachiyomi.data.backup.full.models.BackupManga -import eu.kanade.tachiyomi.data.backup.full.models.BackupPreference -import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer -import eu.kanade.tachiyomi.data.backup.full.models.BackupSource -import eu.kanade.tachiyomi.data.backup.full.models.BooleanPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.FloatPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.IntPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.LongPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.StringPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.StringSetPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.backupAnimeTrackMapper -import eu.kanade.tachiyomi.data.backup.full.models.backupCategoryMapper -import eu.kanade.tachiyomi.data.backup.full.models.backupChapterMapper -import eu.kanade.tachiyomi.data.backup.full.models.backupEpisodeMapper -import eu.kanade.tachiyomi.data.backup.full.models.backupTrackMapper +import eu.kanade.tachiyomi.data.backup.models.Backup +import eu.kanade.tachiyomi.data.backup.models.BackupAnime +import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory +import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSerializer +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue +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.backup.models.backupAnimeTrackMapper +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 import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.Chapter 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 79c811106..277808d40 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 @@ -4,21 +4,21 @@ import android.content.Context import android.net.Uri import androidx.preference.PreferenceManager import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnime -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeHistory -import eu.kanade.tachiyomi.data.backup.full.models.BackupAnimeSource -import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory -import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory -import eu.kanade.tachiyomi.data.backup.full.models.BackupManga -import eu.kanade.tachiyomi.data.backup.full.models.BackupPreference -import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer -import eu.kanade.tachiyomi.data.backup.full.models.BackupSource -import eu.kanade.tachiyomi.data.backup.full.models.BooleanPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.FloatPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.IntPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.LongPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.StringPreferenceValue -import eu.kanade.tachiyomi.data.backup.full.models.StringSetPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.BackupAnime +import eu.kanade.tachiyomi.data.backup.models.BackupAnimeHistory +import eu.kanade.tachiyomi.data.backup.models.BackupAnimeSource +import eu.kanade.tachiyomi.data.backup.models.BackupCategory +import eu.kanade.tachiyomi.data.backup.models.BackupHistory +import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSerializer +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BooleanPreferenceValue +import eu.kanade.tachiyomi.data.backup.models.FloatPreferenceValue +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 import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.Chapter diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt similarity index 95% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index 4855f0f8d..cca140461 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnime.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt index 7837a636e..972c8cce7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnime.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.domain.anime.model.Anime import eu.kanade.tachiyomi.data.database.models.AnimeImpl diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeHistory.kt similarity index 89% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeHistory.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeHistory.kt index e65099230..58b3cb05b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeHistory.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeSource.kt similarity index 92% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeSource.kt index 4b604518f..43a97ce34 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeSource.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.animesource.AnimeSource import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeTracking.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt index e5e7e4397..bdceba584 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupAnimeTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupAnimeTracking.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.data.database.models.AnimeTrack import eu.kanade.tachiyomi.data.database.models.AnimeTrackImpl diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt similarity index 94% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt index dde26daaa..d58a85ce9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupCategory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupCategory.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.domain.category.model.Category import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt index e9efcd9bc..19a81fc60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.data.database.models.ChapterImpl import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupEpisode.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupEpisode.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt index 71a76c5bd..86898cf6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupEpisode.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupEpisode.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.data.database.models.EpisodeImpl import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt similarity index 90% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt index a9f1c93b1..8b47f0d8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupHistory.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index 4cc0873d9..3f6f1d8df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.domain.manga.model.Manga import eu.kanade.tachiyomi.data.database.models.ChapterImpl diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupPreference.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt index bf96b7cdd..791c9706f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupPreference.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt similarity index 66% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt index 55b1c6afc..2e79ebecd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSerializer.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializer diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt similarity index 92% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSource.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt index 8c823f71c..7bf2d0bc3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupSource.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.source.Source import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt index 0780af07c..d6a041a4d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupTracking.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupTracking.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.data.backup.full.models +package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.data.database.models.TrackImpl import kotlinx.serialization.Serializable diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Anime.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Anime.kt index 667a6c5d9..f70d57533 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Anime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Anime.kt @@ -29,7 +29,6 @@ interface Anime : SAnime { var skipIntroLength: Int get() = viewer_flags and 0x000000FF set(skipIntro) = setViewerFlags(skipIntro, 0x000000FF) - } fun Anime.toDomainAnime(): DomainAnime? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedAnimeTrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedAnimeTrackService.kt index c123c75c8..729aa7979 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedAnimeTrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/EnhancedAnimeTrackService.kt @@ -4,10 +4,6 @@ import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.animetrack.model.AnimeTrack import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch -import eu.kanade.domain.manga.model.Manga -import eu.kanade.domain.track.model.Track -import eu.kanade.tachiyomi.data.track.model.TrackSearch -import eu.kanade.tachiyomi.source.Source /** * An Enhanced Track Service will never prompt the user to match a manga with the remote. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTrackService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTrackService.kt index cac351ef6..e64669ab3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTrackService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/MangaTrackService.kt @@ -134,5 +134,4 @@ interface MangaTrackService { } } } - } 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 ce03d18df..49bb7c8e8 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 @@ -15,8 +15,8 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import uy.kohesive.injekt.injectLazy -import eu.kanade.domain.track.model.Track as DomainTrack import eu.kanade.domain.animetrack.model.AnimeTrack as DomainAnimeTrack +import eu.kanade.domain.track.model.Track as DomainTrack class Anilist(private val context: Context, id: Long) : TrackService(id), MangaTrackService, AnimeTrackService { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt index 8c9428044..395120614 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuModels.kt @@ -173,6 +173,7 @@ class KitsuLibAnime(obj: JsonObject, anime: JsonObject) { else -> throw Exception("Unknown status") } } + @Serializable data class OAuth( val access_token: String, diff --git a/app/src/main/java/eu/kanade/tachiyomi/glance/AnimeUpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/glance/AnimeUpdatesGridGlanceWidget.kt index 0c367c318..ad32994f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/glance/AnimeUpdatesGridGlanceWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/glance/AnimeUpdatesGridGlanceWidget.kt @@ -47,8 +47,8 @@ import eu.kanade.data.AnimeDatabaseHandler import eu.kanade.domain.anime.model.AnimeCover import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.security.SecurityPreferences -import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.dpToPx import kotlinx.coroutines.MainScope diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsPresenter.kt deleted file mode 100644 index 55d989887..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsPresenter.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.ui - -import androidx.compose.runtime.getValue -import eu.kanade.domain.base.BasePreferences -import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryPresenter -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.ui.history.HistoryPresenter -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class HistoryTabsPresenter( - preferences: BasePreferences = Injekt.get(), -) : BasePresenter() { - - val animeHistoryPresenter = AnimeHistoryPresenter(presenterScope, view) - val historyPresenter = HistoryPresenter(presenterScope, view) - - val isDownloadOnly: Boolean by preferences.downloadedOnly().asState() - val isIncognitoMode: Boolean by preferences.incognitoMode().asState() - - fun resumeLastChapterRead() { - historyPresenter.resumeLastChapterRead() - } - - fun resumeLastEpisodeSeen() { - animeHistoryPresenter.resumeLastEpisodeSeen() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsController.kt deleted file mode 100644 index 9811ff683..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsController.kt +++ /dev/null @@ -1,63 +0,0 @@ -package eu.kanade.tachiyomi.ui - -import android.Manifest -import android.view.View -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import eu.kanade.domain.library.service.LibraryPreferences -import eu.kanade.presentation.components.PagerState -import eu.kanade.presentation.components.TabbedScreen -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.animeupdates.animeUpdatesTab -import eu.kanade.tachiyomi.ui.base.controller.FullComposeController -import eu.kanade.tachiyomi.ui.base.controller.RootController -import eu.kanade.tachiyomi.ui.base.controller.pushController -import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe -import eu.kanade.tachiyomi.ui.download.anime.AnimeDownloadController -import eu.kanade.tachiyomi.ui.download.manga.DownloadController -import eu.kanade.tachiyomi.ui.main.MainActivity -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class UpdatesTabsController : FullComposeController(), RootController { - - override fun createPresenter() = UpdatesTabsPresenter() - - private val state = PagerState(currentPage = TAB_ANIME) - - @Composable - override fun ComposeContent() { - val libraryPreferences: LibraryPreferences = Injekt.get() - val fromMore = libraryPreferences.bottomNavStyle().get() == 1 - TabbedScreen( - titleRes = R.string.label_recent_updates, - tabs = listOf( - animeUpdatesTab(router, presenter.animeUpdatesPresenter, activity, fromMore), - updatesTab(router, presenter.updatesPresenter, activity, fromMore), - ), - incognitoMode = presenter.isIncognitoMode, - downloadedOnlyMode = presenter.isDownloadOnly, - state = state, - ) - - LaunchedEffect(Unit) { - (activity as? MainActivity)?.ready = true - } - } - - override fun onViewCreated(view: View) { - super.onViewCreated(view) - requestPermissionsSafe(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 301) - } - - fun openDownloadQueue() { - if (state.currentPage == TAB_MANGA) { - router.pushController(DownloadController()) - } else { - router.pushController(AnimeDownloadController()) - } - } -} - -private const val TAB_ANIME = 0 -private const val TAB_MANGA = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsPresenter.kt deleted file mode 100644 index 18ec5dcf9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/UpdatesTabsPresenter.kt +++ /dev/null @@ -1,28 +0,0 @@ -package eu.kanade.tachiyomi.ui - -import android.os.Bundle -import androidx.compose.runtime.getValue -import eu.kanade.domain.base.BasePreferences -import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesPresenter -import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter -import eu.kanade.tachiyomi.ui.updates.UpdatesPresenter -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class UpdatesTabsPresenter( - preferences: BasePreferences = Injekt.get(), -) : BasePresenter() { - - val animeUpdatesPresenter = AnimeUpdatesPresenter(presenterScope, view) - val updatesPresenter = UpdatesPresenter(presenterScope, view) - - val isDownloadOnly: Boolean by preferences.downloadedOnly().asState() - val isIncognitoMode: Boolean by preferences.incognitoMode().asState() - - override fun onCreate(savedState: Bundle?) { - super.onCreate(savedState) - - animeUpdatesPresenter.onCreate() - updatesPresenter.onCreate() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreen.kt index 39a258c67..bf01ccf12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.core.net.toUri -import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.uniqueScreenKey @@ -27,35 +26,35 @@ import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import com.commandiron.wheel_picker_compose.WheelTextPicker import eu.kanade.domain.anime.interactor.SetAnimeViewerFlags -import eu.kanade.domain.episode.model.Episode import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.hasCustomCover +import eu.kanade.domain.episode.model.Episode +import eu.kanade.presentation.anime.AnimeScreen +import eu.kanade.presentation.anime.EpisodeSettingsDialog +import eu.kanade.presentation.anime.components.AnimeCoverDialog +import eu.kanade.presentation.anime.components.DeleteEpisodesDialog import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.DuplicateAnimeDialog import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.NavigatorAdaptiveSheet -import eu.kanade.presentation.anime.EpisodeSettingsDialog -import eu.kanade.presentation.manga.EditCoverAction -import eu.kanade.presentation.anime.AnimeScreen -import eu.kanade.presentation.anime.components.DeleteEpisodesDialog -import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog -import eu.kanade.presentation.anime.components.AnimeCoverDialog import eu.kanade.presentation.manga.BaseSelector -import eu.kanade.presentation.manga.TrackChapterSelector +import eu.kanade.presentation.manga.EditCoverAction +import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.isTabletUi import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.isLocalOrStub import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen +import eu.kanade.tachiyomi.ui.anime.track.AnimeTrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen -import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen +import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.home.HomeScreen -import eu.kanade.tachiyomi.ui.anime.track.AnimeTrackInfoDialogHomeScreen +import eu.kanade.tachiyomi.ui.player.ExternalIntents import eu.kanade.tachiyomi.ui.player.PlayerActivity +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.withIOContext @@ -64,10 +63,7 @@ import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import logcat.LogPriority -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy class AnimeScreen( @@ -116,7 +112,12 @@ class AnimeScreen( snackbarHostState = screenModel.snackbarHostState, isTabletUi = isTabletUi(), onBackClicked = navigator::pop, - onEpisodeClicked = { episode, alt -> openEpisode(context, episode, alt) }, + onEpisodeClicked = { episode, alt -> + scope.launchIO { + openEpisode(context, episode, alt) + } + Unit + }, onDownloadEpisode = screenModel::runEpisodeDownloadActions.takeIf { !successState.source.isLocalOrStub() }, onAddToLibraryClicked = { screenModel.toggleFavorite() @@ -128,14 +129,19 @@ class AnimeScreen( onTagClicked = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onFilterButtonClicked = screenModel::showSettingsDialog, onRefresh = screenModel::fetchAllFromSource, - onContinueWatching = { continueWatching(context, screenModel.getNextUnseenEpisode()) }, + onContinueWatching = { + scope.launchIO { + continueWatching(context, screenModel.getNextUnseenEpisode()) + } + Unit + }, onSearch = { query, global -> scope.launch { performSearch(navigator, query, global) } }, onCoverClicked = screenModel::showCoverDialog, onShareClicked = { shareAnime(context, screenModel.anime, screenModel.source) }.takeIf { isAnimeHttpSource }, onDownloadActionClicked = screenModel::runDownloadAction.takeIf { !successState.source.isLocalOrStub() }, onEditCategoryClicked = screenModel::promptChangeCategories.takeIf { successState.anime.favorite }, onMigrateClicked = { navigator.push(MigrateAnimeSearchScreen(successState.anime.id)) }.takeIf { successState.anime.favorite }, - changeAnimeSkipIntro = {screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite }}, + changeAnimeSkipIntro = { screenModel::showAnimeSkipIntroDialog.takeIf { successState.anime.favorite } }, onMultiBookmarkClicked = screenModel::bookmarkEpisodes, onMultiMarkAsSeenClicked = screenModel::markEpisodesSeen, onMarkPreviousAsSeenClicked = screenModel::markPreviousEpisodeSeen, @@ -152,7 +158,7 @@ class AnimeScreen( ChangeCategoryDialog( initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, - onEditCategories = { navigator.push(AnimeCategoryScreen()) }, + onEditCategories = { navigator.push(CategoriesTab(false)) }, onConfirm = { include, _ -> screenModel.moveAnimeToCategoriesAndAddToLibrary(dialog.anime, include) }, @@ -242,13 +248,24 @@ class AnimeScreen( } } - private fun continueWatching(context: Context, unseenEpisode: Episode?) { + private suspend fun continueWatching(context: Context, unseenEpisode: Episode?) { if (unseenEpisode != null) openEpisode(context, unseenEpisode) } + private fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId)) + } - // TODO: External Player support - private fun openEpisode(context: Context, episode: Episode, alt: Boolean = false) { - context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id)) + private suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId)) + } + + private suspend fun openEpisode(context: Context, episode: Episode, altPlayer: Boolean = false) { + val playerPreferences: PlayerPreferences by injectLazy() + if (playerPreferences.alwaysUseExternalPlayer().get() != altPlayer) { + openEpisodeExternal(context, episode.animeId, episode.id) + } else { + openEpisodeInternal(context, episode.animeId, episode.id) + } } private fun getAnimeUrl(anime_: Anime?, source_: AnimeSource?): String? { @@ -347,6 +364,7 @@ fun ChangeIntroLength( anime: Anime, onDismissRequest: () -> Unit, ) { + val scope = rememberCoroutineScope() val setAnimeViewerFlags: SetAnimeViewerFlags by injectLazy() val titleText = R.string.action_change_intro_length var newLength = 0 @@ -364,10 +382,12 @@ fun ChangeIntroLength( ) }, onConfirm = { - launchIO { + scope.launchIO { setAnimeViewerFlags.awaitSetSkipIntroLength(anime.id, newLength.toLong()) onDismissRequest() - }}, + } + Unit + }, onDismissRequest = onDismissRequest, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreenModel.kt index 98cee7759..06f590b89 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/AnimeScreenModel.kt @@ -15,7 +15,6 @@ import eu.kanade.domain.anime.interactor.GetDuplicateAnimelibAnime import eu.kanade.domain.anime.interactor.SetAnimeEpisodeFlags import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.model.Anime -import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.anime.model.isLocal import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.model.toDbTrack @@ -30,6 +29,7 @@ import eu.kanade.domain.episode.interactor.UpdateEpisode import eu.kanade.domain.episode.model.Episode import eu.kanade.domain.episode.model.EpisodeUpdate import eu.kanade.domain.library.service.LibraryPreferences +import eu.kanade.domain.manga.model.TriStateFilter import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.EpisodeDownloadAction @@ -41,8 +41,8 @@ import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload -import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService import eu.kanade.tachiyomi.data.track.AnimeTrackService +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.network.HttpException @@ -57,7 +57,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes import eu.kanade.tachiyomi.util.system.logcat -import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.catch @@ -123,6 +122,7 @@ class AnimeInfoScreenModel( private val selectedPositions: Array = arrayOf(-1, -1) // first and last selected index in list private val selectedEpisodeIds: HashSet = HashSet() + /** * Helper function to update the UI state only if it's currently in success state */ @@ -252,11 +252,10 @@ class AnimeInfoScreenModel( if (trackPreferences.trackOnAddingToLibrary().get() && loggedServices.isNotEmpty()) { showTrackDialog() } - } + }, ) } - /** * Update favorite status of anime, (removes / adds) anime (to / from) library. */ @@ -610,7 +609,6 @@ class AnimeInfoScreenModel( } } - fun runEpisodeDownloadActions( items: List, action: EpisodeDownloadAction, @@ -789,7 +787,7 @@ class AnimeInfoScreenModel( TriStateFilter.ENABLED_NOT -> Anime.EPISODE_SHOW_NOT_BOOKMARKED } - coroutineScope.launchNonCancellable { + coroutineScope.launchNonCancellable { setAnimeEpisodeFlags.awaitSetBookmarkFilter(anime, flag) } } @@ -829,7 +827,6 @@ class AnimeInfoScreenModel( } } - fun toggleSelection( item: EpisodeItem, selected: Boolean, @@ -841,7 +838,7 @@ class AnimeInfoScreenModel( val selectedIndex = successState.processedEpisodes.indexOfFirst { it.episode.id == item.episode.id } if (selectedIndex < 0) return@apply - val selectedItem = get(selectedIndex ) + val selectedItem = get(selectedIndex) if ((selectedItem.selected && selected) || (!selectedItem.selected && !selected)) return@apply val firstSelection = none { it.selected } @@ -855,10 +852,10 @@ class AnimeInfoScreenModel( } else { // Try to select the items in-between when possible val range: IntRange - if (selectedIndex < selectedPositions[0]) { - range = selectedIndex + 1 until selectedPositions[0] + if (selectedIndex < selectedPositions[0]) { + range = selectedIndex + 1 until selectedPositions[0] selectedPositions[0] = selectedIndex - } else if (selectedIndex > selectedPositions[1]) { + } else if (selectedIndex > selectedPositions[1]) { range = (selectedPositions[1] + 1) until selectedIndex selectedPositions[1] = selectedIndex } else { @@ -876,15 +873,15 @@ class AnimeInfoScreenModel( } } else if (userSelected && !fromLongPress) { if (!selected) { - if (selectedIndex == selectedPositions[0]) { + if (selectedIndex == selectedPositions[0]) { selectedPositions[0] = indexOfFirst { it.selected } - } else if (selectedIndex == selectedPositions[1]) { + } else if (selectedIndex == selectedPositions[1]) { selectedPositions[1] = indexOfLast { it.selected } } } else { - if (selectedIndex < selectedPositions[0]) { + if (selectedIndex < selectedPositions[0]) { selectedPositions[0] = selectedIndex - } else if (selectedIndex > selectedPositions[1]) { + } else if (selectedIndex > selectedPositions[1]) { selectedPositions[1] = selectedIndex } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/track/AnimeTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/track/AnimeTrackInfoDialog.kt index 4eb127a06..12792b4e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/anime/track/AnimeTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/anime/track/AnimeTrackInfoDialog.kt @@ -37,7 +37,6 @@ import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.interactor.GetAnimeWithEpisodes import eu.kanade.domain.animetrack.interactor.DeleteAnimeTrack @@ -45,20 +44,21 @@ import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack import eu.kanade.domain.animetrack.model.toDbTrack import eu.kanade.domain.animetrack.model.toDomainTrack +import eu.kanade.domain.episode.interactor.SyncEpisodesWithTrackServiceTwoWay import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.anime.AnimeTrackInfoDialogHome +import eu.kanade.presentation.anime.AnimeTrackServiceSearch import eu.kanade.presentation.components.AlertDialogContent import eu.kanade.presentation.manga.TrackChapterSelector import eu.kanade.presentation.manga.TrackDateSelector import eu.kanade.presentation.manga.TrackScoreSelector -import eu.kanade.presentation.anime.AnimeTrackServiceSearch import eu.kanade.presentation.manga.TrackStatusSelector import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.data.database.models.AnimeTrack 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.animesource.AnimeSourceManager import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.withIOContext diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreen.kt deleted file mode 100644 index ff3ae6397..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreen.kt +++ /dev/null @@ -1,82 +0,0 @@ -package eu.kanade.tachiyomi.ui.animecategory - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.core.screen.uniqueScreenKey -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.category.AnimeCategoryScreen -import eu.kanade.presentation.category.components.CategoryCreateDialog -import eu.kanade.presentation.category.components.CategoryDeleteDialog -import eu.kanade.presentation.category.components.CategoryRenameDialog -import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.collectLatest - -class AnimeCategoryScreen : Screen { - - override val key = uniqueScreenKey - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val screenModel = rememberScreenModel { AnimeCategoryScreenModel() } - - val state by screenModel.state.collectAsState() - - if (state is AnimeCategoryScreenState.Loading) { - LoadingScreen() - return - } - - val successState = state as AnimeCategoryScreenState.Success - - AnimeCategoryScreen( - state = successState, - onClickCreate = { screenModel.showDialog(AnimeCategoryDialog.Create) }, - onClickRename = { screenModel.showDialog(AnimeCategoryDialog.Rename(it)) }, - onClickDelete = { screenModel.showDialog(AnimeCategoryDialog.Delete(it)) }, - onClickMoveUp = screenModel::moveUp, - onClickMoveDown = screenModel::moveDown, - navigateUp = navigator::pop, - ) - - when (val dialog = successState.dialog) { - null -> {} - AnimeCategoryDialog.Create -> { - CategoryCreateDialog( - onDismissRequest = screenModel::dismissDialog, - onCreate = { screenModel.createCategory(it) }, - ) - } - is AnimeCategoryDialog.Rename -> { - CategoryRenameDialog( - onDismissRequest = screenModel::dismissDialog, - onRename = { screenModel.renameCategory(dialog.category, it) }, - category = dialog.category, - ) - } - is AnimeCategoryDialog.Delete -> { - CategoryDeleteDialog( - onDismissRequest = screenModel::dismissDialog, - onDelete = { screenModel.deleteCategory(dialog.category.id) }, - category = dialog.category, - ) - } - } - - LaunchedEffect(Unit) { - screenModel.events.collectLatest { event -> - if (event is AnimeCategoryEvent.LocalizedMessage) { - context.toast(event.stringRes) - } - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryTab.kt deleted file mode 100644 index db9d30ba9..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryTab.kt +++ /dev/null @@ -1,132 +0,0 @@ -package eu.kanade.tachiyomi.ui.animehistory - -import android.content.Context -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow -import cafe.adriel.voyager.navigator.tab.LocalTabNavigator -import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.domain.episode.model.Episode -import eu.kanade.presentation.animehistory.AnimeHistoryScreen -import eu.kanade.presentation.history.components.HistoryDeleteAllDialog -import eu.kanade.presentation.animehistory.components.AnimeHistoryDeleteDialog -import eu.kanade.presentation.util.Tab -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.anime.AnimeScreen -import eu.kanade.tachiyomi.ui.player.PlayerActivity -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.consumeAsFlow -import uy.kohesive.injekt.injectLazy - -object AnimeHistoryTab : Tab { - - private val snackbarHostState = SnackbarHostState() - - private val resumeLastEpisodeReadEvent = Channel() - - private val playerPreferences: PlayerPreferences by injectLazy() - - override val options: TabOptions - @Composable - get() { - val isSelected = LocalTabNavigator.current.current.key == key - val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter) - return TabOptions( - index = 2u, - title = stringResource(R.string.label_recent_manga), - icon = rememberAnimatedVectorPainter(image, isSelected), - ) - } - - override suspend fun onReselect(navigator: Navigator) { - resumeLastEpisodeReadEvent.send(Unit) - } - - @Composable - override fun Content() { - val navigator = LocalNavigator.currentOrThrow - val context = LocalContext.current - val screenModel = rememberScreenModel { AnimeHistoryScreenModel() } - val state by screenModel.state.collectAsState() - - AnimeHistoryScreen( - state = state, - snackbarHostState = snackbarHostState, - onSearchQueryChange = screenModel::updateSearchQuery, - onClickCover = { navigator.push(AnimeScreen(it)) }, - onClickResume = screenModel::getNextEpisodeForAnime, - onDialogChange = screenModel::setDialog, - ) - - val onDismissRequest = { screenModel.setDialog(null) } - when (val dialog = state.dialog) { - is AnimeHistoryScreenModel.Dialog.Delete -> { - AnimeHistoryDeleteDialog( - onDismissRequest = onDismissRequest, - onDelete = { all -> - if (all) { - screenModel.removeAllFromHistory(dialog.history.animeId) - } else { - screenModel.removeFromHistory(dialog.history) - } - }, - ) - } - is AnimeHistoryScreenModel.Dialog.DeleteAll -> { - HistoryDeleteAllDialog( - onDismissRequest = onDismissRequest, - onDelete = screenModel::removeAllHistory, - ) - } - null -> {} - } - - LaunchedEffect(state.list) { - if (state.list != null) { - (context as? MainActivity)?.ready = true - } - } - - LaunchedEffect(Unit) { - screenModel.events.collectLatest { e -> - when (e) { - AnimeHistoryScreenModel.Event.InternalError -> - snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) - AnimeHistoryScreenModel.Event.HistoryCleared -> - snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed)) - is AnimeHistoryScreenModel.Event.OpenEpisode -> openEpisode(context, e.episode) - } - } - } - - LaunchedEffect(Unit) { - resumeLastEpisodeReadEvent.consumeAsFlow().collectLatest { - openEpisode(context, screenModel.getNextEpisode()) - } - } - } - - // TODO: Add external player setting - suspend fun openEpisode(context: Context, episode: Episode?) { - if (episode != null) { - val intent = PlayerActivity.newIntent(context, episode.animeId, episode.id) - context.startActivity(intent) - } else { - snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode)) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibScreenModel.kt index 6530633dd..e0bdb77a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibScreenModel.kt @@ -15,33 +15,34 @@ import eu.kanade.core.util.fastFilter import eu.kanade.core.util.fastFilterNot import eu.kanade.core.util.fastMapNotNull import eu.kanade.core.util.fastPartition +import eu.kanade.domain.anime.interactor.GetAnimelibAnime +import eu.kanade.domain.anime.interactor.UpdateAnime +import eu.kanade.domain.anime.model.Anime +import eu.kanade.domain.anime.model.AnimeUpdate +import eu.kanade.domain.anime.model.isLocal +import eu.kanade.domain.animehistory.interactor.GetNextEpisodes +import eu.kanade.domain.animelib.model.AnimelibAnime +import eu.kanade.domain.animetrack.interactor.GetTracksPerAnime import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.category.interactor.GetAnimeCategories import eu.kanade.domain.category.interactor.SetAnimeCategories import eu.kanade.domain.category.model.Category import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId import eu.kanade.domain.episode.interactor.SetSeenStatus -import eu.kanade.domain.animehistory.interactor.GetNextEpisodes +import eu.kanade.domain.episode.model.Episode import eu.kanade.domain.library.model.LibrarySort import eu.kanade.domain.library.model.sort import eu.kanade.domain.library.service.LibraryPreferences -import eu.kanade.domain.anime.interactor.GetAnimelibAnime -import eu.kanade.domain.anime.interactor.UpdateAnime -import eu.kanade.domain.anime.model.Anime -import eu.kanade.domain.anime.model.AnimeUpdate -import eu.kanade.domain.anime.model.isLocal -import eu.kanade.domain.animelib.model.AnimelibAnime -import eu.kanade.domain.animetrack.interactor.GetTracksPerAnime -import eu.kanade.domain.episode.model.Episode import eu.kanade.presentation.library.components.LibraryToolbarTitle import eu.kanade.presentation.manga.DownloadAction -import eu.kanade.tachiyomi.data.cache.AnimeCoverCache -import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache -import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager -import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache +import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager +import eu.kanade.tachiyomi.data.cache.AnimeCoverCache +import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.util.episode.getNextUnseen import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchNonCancellable @@ -337,11 +338,11 @@ class AnimelibScreenModel( libraryPreferences.languageBadge().changes(), preferences.downloadedOnly().changes(), - libraryPreferences.filterDownloaded().changes(), - libraryPreferences.filterUnread().changes(), - libraryPreferences.filterStarted().changes(), - libraryPreferences.filterBookmarked().changes(), - libraryPreferences.filterCompleted().changes(), + libraryPreferences.filterDownloadedAnime().changes(), + libraryPreferences.filterUnseen().changes(), + libraryPreferences.filterStartedAnime().changes(), + libraryPreferences.filterBookmarkedAnime().changes(), + libraryPreferences.filterCompletedAnime().changes(), transform = { ItemPreferences( downloadBadge = it[0] as Boolean, @@ -406,10 +407,10 @@ class AnimelibScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedServices = trackManager.services.filter { it.isLogged } + val loggedServices = trackManager.services.filter { it.isLogged && it is AnimeTrackService } return if (loggedServices.isNotEmpty()) { val prefFlows = loggedServices - .map { libraryPreferences.filterTracking(it.id.toInt()).changes() } + .map { libraryPreferences.filterTrackingAnime(it.id.toInt()).changes() } .toTypedArray() combine(*prefFlows) { loggedServices diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibSettingsSheet.kt index bcca623b8..b90b60ac0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibSettingsSheet.kt @@ -126,15 +126,15 @@ class AnimelibSettingsSheet( downloaded.state = State.INCLUDE.value downloaded.enabled = false } else { - downloaded.state = libraryPreferences.filterDownloaded().get() + downloaded.state = libraryPreferences.filterDownloadedAnime().get() } - unseen.state = libraryPreferences.filterUnread().get() - started.state = libraryPreferences.filterStarted().get() - bookmarked.state = libraryPreferences.filterBookmarked().get() - completed.state = libraryPreferences.filterCompleted().get() + unseen.state = libraryPreferences.filterUnseen().get() + started.state = libraryPreferences.filterStartedAnime().get() + bookmarked.state = libraryPreferences.filterBookmarkedAnime().get() + completed.state = libraryPreferences.filterCompletedAnime().get() trackFilters.forEach { trackFilter -> - trackFilter.value.state = libraryPreferences.filterTracking(trackFilter.key.toInt()).get() + trackFilter.value.state = libraryPreferences.filterTrackingAnime(trackFilter.key.toInt()).get() } } @@ -148,15 +148,15 @@ class AnimelibSettingsSheet( } item.state = newState when (item) { - downloaded -> libraryPreferences.filterDownloaded().set(newState) - unseen -> libraryPreferences.filterUnread().set(newState) - started -> libraryPreferences.filterStarted().set(newState) - bookmarked -> libraryPreferences.filterBookmarked().set(newState) - completed -> libraryPreferences.filterCompleted().set(newState) + downloaded -> libraryPreferences.filterDownloadedAnime().set(newState) + unseen -> libraryPreferences.filterUnseen().set(newState) + started -> libraryPreferences.filterStartedAnime().set(newState) + bookmarked -> libraryPreferences.filterBookmarkedAnime().set(newState) + completed -> libraryPreferences.filterCompletedAnime().set(newState) else -> { trackFilters.forEach { trackFilter -> if (trackFilter.value == item) { - libraryPreferences.filterTracking(trackFilter.key.toInt()).set(newState) + libraryPreferences.filterTrackingAnime(trackFilter.key.toInt()).set(newState) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibTab.kt index a5f831495..af1acb6f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/animelib/AnimelibTab.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.animelib +import android.content.Context import androidx.activity.compose.BackHandler import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter @@ -28,31 +29,34 @@ import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.domain.category.model.Category -import eu.kanade.domain.animelib.model.AnimelibAnime -import eu.kanade.domain.library.model.display -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.isLocal +import eu.kanade.domain.animelib.model.AnimelibAnime +import eu.kanade.domain.category.model.Category +import eu.kanade.domain.episode.model.Episode +import eu.kanade.domain.library.model.display +import eu.kanade.domain.library.service.LibraryPreferences +import eu.kanade.presentation.animelib.components.AnimelibContent +import eu.kanade.presentation.components.AnimelibBottomActionMenu import eu.kanade.presentation.components.ChangeCategoryDialog import eu.kanade.presentation.components.DeleteAnimelibAnimeDialog import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreenAction -import eu.kanade.presentation.components.AnimelibBottomActionMenu import eu.kanade.presentation.components.LoadingScreen import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.animelib.components.AnimelibContent import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.manga.components.DownloadCustomAmountDialog import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService +import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.browse.animesource.globalsearch.GlobalAnimeSearchScreen -import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.player.ExternalIntents import eu.kanade.tachiyomi.ui.player.PlayerActivity +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.util.lang.launchIO import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest @@ -72,7 +76,7 @@ object AnimelibTab : Tab { R.string.label_animelib } val isSelected = LocalTabNavigator.current.current.key == key - val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter) + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_animelibrary_leave) return TabOptions( index = 0u, title = stringResource(title), @@ -108,6 +112,23 @@ object AnimelibTab : Tab { scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategoryIndex]) } } + fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId)) + } + + suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId)) + } + + suspend fun openEpisode(episode: Episode) { + val playerPreferences: PlayerPreferences by injectLazy() + if (playerPreferences.alwaysUseExternalPlayer().get()) { + openEpisodeExternal(context, episode.animeId, episode.id) + } else { + openEpisodeInternal(context, episode.animeId, episode.id) + } + } + Scaffold( topBar = { scrollBehavior -> val title = state.getToolbarTitle( @@ -183,13 +204,8 @@ object AnimelibTab : Tab { onContinueWatchingClicked = { it: AnimelibAnime -> scope.launchIO { val episode = screenModel.getNextUnseenEpisode(it.anime) - if (episode != null) { - context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id)) - } else { - snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode)) - } + if (episode != null) openEpisode(episode) } - // TODO: External Intent Unit }.takeIf { state.showAnimeContinueButton }, onToggleSelection = { screenModel.toggleSelection(it) }, @@ -217,7 +233,7 @@ object AnimelibTab : Tab { onDismissRequest = onDismissRequest, onEditCategories = { screenModel.clearSelection() - navigator.push(CategoryScreen()) + navigator.push(CategoriesTab(false)) }, onConfirm = { include, exclude -> screenModel.clearSelection() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesTab.kt deleted file mode 100644 index bd31b4715..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesTab.kt +++ /dev/null @@ -1,116 +0,0 @@ -package eu.kanade.tachiyomi.ui.animeupdates - -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow -import cafe.adriel.voyager.navigator.tab.LocalTabNavigator -import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.presentation.animeupdates.AnimeUpdateScreen -import eu.kanade.presentation.animeupdates.AnimeUpdatesDeleteConfirmationDialog -import eu.kanade.presentation.util.Tab -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.download.anime.AnimeDownloadQueueScreen -import eu.kanade.tachiyomi.ui.home.HomeScreen -import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.anime.AnimeScreen -import eu.kanade.tachiyomi.ui.animeupdates.AnimeUpdatesScreenModel.Event -import kotlinx.coroutines.flow.collectLatest - -object AnimeUpdatesTab : Tab { - - override val options: TabOptions - @Composable - get() { - val isSelected = LocalTabNavigator.current.current.key == key - val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter) - return TabOptions( - index = 1u, - title = stringResource(R.string.label_recent_updates), - icon = rememberAnimatedVectorPainter(image, isSelected), - ) - } - - override suspend fun onReselect(navigator: Navigator) { - navigator.push(AnimeDownloadQueueScreen) - } - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val screenModel = rememberScreenModel { AnimeUpdatesScreenModel() } - val state by screenModel.state.collectAsState() - - AnimeUpdateScreen( - state = state, - snackbarHostState = screenModel.snackbarHostState, - lastUpdated = screenModel.lastUpdated, - relativeTime = screenModel.relativeTime, - onClickCover = { item -> navigator.push(AnimeScreen(item.update.animeId)) }, - onSelectAll = screenModel::toggleAllSelection, - onInvertSelection = screenModel::invertSelection, - onUpdateLibrary = screenModel::updateLibrary, - onDownloadEpisode = screenModel::downloadEpisodes, - onMultiBookmarkClicked = screenModel::bookmarkUpdates, - onMultiMarkAsReadClicked = screenModel::markUpdatesSeen, - onMultiDeleteClicked = screenModel::showConfirmDeleteEpisodes, - onUpdateSelected = screenModel::toggleSelection, - onOpenEpisode = screenModel::openEpisode, - ) - - val onDismissDialog = { screenModel.setDialog(null) } - when (val dialog = state.dialog) { - is AnimeUpdatesScreenModel.Dialog.DeleteConfirmation -> { - AnimeUpdatesDeleteConfirmationDialog( - onDismissRequest = onDismissDialog, - onConfirm = { screenModel.deleteEpisodes(dialog.toDelete) }, - ) - } - null -> {} - } - - LaunchedEffect(Unit) { - screenModel.events.collectLatest { event -> - when (event) { - Event.InternalError -> screenModel.snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) - is Event.LibraryUpdateTriggered -> { - val msg = if (event.started) { - R.string.updating_library - } else { - R.string.update_already_running - } - screenModel.snackbarHostState.showSnackbar(context.getString(msg)) - } - } - } - } - - LaunchedEffect(state.selectionMode) { - HomeScreen.showBottomNav(!state.selectionMode) - } - - LaunchedEffect(state.isLoading) { - if (!state.isLoading) { - (context as? MainActivity)?.ready = true - } - } - DisposableEffect(Unit) { - screenModel.resetNewUpdatesCount() - - onDispose { - screenModel.resetNewUpdatesCount() - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt index f2aef4db9..306673ca6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BrowseTab.kt @@ -36,7 +36,7 @@ data class BrowseTab( val isSelected = LocalTabNavigator.current.current.key == key val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter) return TabOptions( - index = 6u, + index = 3u, title = stringResource(R.string.browse), icon = rememberAnimatedVectorPainter(image, isSelected), ) @@ -68,6 +68,7 @@ data class BrowseTab( onChangeSearchQuery = extensionsScreenModel::search, searchQueryAnime = animeExtensionsQuery, onChangeSearchQueryAnime = animeExtensionsScreenModel::search, + scrollable = true, ) // For local source diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/AnimeExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/AnimeExtensionsScreenModel.kt index 7a8b42e26..cb275f797 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/AnimeExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/AnimeExtensionsScreenModel.kt @@ -220,7 +220,6 @@ data class AnimeExtensionsState( val isEmpty = items.isEmpty() } - sealed interface AnimeExtensionUiModel { sealed interface Header : AnimeExtensionUiModel { data class Resource(@StringRes val textRes: Int) : Header diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/AnimeExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/AnimeExtensionDetailsScreen.kt index 774ce6176..c5128cf5f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/AnimeExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/AnimeExtensionDetailsScreen.kt @@ -21,189 +21,7 @@ data class AnimeExtensionDetailsScreen( @Composable override fun Content() { val context = LocalContext.current - val screenModel = rememberScreenModel { AnimeEpackage eu.kanade.tachiyomi.ui.browse.extension.details - - import android.content.Context - import android.os.Bundle - import android.util.TypedValue - import android.view.View - import androidx.appcompat.view.ContextThemeWrapper - import androidx.compose.foundation.layout.fillMaxSize - import androidx.compose.foundation.layout.padding - import androidx.compose.material.icons.Icons - import androidx.compose.material.icons.outlined.ArrowBack - import androidx.compose.material3.Icon - import androidx.compose.material3.IconButton - import androidx.compose.material3.Text - import androidx.compose.material3.TopAppBar - import androidx.compose.runtime.Composable - import androidx.compose.runtime.getValue - import androidx.compose.runtime.mutableStateOf - import androidx.compose.runtime.saveable.rememberSaveable - import androidx.compose.runtime.setValue - import androidx.compose.ui.Modifier - import androidx.compose.ui.platform.LocalContext - import androidx.compose.ui.res.stringResource - import androidx.compose.ui.viewinterop.AndroidView - import androidx.core.os.bundleOf - import androidx.fragment.app.FragmentActivity - import androidx.fragment.app.FragmentContainerView - import androidx.fragment.app.FragmentManager - import androidx.fragment.app.FragmentTransaction - import androidx.fragment.app.commit - import androidx.lifecycle.lifecycleScope - import androidx.preference.DialogPreference - import androidx.preference.EditTextPreference - import androidx.preference.PreferenceFragmentCompat - import androidx.preference.PreferenceScreen - import androidx.preference.forEach - import androidx.preference.getOnBindEditTextListener - import cafe.adriel.voyager.core.screen.Screen - import cafe.adriel.voyager.core.screen.uniqueScreenKey - import cafe.adriel.voyager.navigator.LocalNavigator - import cafe.adriel.voyager.navigator.currentOrThrow - import eu.kanade.presentation.components.Scaffold - import eu.kanade.tachiyomi.R - import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore - import eu.kanade.tachiyomi.source.ConfigurableSource - import eu.kanade.tachiyomi.source.SourceManager - import eu.kanade.tachiyomi.source.getPreferenceKey - import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito - import uy.kohesive.injekt.Injekt - import uy.kohesive.injekt.api.get - - class SourcePreferencesScreen(val sourceId: Long) : Screen { - - override val key = uniqueScreenKey - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - - Scaffold( - topBar = { - TopAppBar( - title = { Text(text = Injekt.get().get(sourceId)!!.toString()) }, - navigationIcon = { - IconButton(onClick = navigator::pop) { - Icon( - imageVector = Icons.Outlined.ArrowBack, - contentDescription = stringResource(R.string.abc_action_bar_up_description), - ) - } - }, - scrollBehavior = it, - ) - }, - ) { contentPadding -> - FragmentContainer( - fragmentManager = (context as FragmentActivity).supportFragmentManager, - modifier = Modifier - .fillMaxSize() - .padding(contentPadding), - ) { - val fragment = SourcePreferencesFragment.getInstance(sourceId) - add(it, fragment, null) - } - } - } - - /** - * From https://stackoverflow.com/questions/60520145/fragment-container-in-jetpack-compose/70817794#70817794 - */ - @Composable - private fun FragmentContainer( - fragmentManager: FragmentManager, - modifier: Modifier = Modifier, - commit: FragmentTransaction.(containerId: Int) -> Unit, - ) { - val containerId by rememberSaveable { - mutableStateOf(View.generateViewId()) - } - var initialized by rememberSaveable { mutableStateOf(false) } - AndroidView( - modifier = modifier, - factory = { context -> - FragmentContainerView(context) - .apply { id = containerId } - }, - update = { view -> - if (!initialized) { - fragmentManager.commit { commit(view.id) } - initialized = true - } else { - fragmentManager.onContainerAvailable(view) - } - }, - ) - } - - /** Access to package-private method in FragmentManager through reflection */ - private fun FragmentManager.onContainerAvailable(view: FragmentContainerView) { - val method = FragmentManager::class.java.getDeclaredMethod( - "onContainerAvailable", - FragmentContainerView::class.java, - ) - method.isAccessible = true - method.invoke(this, view) - } - } - - class SourcePreferencesFragment : PreferenceFragmentCompat() { - - override fun getContext(): Context? { - val superCtx = super.getContext() ?: return null - val tv = TypedValue() - superCtx.theme.resolveAttribute(R.attr.preferenceTheme, tv, true) - return ContextThemeWrapper(superCtx, tv.resourceId) - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - preferenceScreen = populateScreen() - } - - private fun populateScreen(): PreferenceScreen { - val sourceId = requireArguments().getLong(SOURCE_ID) - val source = Injekt.get().get(sourceId)!! - - check(source is ConfigurableSource) - - val sharedPreferences = requireContext().getSharedPreferences(source.getPreferenceKey(), Context.MODE_PRIVATE) - val dataStore = SharedPreferencesDataStore(sharedPreferences) - preferenceManager.preferenceDataStore = dataStore - - val sourceScreen = preferenceManager.createPreferenceScreen(requireContext()) - source.setupPreferenceScreen(sourceScreen) - sourceScreen.forEach { pref -> - pref.isIconSpaceReserved = false - if (pref is DialogPreference) { - pref.dialogTitle = pref.title - } - - // Apply incognito IME for EditTextPreference - if (pref is EditTextPreference) { - val setListener = pref.getOnBindEditTextListener() - pref.setOnBindEditTextListener { - setListener?.onBindEditText(it) - it.setIncognito(lifecycleScope) - } - } - } - - return sourceScreen - } - - companion object { - private const val SOURCE_ID = "source_id" - - fun getInstance(sourceId: Long): SourcePreferencesFragment { - val fragment = SourcePreferencesFragment() - fragment.arguments = bundleOf(SOURCE_ID to sourceId) - return fragment - } - } - }xtensionDetailsScreenModel(pkgName = pkgName, context = context) } + val screenModel = rememberScreenModel { AnimeExtensionDetailsScreenModel(pkgName = pkgName, context = context) } val state by screenModel.state.collectAsState() if (state.isLoading) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/SourcePreferencesScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/SourcePreferencesScreen.kt index fcbc8ecf6..d0de9007d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/SourcePreferencesScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animeextension/details/SourcePreferencesScreen.kt @@ -41,9 +41,10 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.Scaffold import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore -import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.AnimeSourceManager +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.getPreferenceKey +import eu.kanade.tachiyomi.data.preference.SharedPreferencesDataStore import eu.kanade.tachiyomi.source.getPreferenceKey import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/AnimeSourcesFilterScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/AnimeSourcesFilterScreenModel.kt index 426494353..fb33349c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/AnimeSourcesFilterScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/AnimeSourcesFilterScreenModel.kt @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.animesource import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.animesource.interactor.GetLanguagesWithAnimeSources -import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.animesource.interactor.ToggleAnimeSource import eu.kanade.domain.animesource.model.AnimeSource +import eu.kanade.domain.source.interactor.ToggleLanguage import eu.kanade.domain.source.service.SourcePreferences import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest @@ -27,7 +27,7 @@ class AnimeSourcesFilterScreenModel( combine( getLanguagesWithSources.subscribe(), preferences.enabledLanguages().changes(), - preferences.disabledSources().changes(), + preferences.disabledAnimeSources().changes(), ) { a, b, c -> Triple(a, b, c) } .catch { throwable -> mutableState.update { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreen.kt index 05d1a84d0..7c7e4b11e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreen.kt @@ -50,9 +50,9 @@ import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource -import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing -import eu.kanade.tachiyomi.ui.animecategory.AnimeCategoryScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel.Listing +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.Constants import eu.kanade.tachiyomi.util.lang.launchIO @@ -242,7 +242,7 @@ data class BrowseAnimeSourceScreen( ChangeCategoryDialog( initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, - onEditCategories = { navigator.push(AnimeCategoryScreen()) }, + onEditCategories = { navigator.push(CategoriesTab(false)) }, onConfirm = { include, _ -> screenModel.changeAnimeFavorite(dialog.anime) screenModel.moveAnimeToCategories(dialog.anime, include) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreenModel.kt index 8ea106317..ded543ca8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/browse/BrowseAnimeSourceScreenModel.kt @@ -21,8 +21,8 @@ import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.interactor.GetDuplicateAnimelibAnime import eu.kanade.domain.anime.interactor.NetworkToLocalAnime import eu.kanade.domain.anime.interactor.UpdateAnime -import eu.kanade.domain.anime.model.toAnimeUpdate import eu.kanade.domain.anime.model.Anime +import eu.kanade.domain.anime.model.toAnimeUpdate import eu.kanade.domain.anime.model.toDomainAnime import eu.kanade.domain.animesource.interactor.GetRemoteAnime import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack @@ -299,10 +299,12 @@ class BrowseAnimeSourceScreenModel( // Choose a category else -> { val preselectedIds = getCategories.await(anime.id).map { it.id } - setDialog(Dialog.ChangeAnimeCategory( - anime, - categories.mapAsCheckboxState { it.id in preselectedIds }, - )) + setDialog( + Dialog.ChangeAnimeCategory( + anime, + categories.mapAsCheckboxState { it.id in preselectedIds }, + ), + ) } } } @@ -320,7 +322,7 @@ class BrowseAnimeSourceScreenModel( insertTrack.await(track.toDomainTrack()!!) val chapters = getEpisodeByAnimeId.await(anime.id) - syncEpisodesWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.animeService ) + syncEpisodesWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service.animeService) } } catch (e: Exception) { logcat( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/AnimeSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/AnimeSearchScreenModel.kt index da66f2614..c0c91fe16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/AnimeSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/AnimeSearchScreenModel.kt @@ -9,8 +9,8 @@ import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.interactor.NetworkToLocalAnime import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.model.Anime -import eu.kanade.domain.anime.model.toDomainAnime import eu.kanade.domain.anime.model.toAnimeUpdate +import eu.kanade.domain.anime.model.toDomainAnime import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.animeextension.AnimeExtensionManager import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource @@ -42,7 +42,7 @@ abstract class AnimeSearchScreenModel( protected lateinit var extensionFilter: String private val sources by lazy { getSelectedSources() } - private val pinnedSources by lazy { sourcePreferences.pinnedSources().get() } + private val pinnedSources by lazy { sourcePreferences.pinnedAnimeSources().get() } private val sortComparator = { map: Map -> compareBy( @@ -95,8 +95,8 @@ abstract class AnimeSearchScreenModel( val enabledSources = getEnabledSources() if (filter.isEmpty()) { - val shouldSearchPinnedOnly = sourcePreferences.searchPinnedSourcesOnly().get() - val pinnedSources = sourcePreferences.pinnedSources().get() + val shouldSearchPinnedOnly = sourcePreferences.searchPinnedAnimeSourcesOnly().get() + val pinnedSources = sourcePreferences.pinnedAnimeSources().get() return enabledSources.filter { if (shouldSearchPinnedOnly) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreen.kt index 6f44f8b4e..69853e99c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreen.kt @@ -9,8 +9,8 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.animebrowse.GlobalAnimeSearchScreen -import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreen class GlobalAnimeSearchScreen( val searchQuery: String = "", diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreenModel.kt index 5b473e83a..ee9947ece 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/animesource/globalsearch/GlobalAnimeSearchScreenModel.kt @@ -18,7 +18,7 @@ class GlobalAnimeSearchScreenModel( ) : AnimeSearchScreenModel(GlobalAnimeSearchState(searchQuery = initialQuery)) { val incognitoMode = preferences.incognitoMode() - val lastUsedSourceId = sourcePreferences.lastUsedSource() + val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource() init { extensionFilter = initialExtensionFilter diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrationAnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrationAnimeScreen.kt index 87ab8d5fa..722d5fb52 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrationAnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrationAnimeScreen.kt @@ -12,8 +12,8 @@ import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.animebrowse.MigrateAnimeScreen import eu.kanade.presentation.components.LoadingScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateAnimeSearchScreen import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.flow.collectLatest @@ -38,7 +38,7 @@ data class MigrationAnimeScreen( navigateUp = navigator::pop, title = state.source!!.name, state = state, - onClickItem = { navigator.push(MigrateSearchScreen(it.id)) }, + onClickItem = { navigator.push(MigrateAnimeSearchScreen(it.id)) }, onClickCover = { navigator.push(AnimeScreen(it.id)) }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/animesources/MigrateAnimeSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/animesources/MigrateAnimeSourceScreenModel.kt index 0002aef77..7a1614e2a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/animesources/MigrateAnimeSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/animesources/MigrateAnimeSourceScreenModel.kt @@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.browse.migration.animesources import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.animesource.interactor.GetAnimeSourcesWithFavoriteCount -import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.animesource.model.AnimeSource +import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.logcat diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/AnimeSourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/AnimeSourceSearchScreen.kt index 21b57e4b4..4f90acd18 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/AnimeSourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/AnimeSourceSearchScreen.kt @@ -30,9 +30,9 @@ import eu.kanade.presentation.components.SearchToolbar import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.browse.animesource.browse.BrowseAnimeSourceScreenModel import eu.kanade.tachiyomi.ui.home.HomeScreen -import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.Constants import kotlinx.coroutines.launch diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreen.kt index 60e7a11e1..641bc7894 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreen.kt @@ -27,30 +27,30 @@ import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.domain.category.interactor.GetCategories -import eu.kanade.domain.category.interactor.SetAnimeCategories -import eu.kanade.domain.episode.interactor.UpdateEpisode -import eu.kanade.domain.episode.model.toEpisodeUpdate import eu.kanade.domain.anime.interactor.UpdateAnime import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.anime.model.AnimeUpdate import eu.kanade.domain.anime.model.hasCustomCover import eu.kanade.domain.animetrack.interactor.GetAnimeTracks import eu.kanade.domain.animetrack.interactor.InsertAnimeTrack +import eu.kanade.domain.category.interactor.GetCategories +import eu.kanade.domain.category.interactor.SetAnimeCategories import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId import eu.kanade.domain.episode.interactor.SyncEpisodesWithSource +import eu.kanade.domain.episode.interactor.UpdateEpisode +import eu.kanade.domain.episode.model.toEpisodeUpdate import eu.kanade.presentation.animebrowse.MigrateAnimeSearchScreen import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.core.preference.Preference -import eu.kanade.tachiyomi.core.preference.PreferenceStore -import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService -import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.core.preference.Preference +import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.data.cache.AnimeCoverCache -import eu.kanade.tachiyomi.ui.browse.migration.AnimeMigrationFlags +import eu.kanade.tachiyomi.data.track.EnhancedAnimeTrackService +import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.browse.migration.AnimeMigrationFlags import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import uy.kohesive.injekt.Injekt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreenModel.kt index 3ea767ad4..023e003b5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateAnimeSearchScreenModel.kt @@ -2,9 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.search import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.coroutineScope -import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.model.Anime +import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.AnimeSourceManager @@ -38,7 +38,7 @@ class MigrateAnimeSearchScreenModel( } val incognitoMode = preferences.incognitoMode() - val lastUsedSourceId = sourcePreferences.lastUsedSource() + val lastUsedSourceId = sourcePreferences.lastUsedAnimeSource() override fun getEnabledSources(): List { val enabledLanguages = sourcePreferences.enabledLanguages().get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt index bf5543852..6b3c7a916 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrateSourceTab.kt @@ -25,7 +25,7 @@ fun Screen.migrateSourceTab(): TabContent { val state by screenModel.state.collectAsState() return TabContent( - titleRes = R.string.label_migration_manga, + titleRes = R.string.label_migration, actions = listOf( AppBar.Action( title = stringResource(R.string.migration_help_guide), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index bb3e91cf3..3eee79e1a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -51,7 +51,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing -import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.util.Constants @@ -242,7 +242,7 @@ data class BrowseSourceScreen( ChangeCategoryDialog( initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, - onEditCategories = { navigator.push(CategoryScreen()) }, + onEditCategories = { navigator.push(CategoriesTab(true)) }, onConfirm = { include, _ -> screenModel.changeMangaFavorite(dialog.manga) screenModel.moveMangaToCategories(dialog.manga, include) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt new file mode 100644 index 000000000..a49a29380 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoriesTab.kt @@ -0,0 +1,52 @@ +package eu.kanade.tachiyomi.ui.category + +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions +import eu.kanade.presentation.components.TabbedScreen +import eu.kanade.presentation.util.Tab +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.category.anime.animeCategoryTab +import eu.kanade.tachiyomi.ui.category.manga.mangaCategoryTab +import eu.kanade.tachiyomi.ui.main.MainActivity + +data class CategoriesTab( + private val isManga: Boolean = false, +) : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter) + return TabOptions( + index = 7u, + title = stringResource(R.string.general_categories), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + @Composable + override fun Content() { + val context = LocalContext.current + + TabbedScreen( + titleRes = R.string.general_categories, + tabs = listOf( + animeCategoryTab(), + mangaCategoryTab(), + ), + startIndex = 1.takeIf { isManga }, + ) + + LaunchedEffect(Unit) { + (context as? MainActivity)?.ready = true + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt deleted file mode 100644 index e71f43640..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreen.kt +++ /dev/null @@ -1,82 +0,0 @@ -package eu.kanade.tachiyomi.ui.category - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.core.screen.uniqueScreenKey -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.category.CategoryScreen -import eu.kanade.presentation.category.components.CategoryCreateDialog -import eu.kanade.presentation.category.components.CategoryDeleteDialog -import eu.kanade.presentation.category.components.CategoryRenameDialog -import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.flow.collectLatest - -class CategoryScreen : Screen { - - override val key = uniqueScreenKey - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val screenModel = rememberScreenModel { CategoryScreenModel() } - - val state by screenModel.state.collectAsState() - - if (state is CategoryScreenState.Loading) { - LoadingScreen() - return - } - - val successState = state as CategoryScreenState.Success - - CategoryScreen( - state = successState, - onClickCreate = { screenModel.showDialog(CategoryDialog.Create) }, - onClickRename = { screenModel.showDialog(CategoryDialog.Rename(it)) }, - onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) }, - onClickMoveUp = screenModel::moveUp, - onClickMoveDown = screenModel::moveDown, - navigateUp = navigator::pop, - ) - - when (val dialog = successState.dialog) { - null -> {} - CategoryDialog.Create -> { - CategoryCreateDialog( - onDismissRequest = screenModel::dismissDialog, - onCreate = { screenModel.createCategory(it) }, - ) - } - is CategoryDialog.Rename -> { - CategoryRenameDialog( - onDismissRequest = screenModel::dismissDialog, - onRename = { screenModel.renameCategory(dialog.category, it) }, - category = dialog.category, - ) - } - is CategoryDialog.Delete -> { - CategoryDeleteDialog( - onDismissRequest = screenModel::dismissDialog, - onDelete = { screenModel.deleteCategory(dialog.category.id) }, - category = dialog.category, - ) - } - } - - LaunchedEffect(Unit) { - screenModel.events.collectLatest { event -> - if (event is CategoryEvent.LocalizedMessage) { - context.toast(event.stringRes) - } - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryScreenModel.kt similarity index 99% rename from app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryScreenModel.kt index 9268c6e84..2d46974e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animecategory/AnimeCategoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.animecategory +package eu.kanade.tachiyomi.ui.category.anime import androidx.annotation.StringRes import androidx.compose.runtime.Immutable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryTab.kt new file mode 100644 index 000000000..5d7067d62 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/anime/AnimeCategoryTab.kt @@ -0,0 +1,84 @@ +package eu.kanade.tachiyomi.ui.category.anime + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.category.AnimeCategoryScreen +import eu.kanade.presentation.category.components.CategoryCreateDialog +import eu.kanade.presentation.category.components.CategoryDeleteDialog +import eu.kanade.presentation.category.components.CategoryRenameDialog +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.TabContent +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun Screen.animeCategoryTab(): TabContent { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { AnimeCategoryScreenModel() } + + val state by screenModel.state.collectAsState() + + return TabContent( + titleRes = R.string.label_anime, + searchEnabled = false, + content = { contentPadding, _ -> + if (state is AnimeCategoryScreenState.Loading) { + LoadingScreen() + } else { + val successState = state as AnimeCategoryScreenState.Success + + AnimeCategoryScreen( + state = successState, + contentPadding = contentPadding, + onClickCreate = { screenModel.showDialog(AnimeCategoryDialog.Create) }, + onClickRename = { screenModel.showDialog(AnimeCategoryDialog.Rename(it)) }, + onClickDelete = { screenModel.showDialog(AnimeCategoryDialog.Delete(it)) }, + onClickMoveUp = screenModel::moveUp, + onClickMoveDown = screenModel::moveDown, + ) + + when (val dialog = successState.dialog) { + null -> {} + AnimeCategoryDialog.Create -> { + CategoryCreateDialog( + onDismissRequest = screenModel::dismissDialog, + onCreate = { screenModel.createCategory(it) }, + ) + } + is AnimeCategoryDialog.Rename -> { + CategoryRenameDialog( + onDismissRequest = screenModel::dismissDialog, + onRename = { screenModel.renameCategory(dialog.category, it) }, + category = dialog.category, + ) + } + is AnimeCategoryDialog.Delete -> { + CategoryDeleteDialog( + onDismissRequest = screenModel::dismissDialog, + onDelete = { screenModel.deleteCategory(dialog.category.id) }, + category = dialog.category, + ) + } + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + if (event is AnimeCategoryEvent.LocalizedMessage) { + context.toast(event.stringRes) + } + } + } + } + }, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryScreenModel.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryScreenModel.kt index 19ee7df94..a56d9de15 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.category +package eu.kanade.tachiyomi.ui.category.manga import androidx.annotation.StringRes import androidx.compose.runtime.Immutable @@ -19,7 +19,7 @@ import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class CategoryScreenModel( +class MangaCategoryScreenModel( private val getCategories: GetCategories = Injekt.get(), private val createCategoryWithName: CreateCategoryWithName = Injekt.get(), private val deleteCategory: DeleteCategory = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryTab.kt new file mode 100644 index 000000000..11668e139 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/manga/MangaCategoryTab.kt @@ -0,0 +1,85 @@ +package eu.kanade.tachiyomi.ui.category.manga + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.category.CategoryScreen +import eu.kanade.presentation.category.components.CategoryCreateDialog +import eu.kanade.presentation.category.components.CategoryDeleteDialog +import eu.kanade.presentation.category.components.CategoryRenameDialog +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.TabContent +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.toast +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun Screen.mangaCategoryTab(): TabContent { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { MangaCategoryScreenModel() } + + val state by screenModel.state.collectAsState() + + return TabContent( + titleRes = R.string.label_manga, + searchEnabled = false, + content = { contentPadding, _ -> + + if (state is CategoryScreenState.Loading) { + LoadingScreen() + } else { + val successState = state as CategoryScreenState.Success + + CategoryScreen( + state = successState, + contentPadding = contentPadding, + onClickCreate = { screenModel.showDialog(CategoryDialog.Create) }, + onClickRename = { screenModel.showDialog(CategoryDialog.Rename(it)) }, + onClickDelete = { screenModel.showDialog(CategoryDialog.Delete(it)) }, + onClickMoveUp = screenModel::moveUp, + onClickMoveDown = screenModel::moveDown, + ) + + when (val dialog = successState.dialog) { + null -> {} + CategoryDialog.Create -> { + CategoryCreateDialog( + onDismissRequest = screenModel::dismissDialog, + onCreate = { screenModel.createCategory(it) }, + ) + } + is CategoryDialog.Rename -> { + CategoryRenameDialog( + onDismissRequest = screenModel::dismissDialog, + onRename = { screenModel.renameCategory(dialog.category, it) }, + category = dialog.category, + ) + } + is CategoryDialog.Delete -> { + CategoryDeleteDialog( + onDismissRequest = screenModel::dismissDialog, + onDelete = { screenModel.deleteCategory(dialog.category.id) }, + category = dialog.category, + ) + } + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + if (event is CategoryEvent.LocalizedMessage) { + context.toast(event.stringRes) + } + } + } + } + }, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadsTab.kt new file mode 100644 index 000000000..761e4e864 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadsTab.kt @@ -0,0 +1,55 @@ +package eu.kanade.tachiyomi.ui.download + +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions +import eu.kanade.presentation.components.TabbedScreen +import eu.kanade.presentation.util.Tab +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.download.anime.animeDownloadTab +import eu.kanade.tachiyomi.ui.download.manga.mangaDownloadTab +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.util.storage.DiskUtil + +data class DownloadsTab( + private val isManga: Boolean = false, +) : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter) + return TabOptions( + index = 6u, + title = stringResource(R.string.label_download_queue), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + @Composable + override fun Content() { + val context = LocalContext.current + + TabbedScreen( + titleRes = R.string.label_download_queue, + tabs = listOf( + animeDownloadTab(), + mangaDownloadTab(), + ), + ) + + LaunchedEffect(Unit) { + (context as? MainActivity)?.ready = true + } + + // For local source + DiskUtil.RequestStoragePermission() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt index 0e28aa9e9..789c1de1a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueScreen.kt @@ -1,294 +1,180 @@ package eu.kanade.tachiyomi.ui.download.anime +import android.content.Context import android.view.LayoutInflater import android.view.ViewGroup import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.outlined.Pause import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.ViewCompat import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.recyclerview.widget.LinearLayoutManager -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.ExtendedFloatingActionButton -import eu.kanade.presentation.components.OverflowMenu -import eu.kanade.presentation.components.Pill import eu.kanade.presentation.components.Scaffold import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService import eu.kanade.tachiyomi.databinding.DownloadListBinding import eu.kanade.tachiyomi.util.lang.launchUI +import kotlinx.coroutines.CoroutineScope import kotlin.math.roundToInt -object AnimeDownloadQueueScreen : Screen { - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val scope = rememberCoroutineScope() - val screenModel = rememberScreenModel { AnimeDownloadQueueScreenModel() } - val downloadList by screenModel.state.collectAsState() - val downloadCount by remember { - derivedStateOf { downloadList.sumOf { it.subItems.size } } - } - - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - var fabExpanded by remember { mutableStateOf(true) } - val nestedScrollConnection = remember { - // All this lines just for fab state :/ - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - fabExpanded = available.y >= 0 - return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) - } - - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPreFling(available) - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) - } +@Composable +fun AnimeDownloadQueueScreen( + context: Context, + contentPadding: PaddingValues, + scope: CoroutineScope, + screenModel: AnimeDownloadQueueScreenModel, + downloadList: List, +) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + var fabExpanded by remember { mutableStateOf(true) } + val nestedScrollConnection = remember { + // All this lines just for fab state :/ + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + fabExpanded = available.y >= 0 + return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) } - } - Scaffold( - topBar = { - AppBar( - titleContent = { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(R.string.label_download_queue), - maxLines = 1, - modifier = Modifier.weight(1f, false), - overflow = TextOverflow.Ellipsis, - ) - if (downloadCount > 0) { - val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f - Pill( - text = "$downloadCount", - modifier = Modifier.padding(start = 4.dp), - color = MaterialTheme.colorScheme.onBackground - .copy(alpha = pillAlpha), - fontSize = 14.sp, - ) - } - } - }, - navigateUp = navigator::pop, - actions = { - if (downloadList.isNotEmpty()) { - OverflowMenu { closeMenu -> - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_reorganize_by)) }, - children = { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_order_by_upload_date)) }, - children = { - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_newest)) }, - onClick = { - screenModel.reorderQueue( - { it.download.episode.dateUpload }, - true, - ) - closeMenu() - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_oldest)) }, - onClick = { - screenModel.reorderQueue( - { it.download.episode.dateUpload }, - false, - ) - closeMenu() - }, - ) - }, - ) - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_order_by_episode_number)) }, - children = { - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_asc)) }, - onClick = { - screenModel.reorderQueue( - { it.download.episode.episodeNumber }, - false, - ) - closeMenu() - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_desc)) }, - onClick = { - screenModel.reorderQueue( - { it.download.episode.episodeNumber }, - true, - ) - closeMenu() - }, - ) - }, - ) - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_cancel_all)) }, - onClick = { - screenModel.clearQueue(context) - closeMenu() - }, - ) - } - } - }, - scrollBehavior = scrollBehavior, - ) - }, - floatingActionButton = { - AnimatedVisibility( - visible = downloadList.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - val isRunning by AnimeDownloadService.isRunning.collectAsState() - ExtendedFloatingActionButton( - text = { - val id = if (isRunning) { - R.string.action_pause - } else { - R.string.action_resume - } - Text(text = stringResource(id)) - }, - icon = { - val icon = if (isRunning) { - Icons.Outlined.Pause - } else { - Icons.Filled.PlayArrow - } - Icon(imageVector = icon, contentDescription = null) - }, - onClick = { - if (isRunning) { - AnimeDownloadService.stop(context) - screenModel.pauseDownloads() - } else { - AnimeDownloadService.start(context) - } - }, - expanded = fabExpanded, - modifier = Modifier.navigationBarsPadding(), - ) - } - }, - ) { contentPadding -> - if (downloadList.isEmpty()) { - EmptyScreen( - textResource = R.string.information_no_downloads, - modifier = Modifier.padding(contentPadding), - ) - return@Scaffold + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) } - val density = LocalDensity.current - val layoutDirection = LocalLayoutDirection.current - val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } - val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } - val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } - val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } - Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { - AndroidView( - factory = { context -> - screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context)) - screenModel.adapter = AnimeDownloadAdapter(screenModel.listener) - screenModel.controllerBinding.recycler.adapter = screenModel.adapter - screenModel.adapter?.isHandleDragEnabled = true - screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller - screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager(context) + override suspend fun onPreFling(available: Velocity): Velocity { + return scrollBehavior.nestedScrollConnection.onPreFling(available) + } - ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) - - scope.launchUI { - screenModel.getDownloadStatusFlow() - .collect(screenModel::onStatusChange) - } - scope.launchUI { - screenModel.getDownloadProgressFlow() - .collect(screenModel::onUpdateDownloadedPages) - } - - screenModel.controllerBinding.root - }, - update = { - screenModel.controllerBinding.recycler - .updatePadding( - left = left, - top = top, - right = right, - bottom = bottom, - ) - - screenModel.controllerBinding.fastScroller - .updateLayoutParams { - leftMargin = left - topMargin = top - rightMargin = right - bottomMargin = bottom - } - - screenModel.adapter?.updateDataSet(downloadList) - }, - ) + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) } } } + + Scaffold( + floatingActionButton = { + AnimatedVisibility( + visible = downloadList.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + val isRunning by AnimeDownloadService.isRunning.collectAsState() + ExtendedFloatingActionButton( + text = { + val id = if (isRunning) { + R.string.action_pause + } else { + R.string.action_resume + } + Text(text = stringResource(id)) + }, + icon = { + val icon = if (isRunning) { + Icons.Outlined.Pause + } else { + Icons.Filled.PlayArrow + } + Icon(imageVector = icon, contentDescription = null) + }, + onClick = { + if (isRunning) { + AnimeDownloadService.stop(context) + screenModel.pauseDownloads() + } else { + AnimeDownloadService.start(context) + } + }, + expanded = fabExpanded, + modifier = Modifier.navigationBarsPadding(), + ) + } + }, + ) { + if (downloadList.isEmpty()) { + EmptyScreen( + textResource = R.string.information_no_downloads, + modifier = Modifier.padding(contentPadding), + ) + return@Scaffold + } + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } + val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } + val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } + val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } + + Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { + AndroidView( + factory = { context -> + screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context)) + screenModel.adapter = AnimeDownloadAdapter(screenModel.listener) + screenModel.controllerBinding.recycler.adapter = screenModel.adapter + screenModel.adapter?.isHandleDragEnabled = true + screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller + screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager(context) + + ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) + + scope.launchUI { + screenModel.getDownloadStatusFlow() + .collect(screenModel::onStatusChange) + } + scope.launchUI { + screenModel.getDownloadProgressFlow() + .collect(screenModel::onUpdateDownloadedPages) + } + + screenModel.controllerBinding.root + }, + update = { + screenModel.controllerBinding.recycler + .updatePadding( + left = left, + top = top, + right = right, + bottom = bottom, + ) + + screenModel.controllerBinding.fastScroller + .updateLayoutParams { + leftMargin = left + topMargin = top + rightMargin = right + bottomMargin = bottom + } + + screenModel.adapter?.updateDataSet(downloadList) + }, + ) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueTab.kt new file mode 100644 index 000000000..151af8d73 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/anime/AnimeDownloadQueueTab.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.ui.download.anime + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.TabContent +import eu.kanade.tachiyomi.R + +@Composable +fun Screen.animeDownloadTab(): TabContent { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() + val screenModel = rememberScreenModel { AnimeDownloadQueueScreenModel() } + val downloadList by screenModel.state.collectAsState() + val downloadCount by remember { + derivedStateOf { downloadList.sumOf { it.subItems.size } } + } + + return TabContent( + titleRes = R.string.label_anime, + searchEnabled = false, + content = { contentPadding, _ -> + AnimeDownloadQueueScreen( + context = context, + contentPadding = contentPadding, + scope = scope, + screenModel = screenModel, + downloadList = downloadList, + ) + }, + numberTitle = downloadCount, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreen.kt deleted file mode 100644 index 346c1baad..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreen.kt +++ /dev/null @@ -1,294 +0,0 @@ -package eu.kanade.tachiyomi.ui.download.manga - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.PlayArrow -import androidx.compose.material.icons.outlined.Pause -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.view.ViewCompat -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding -import androidx.recyclerview.widget.LinearLayoutManager -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.components.AppBar -import eu.kanade.presentation.components.EmptyScreen -import eu.kanade.presentation.components.ExtendedFloatingActionButton -import eu.kanade.presentation.components.OverflowMenu -import eu.kanade.presentation.components.Pill -import eu.kanade.presentation.components.Scaffold -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.download.DownloadService -import eu.kanade.tachiyomi.databinding.DownloadListBinding -import eu.kanade.tachiyomi.util.lang.launchUI -import kotlin.math.roundToInt - -object DownloadQueueScreen : Screen { - - @Composable - override fun Content() { - val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val scope = rememberCoroutineScope() - val screenModel = rememberScreenModel { DownloadQueueScreenModel() } - val downloadList by screenModel.state.collectAsState() - val downloadCount by remember { - derivedStateOf { downloadList.sumOf { it.subItems.size } } - } - - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - var fabExpanded by remember { mutableStateOf(true) } - val nestedScrollConnection = remember { - // All this lines just for fab state :/ - object : NestedScrollConnection { - override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { - fabExpanded = available.y >= 0 - return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) - } - - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPreFling(available) - } - - override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) - } - } - } - - Scaffold( - topBar = { - AppBar( - titleContent = { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = stringResource(R.string.label_download_queue), - maxLines = 1, - modifier = Modifier.weight(1f, false), - overflow = TextOverflow.Ellipsis, - ) - if (downloadCount > 0) { - val pillAlpha = if (isSystemInDarkTheme()) 0.12f else 0.08f - Pill( - text = "$downloadCount", - modifier = Modifier.padding(start = 4.dp), - color = MaterialTheme.colorScheme.onBackground - .copy(alpha = pillAlpha), - fontSize = 14.sp, - ) - } - } - }, - navigateUp = navigator::pop, - actions = { - if (downloadList.isNotEmpty()) { - OverflowMenu { closeMenu -> - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_reorganize_by)) }, - children = { - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_order_by_upload_date)) }, - children = { - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_newest)) }, - onClick = { - screenModel.reorderQueue( - { it.download.chapter.dateUpload }, - true, - ) - closeMenu() - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_oldest)) }, - onClick = { - screenModel.reorderQueue( - { it.download.chapter.dateUpload }, - false, - ) - closeMenu() - }, - ) - }, - ) - DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_order_by_chapter_number)) }, - children = { - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_asc)) }, - onClick = { - screenModel.reorderQueue( - { it.download.chapter.chapterNumber }, - false, - ) - closeMenu() - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_desc)) }, - onClick = { - screenModel.reorderQueue( - { it.download.chapter.chapterNumber }, - true, - ) - closeMenu() - }, - ) - }, - ) - }, - ) - androidx.compose.material3.DropdownMenuItem( - text = { Text(text = stringResource(R.string.action_cancel_all)) }, - onClick = { - screenModel.clearQueue(context) - closeMenu() - }, - ) - } - } - }, - scrollBehavior = scrollBehavior, - ) - }, - floatingActionButton = { - AnimatedVisibility( - visible = downloadList.isNotEmpty(), - enter = fadeIn(), - exit = fadeOut(), - ) { - val isRunning by DownloadService.isRunning.collectAsState() - ExtendedFloatingActionButton( - text = { - val id = if (isRunning) { - R.string.action_pause - } else { - R.string.action_resume - } - Text(text = stringResource(id)) - }, - icon = { - val icon = if (isRunning) { - Icons.Outlined.Pause - } else { - Icons.Filled.PlayArrow - } - Icon(imageVector = icon, contentDescription = null) - }, - onClick = { - if (isRunning) { - DownloadService.stop(context) - screenModel.pauseDownloads() - } else { - DownloadService.start(context) - } - }, - expanded = fabExpanded, - modifier = Modifier.navigationBarsPadding(), - ) - } - }, - ) { contentPadding -> - if (downloadList.isEmpty()) { - EmptyScreen( - textResource = R.string.information_no_downloads, - modifier = Modifier.padding(contentPadding), - ) - return@Scaffold - } - val density = LocalDensity.current - val layoutDirection = LocalLayoutDirection.current - val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } - val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } - val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } - val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } - - Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { - AndroidView( - factory = { context -> - screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context)) - screenModel.adapter = DownloadAdapter(screenModel.listener) - screenModel.controllerBinding.recycler.adapter = screenModel.adapter - screenModel.adapter?.isHandleDragEnabled = true - screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller - screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager(context) - - ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) - - scope.launchUI { - screenModel.getDownloadStatusFlow() - .collect(screenModel::onStatusChange) - } - scope.launchUI { - screenModel.getDownloadProgressFlow() - .collect(screenModel::onUpdateDownloadedPages) - } - - screenModel.controllerBinding.root - }, - update = { - screenModel.controllerBinding.recycler - .updatePadding( - left = left, - top = top, - right = right, - bottom = bottom, - ) - - screenModel.controllerBinding.fastScroller - .updateLayoutParams { - leftMargin = left - topMargin = top - rightMargin = right - bottomMargin = bottom - } - - screenModel.adapter?.updateDataSet(downloadList) - }, - ) - } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadAdapter.kt similarity index 86% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadAdapter.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadAdapter.kt index caccf5228..bb17f63b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadAdapter.kt @@ -9,7 +9,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem * * @param downloadItemListener Listener called when an item of the list is released. */ -class DownloadAdapter(val downloadItemListener: DownloadItemListener) : FlexibleAdapter>( +class MangaDownloadAdapter(val downloadItemListener: DownloadItemListener) : FlexibleAdapter>( null, downloadItemListener, true, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderHolder.kt similarity index 87% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderHolder.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderHolder.kt index adf43e2c4..f3aef1a91 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderHolder.kt @@ -8,12 +8,12 @@ import eu.davidea.viewholders.ExpandableViewHolder import eu.kanade.tachiyomi.databinding.DownloadHeaderBinding import eu.kanade.tachiyomi.ui.download.anime.AnimeDownloadAdapter -class DownloadHeaderHolder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter) { +class MangaDownloadHeaderHolder(view: View, adapter: FlexibleAdapter<*>) : ExpandableViewHolder(view, adapter) { private val binding = DownloadHeaderBinding.bind(view) @SuppressLint("SetTextI18n") - fun bind(item: DownloadHeaderItem) { + fun bind(item: MangaDownloadHeaderItem) { setDragHandleView(binding.reorder) binding.title.text = "${item.name} (${item.size})" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderItem.kt similarity index 84% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderItem.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderItem.kt index 87ce4b767..46514857c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHeaderItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHeaderItem.kt @@ -7,11 +7,11 @@ import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R -data class DownloadHeaderItem( +data class MangaDownloadHeaderItem( val id: Long, val name: String, val size: Int, -) : AbstractExpandableHeaderItem() { +) : AbstractExpandableHeaderItem() { override fun getLayoutRes(): Int { return R.layout.download_header @@ -20,13 +20,13 @@ data class DownloadHeaderItem( override fun createViewHolder( view: View, adapter: FlexibleAdapter>, - ): DownloadHeaderHolder { - return DownloadHeaderHolder(view, adapter) + ): MangaDownloadHeaderHolder { + return MangaDownloadHeaderHolder(view, adapter) } override fun bindViewHolder( adapter: FlexibleAdapter>, - holder: DownloadHeaderHolder, + holder: MangaDownloadHeaderHolder, position: Int, payloads: List?, ) { @@ -37,7 +37,7 @@ data class DownloadHeaderItem( if (this === other) return true if (javaClass != other?.javaClass) return false - other as DownloadHeaderItem + other as MangaDownloadHeaderItem if (id != other.id) return false if (name != other.name) return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHolder.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHolder.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHolder.kt index 964ad761e..f849899c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadHolder.kt @@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.util.view.popupMenu * @param view the inflated view for this holder. * @constructor creates a new download holder. */ -class DownloadHolder(private val view: View, val adapter: DownloadAdapter) : +class MangaDownloadHolder(private val view: View, val adapter: MangaDownloadAdapter) : FlexibleViewHolder(view, adapter) { private val binding = DownloadItemBinding.bind(view) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadItem.kt similarity index 83% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadItem.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadItem.kt index 65a214548..4e20478b2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadItem.kt @@ -8,10 +8,10 @@ import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download -class DownloadItem( +class MangaDownloadItem( val download: Download, - header: DownloadHeaderItem, -) : AbstractSectionableItem(header) { + header: MangaDownloadHeaderItem, +) : AbstractSectionableItem(header) { override fun getLayoutRes(): Int { return R.layout.download_item @@ -26,8 +26,8 @@ class DownloadItem( override fun createViewHolder( view: View, adapter: FlexibleAdapter>, - ): DownloadHolder { - return DownloadHolder(view, adapter as DownloadAdapter) + ): MangaDownloadHolder { + return MangaDownloadHolder(view, adapter as MangaDownloadAdapter) } /** @@ -40,7 +40,7 @@ class DownloadItem( */ override fun bindViewHolder( adapter: FlexibleAdapter>, - holder: DownloadHolder, + holder: MangaDownloadHolder, position: Int, payloads: MutableList, ) { @@ -56,7 +56,7 @@ class DownloadItem( override fun equals(other: Any?): Boolean { if (this === other) return true - if (other is DownloadItem) { + if (other is MangaDownloadItem) { return download.chapter.id == other.download.chapter.id } return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt new file mode 100644 index 000000000..d78bd6ec3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreen.kt @@ -0,0 +1,180 @@ +package eu.kanade.tachiyomi.ui.download.manga + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.outlined.Pause +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.rememberTopAppBarState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.view.ViewCompat +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.LinearLayoutManager +import eu.kanade.presentation.components.EmptyScreen +import eu.kanade.presentation.components.ExtendedFloatingActionButton +import eu.kanade.presentation.components.Scaffold +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.download.DownloadService +import eu.kanade.tachiyomi.databinding.DownloadListBinding +import eu.kanade.tachiyomi.util.lang.launchUI +import kotlinx.coroutines.CoroutineScope +import kotlin.math.roundToInt + +@Composable +fun DownloadQueueScreen( + context: Context, + contentPadding: PaddingValues, + scope: CoroutineScope, + screenModel: MangaDownloadQueueScreenModel, + downloadList: List, +) { + val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) + var fabExpanded by remember { mutableStateOf(true) } + val nestedScrollConnection = remember { + // All this lines just for fab state :/ + object : NestedScrollConnection { + override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { + fabExpanded = available.y >= 0 + return scrollBehavior.nestedScrollConnection.onPreScroll(available, source) + } + + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source) + } + + override suspend fun onPreFling(available: Velocity): Velocity { + return scrollBehavior.nestedScrollConnection.onPreFling(available) + } + + override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { + return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available) + } + } + } + + Scaffold( + floatingActionButton = { + AnimatedVisibility( + visible = downloadList.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + val isRunning by DownloadService.isRunning.collectAsState() + ExtendedFloatingActionButton( + text = { + val id = if (isRunning) { + R.string.action_pause + } else { + R.string.action_resume + } + Text(text = stringResource(id)) + }, + icon = { + val icon = if (isRunning) { + Icons.Outlined.Pause + } else { + Icons.Filled.PlayArrow + } + Icon(imageVector = icon, contentDescription = null) + }, + onClick = { + if (isRunning) { + DownloadService.stop(context) + screenModel.pauseDownloads() + } else { + DownloadService.start(context) + } + }, + expanded = fabExpanded, + modifier = Modifier.navigationBarsPadding(), + ) + } + }, + ) { + if (downloadList.isEmpty()) { + EmptyScreen( + textResource = R.string.information_no_downloads, + modifier = Modifier.padding(contentPadding), + ) + return@Scaffold + } + val density = LocalDensity.current + val layoutDirection = LocalLayoutDirection.current + val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } + val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() } + val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() } + val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() } + + Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) { + AndroidView( + factory = { context -> + screenModel.controllerBinding = DownloadListBinding.inflate(LayoutInflater.from(context)) + screenModel.adapter = MangaDownloadAdapter(screenModel.listener) + screenModel.controllerBinding.recycler.adapter = screenModel.adapter + screenModel.adapter?.isHandleDragEnabled = true + screenModel.adapter?.fastScroller = screenModel.controllerBinding.fastScroller + screenModel.controllerBinding.recycler.layoutManager = LinearLayoutManager(context) + + ViewCompat.setNestedScrollingEnabled(screenModel.controllerBinding.root, true) + + scope.launchUI { + screenModel.getDownloadStatusFlow() + .collect(screenModel::onStatusChange) + } + scope.launchUI { + screenModel.getDownloadProgressFlow() + .collect(screenModel::onUpdateDownloadedPages) + } + + screenModel.controllerBinding.root + }, + update = { + screenModel.controllerBinding.recycler + .updatePadding( + left = left, + top = top, + right = right, + bottom = bottom, + ) + + screenModel.controllerBinding.fastScroller + .updateLayoutParams { + leftMargin = left + topMargin = top + rightMargin = right + bottomMargin = bottom + } + + screenModel.adapter?.updateDataSet(downloadList) + }, + ) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt similarity index 88% rename from app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt index c2e857185..46f79b789 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/DownloadQueueScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueScreenModel.kt @@ -25,11 +25,11 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit -class DownloadQueueScreenModel( +class MangaDownloadQueueScreenModel( private val downloadManager: DownloadManager = Injekt.get(), ) : ScreenModel { - private val _state = MutableStateFlow(emptyList()) + private val _state = MutableStateFlow(emptyList()) val state = _state.asStateFlow() lateinit var controllerBinding: DownloadListBinding @@ -37,14 +37,14 @@ class DownloadQueueScreenModel( /** * Adapter containing the active downloads. */ - var adapter: DownloadAdapter? = null + var adapter: MangaDownloadAdapter? = null /** * Map of subscriptions for active downloads. */ val progressSubscriptions by lazy { mutableMapOf() } - val listener = object : DownloadAdapter.DownloadItemListener { + val listener = object : MangaDownloadAdapter.DownloadItemListener { /** * Called when an item is released from a drag. * @@ -54,7 +54,7 @@ class DownloadQueueScreenModel( val adapter = adapter ?: return val downloads = adapter.headerItems.flatMap { header -> adapter.getSectionItems(header).map { item -> - (item as DownloadItem).download + (item as MangaDownloadItem).download } } reorder(downloads) @@ -68,13 +68,13 @@ class DownloadQueueScreenModel( */ override fun onMenuItemClick(position: Int, menuItem: MenuItem) { val item = adapter?.getItem(position) ?: return - if (item is DownloadItem) { + if (item is MangaDownloadItem) { when (menuItem.itemId) { R.id.move_to_top, R.id.move_to_bottom -> { val headerItems = adapter?.headerItems ?: return val newDownloads = mutableListOf() headerItems.forEach { headerItem -> - headerItem as DownloadHeaderItem + headerItem as MangaDownloadHeaderItem if (headerItem == item.header) { headerItem.removeSubItem(item) if (menuItem.itemId == R.id.move_to_top) { @@ -89,8 +89,8 @@ class DownloadQueueScreenModel( } R.id.move_to_top_series -> { val (selectedSeries, otherSeries) = adapter?.currentItems - ?.filterIsInstance() - ?.map(DownloadItem::download) + ?.filterIsInstance() + ?.map(MangaDownloadItem::download) ?.partition { item.download.manga.id == it.manga.id } ?: Pair(emptyList(), emptyList()) reorder(selectedSeries + otherSeries) @@ -100,9 +100,9 @@ class DownloadQueueScreenModel( } R.id.cancel_series -> { val allDownloadsForSeries = adapter?.currentItems - ?.filterIsInstance() + ?.filterIsInstance() ?.filter { item.download.manga.id == it.download.manga.id } - ?.map(DownloadItem::download) + ?.map(MangaDownloadItem::download) if (!allDownloadsForSeries.isNullOrEmpty()) { cancel(allDownloadsForSeries) } @@ -120,8 +120,8 @@ class DownloadQueueScreenModel( downloads .groupBy { it.source } .map { entry -> - DownloadHeaderItem(entry.key.id, entry.key.name, entry.value.size).apply { - addSubItems(0, entry.value.map { DownloadItem(it, this) }) + MangaDownloadHeaderItem(entry.key.id, entry.key.name, entry.value.size).apply { + addSubItems(0, entry.value.map { MangaDownloadItem(it, this) }) } } } @@ -157,11 +157,11 @@ class DownloadQueueScreenModel( downloadManager.cancelQueuedDownloads(downloads) } - fun > reorderQueue(selector: (DownloadItem) -> R, reverse: Boolean = false) { + fun > reorderQueue(selector: (MangaDownloadItem) -> R, reverse: Boolean = false) { val adapter = adapter ?: return val newDownloads = mutableListOf() adapter.headerItems.forEach { headerItem -> - headerItem as DownloadHeaderItem + headerItem as MangaDownloadHeaderItem headerItem.subItems = headerItem.subItems.sortedBy(selector).toMutableList().apply { if (reverse) { reverse() @@ -259,7 +259,7 @@ class DownloadQueueScreenModel( * @param download the download to find. * @return the holder of the download or null if it's not bound. */ - private fun getHolder(download: Download): DownloadHolder? { - return controllerBinding.recycler.findViewHolderForItemId(download.chapter.id) as? DownloadHolder + private fun getHolder(download: Download): MangaDownloadHolder? { + return controllerBinding.recycler.findViewHolderForItemId(download.chapter.id) as? MangaDownloadHolder } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueTab.kt new file mode 100644 index 000000000..e60dcb284 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/manga/MangaDownloadQueueTab.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.ui.download.manga + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.platform.LocalContext +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.TabContent +import eu.kanade.tachiyomi.R + +@Composable +fun Screen.mangaDownloadTab(): TabContent { + val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val scope = rememberCoroutineScope() + val screenModel = rememberScreenModel { MangaDownloadQueueScreenModel() } + val downloadList by screenModel.state.collectAsState() + val downloadCount by remember { + derivedStateOf { downloadList.sumOf { it.subItems.size } } + } + + return TabContent( + titleRes = R.string.label_manga, + searchEnabled = false, + content = { contentPadding, _ -> + DownloadQueueScreen( + context = context, + contentPadding = contentPadding, + scope = scope, + screenModel = screenModel, + downloadList = downloadList, + ) + }, + numberTitle = downloadCount, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoriesTab.kt similarity index 65% rename from app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsController.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoriesTab.kt index d8d6756c8..44929cad1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/HistoryTabsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoriesTab.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui +package eu.kanade.tachiyomi.ui.history import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter @@ -8,51 +8,54 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.components.TabbedScreen import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.animehistory.AnimeHistoryScreenModel -import eu.kanade.tachiyomi.ui.animehistory.animeHistoryTab -import eu.kanade.tachiyomi.ui.history.HistoryScreenModel -import eu.kanade.tachiyomi.ui.history.historyTab +import eu.kanade.tachiyomi.ui.history.anime.AnimeHistoryScreenModel +import eu.kanade.tachiyomi.ui.history.anime.animeHistoryTab +import eu.kanade.tachiyomi.ui.history.anime.resumeLastEpisodeSeenEvent +import eu.kanade.tachiyomi.ui.history.manga.MangaHistoryScreenModel +import eu.kanade.tachiyomi.ui.history.manga.mangaHistoryTab import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.storage.DiskUtil -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -data class HistoriesTab() : Tab { +data class HistoriesTab( + private val fromMore: Boolean, +) : Tab { override val options: TabOptions @Composable get() { val isSelected = LocalTabNavigator.current.current.key == key - val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter) + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter) + val index: UShort = if (fromMore) 5u else 2u return TabOptions( - index = 3u, + index = index, title = stringResource(R.string.history), icon = rememberAnimatedVectorPainter(image, isSelected), ) } + override suspend fun onReselect(navigator: Navigator) { + resumeLastEpisodeSeenEvent.send(Unit) + } + @Composable override fun Content() { val context = LocalContext.current - // Hoisted for extensions tab's search bar - val historyScreenModel = rememberScreenModel { HistoryScreenModel() } + // Hoisted for history tab's search bar + val historyScreenModel = rememberScreenModel { MangaHistoryScreenModel() } val animeHistoryScreenModel = rememberScreenModel { AnimeHistoryScreenModel() } - val libraryPreferences: LibraryPreferences = Injekt.get() - val fromMore = libraryPreferences.bottomNavStyle().get() == 0 - TabbedScreen( titleRes = R.string.label_recent_manga, tabs = listOf( - animeHistoryTab(), - historyTab(), + animeHistoryTab(context, fromMore), + mangaHistoryTab(context, fromMore), ), searchQuery = historyScreenModel.getSearchQuery, onChangeSearchQuery = historyScreenModel::updateSearchQuery, @@ -67,17 +70,7 @@ data class HistoriesTab() : Tab { // For local source DiskUtil.RequestStoragePermission() } - - fun resumeLastItem() { - if (state.currentPage == TAB_MANGA) { - presenter.resumeLastChapterRead() - } else { - presenter.resumeLastEpisodeSeen() - } - } } private const val TAB_ANIME = 0 private const val TAB_MANGA = 1 - -// TODO: Fix History, updates and download tabs diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt deleted file mode 100644 index f08d5dbd8..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt +++ /dev/null @@ -1,127 +0,0 @@ -package eu.kanade.tachiyomi.ui.history - -import android.content.Context -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.material3.SnackbarHostState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow -import cafe.adriel.voyager.navigator.tab.LocalTabNavigator -import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.domain.chapter.model.Chapter -import eu.kanade.presentation.history.HistoryScreen -import eu.kanade.presentation.history.components.HistoryDeleteAllDialog -import eu.kanade.presentation.history.components.HistoryDeleteDialog -import eu.kanade.presentation.util.Tab -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaScreen -import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.consumeAsFlow - -object HistoryTab : Tab { - - private val snackbarHostState = SnackbarHostState() - - private val resumeLastChapterReadEvent = Channel() - - override val options: TabOptions - @Composable - get() { - val isSelected = LocalTabNavigator.current.current.key == key - val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter) - return TabOptions( - index = 2u, - title = stringResource(R.string.label_recent_manga), - icon = rememberAnimatedVectorPainter(image, isSelected), - ) - } - - override suspend fun onReselect(navigator: Navigator) { - resumeLastChapterReadEvent.send(Unit) - } - - @Composable - override fun Content() { - val navigator = LocalNavigator.currentOrThrow - val context = LocalContext.current - val screenModel = rememberScreenModel { HistoryScreenModel() } - val state by screenModel.state.collectAsState() - - HistoryScreen( - state = state, - snackbarHostState = snackbarHostState, - onSearchQueryChange = screenModel::updateSearchQuery, - onClickCover = { navigator.push(MangaScreen(it)) }, - onClickResume = screenModel::getNextChapterForManga, - onDialogChange = screenModel::setDialog, - ) - - val onDismissRequest = { screenModel.setDialog(null) } - when (val dialog = state.dialog) { - is HistoryScreenModel.Dialog.Delete -> { - HistoryDeleteDialog( - onDismissRequest = onDismissRequest, - onDelete = { all -> - if (all) { - screenModel.removeAllFromHistory(dialog.history.mangaId) - } else { - screenModel.removeFromHistory(dialog.history) - } - }, - ) - } - is HistoryScreenModel.Dialog.DeleteAll -> { - HistoryDeleteAllDialog( - onDismissRequest = onDismissRequest, - onDelete = screenModel::removeAllHistory, - ) - } - null -> {} - } - - LaunchedEffect(state.list) { - if (state.list != null) { - (context as? MainActivity)?.ready = true - } - } - - LaunchedEffect(Unit) { - screenModel.events.collectLatest { e -> - when (e) { - HistoryScreenModel.Event.InternalError -> - snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) - HistoryScreenModel.Event.HistoryCleared -> - snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed)) - is HistoryScreenModel.Event.OpenChapter -> openChapter(context, e.chapter) - } - } - } - - LaunchedEffect(Unit) { - resumeLastChapterReadEvent.consumeAsFlow().collectLatest { - openChapter(context, screenModel.getNextChapter()) - } - } - } - - suspend fun openChapter(context: Context, chapter: Chapter?) { - if (chapter != null) { - val intent = ReaderActivity.newIntent(context, chapter.mangaId, chapter.id) - context.startActivity(intent) - } else { - snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter)) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryScreenModel.kt similarity index 99% rename from app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryScreenModel.kt index 7c66b5cee..3018b59dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animehistory/AnimeHistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.animehistory +package eu.kanade.tachiyomi.ui.history.anime import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryTab.kt new file mode 100644 index 000000000..f85ea02d0 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/anime/AnimeHistoryTab.kt @@ -0,0 +1,133 @@ +package eu.kanade.tachiyomi.ui.history.anime + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DeleteSweep +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.domain.episode.model.Episode +import eu.kanade.presentation.animehistory.AnimeHistoryScreen +import eu.kanade.presentation.animehistory.components.AnimeHistoryDeleteDialog +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.history.components.HistoryDeleteAllDialog +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.player.ExternalIntents +import eu.kanade.tachiyomi.ui.player.PlayerActivity +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.consumeAsFlow +import uy.kohesive.injekt.injectLazy + +val resumeLastEpisodeSeenEvent = Channel() + +@Composable +fun Screen.animeHistoryTab( + context: Context, + fromMore: Boolean, +): TabContent { + val snackbarHostState = SnackbarHostState() + + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { AnimeHistoryScreenModel() } + val state by screenModel.state.collectAsState() + + suspend fun openEpisode(context: Context, episode: Episode?) { + val playerPreferences: PlayerPreferences by injectLazy() + val altPlayer = playerPreferences.alwaysUseExternalPlayer().get() + if (episode != null) { + val intent = if (altPlayer) { + ExternalIntents.newIntent(context, episode.animeId, episode.id) + } else { + PlayerActivity.newIntent(context, episode.animeId, episode.id) + } + context.startActivity(intent) + } else { + snackbarHostState.showSnackbar(context.getString(R.string.no_next_episode)) + } + } + + val navigateUp: (() -> Unit)? = if (fromMore) navigator::pop else null + + return TabContent( + titleRes = R.string.label_animehistory, + searchEnabled = true, + content = { contentPadding, _ -> + AnimeHistoryScreen( + state = state, + contentPadding = contentPadding, + snackbarHostState = snackbarHostState, + onClickCover = { navigator.push(AnimeScreen(it)) }, + onClickResume = screenModel::getNextEpisodeForAnime, + onDialogChange = screenModel::setDialog, + ) + + val onDismissRequest = { screenModel.setDialog(null) } + when (val dialog = state.dialog) { + is AnimeHistoryScreenModel.Dialog.Delete -> { + AnimeHistoryDeleteDialog( + onDismissRequest = onDismissRequest, + onDelete = { all -> + if (all) { + screenModel.removeAllFromHistory(dialog.history.animeId) + } else { + screenModel.removeFromHistory(dialog.history) + } + }, + ) + } + is AnimeHistoryScreenModel.Dialog.DeleteAll -> { + HistoryDeleteAllDialog( + onDismissRequest = onDismissRequest, + onDelete = screenModel::removeAllHistory, + ) + } + null -> {} + } + + LaunchedEffect(state.list) { + if (state.list != null) { + (context as? MainActivity)?.ready = true + } + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { e -> + when (e) { + AnimeHistoryScreenModel.Event.InternalError -> + snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) + AnimeHistoryScreenModel.Event.HistoryCleared -> + snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed)) + is AnimeHistoryScreenModel.Event.OpenEpisode -> openEpisode(context, e.episode) + } + } + } + + LaunchedEffect(Unit) { + resumeLastEpisodeSeenEvent.consumeAsFlow().collectLatest { + openEpisode(context, screenModel.getNextEpisode()) + } + } + }, + actions = + listOf( + AppBar.Action( + title = stringResource(R.string.pref_clear_history), + icon = Icons.Outlined.DeleteSweep, + onClick = { screenModel.setDialog(AnimeHistoryScreenModel.Dialog.DeleteAll) }, + ), + ), + navigateUp = navigateUp, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryScreenModel.kt similarity index 97% rename from app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryScreenModel.kt index 601414c5e..e84b18061 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.history +package eu.kanade.tachiyomi.ui.history.manga import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel @@ -30,7 +30,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date -class HistoryScreenModel( +class MangaHistoryScreenModel( private val getHistory: GetHistory = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(), private val removeHistory: RemoveHistory = Injekt.get(), @@ -131,5 +131,5 @@ class HistoryScreenModel( data class HistoryState( val searchQuery: String? = null, val list: List? = null, - val dialog: HistoryScreenModel.Dialog? = null, + val dialog: MangaHistoryScreenModel.Dialog? = null, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryTab.kt new file mode 100644 index 000000000..0b9451e2c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/manga/MangaHistoryTab.kt @@ -0,0 +1,124 @@ +package eu.kanade.tachiyomi.ui.history.manga + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.DeleteSweep +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.domain.chapter.model.Chapter +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.history.HistoryScreen +import eu.kanade.presentation.history.components.HistoryDeleteAllDialog +import eu.kanade.presentation.history.components.HistoryDeleteDialog +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.consumeAsFlow + +val resumeLastChapterReadEvent = Channel() + +@Composable +fun Screen.mangaHistoryTab( + context: Context, + fromMore: Boolean, +): TabContent { + val snackbarHostState = SnackbarHostState() + + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { MangaHistoryScreenModel() } + val state by screenModel.state.collectAsState() + + suspend fun openChapter(context: Context, chapter: Chapter?) { + if (chapter != null) { + val intent = ReaderActivity.newIntent(context, chapter.mangaId, chapter.id) + context.startActivity(intent) + } else { + snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter)) + } + } + + val navigateUp: (() -> Unit)? = if (fromMore) navigator::pop else null + + return TabContent( + titleRes = R.string.label_history, + searchEnabled = true, + content = { contentPadding, _ -> + HistoryScreen( + state = state, + contentPadding = contentPadding, + snackbarHostState = snackbarHostState, + onClickCover = { navigator.push(MangaScreen(it)) }, + onClickResume = screenModel::getNextChapterForManga, + onDialogChange = screenModel::setDialog, + ) + + val onDismissRequest = { screenModel.setDialog(null) } + when (val dialog = state.dialog) { + is MangaHistoryScreenModel.Dialog.Delete -> { + HistoryDeleteDialog( + onDismissRequest = onDismissRequest, + onDelete = { all -> + if (all) { + screenModel.removeAllFromHistory(dialog.history.mangaId) + } else { + screenModel.removeFromHistory(dialog.history) + } + }, + ) + } + is MangaHistoryScreenModel.Dialog.DeleteAll -> { + HistoryDeleteAllDialog( + onDismissRequest = onDismissRequest, + onDelete = screenModel::removeAllHistory, + ) + } + null -> {} + } + + LaunchedEffect(state.list) { + if (state.list != null) { + (context as? MainActivity)?.ready = true + } + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { e -> + when (e) { + MangaHistoryScreenModel.Event.InternalError -> + snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) + MangaHistoryScreenModel.Event.HistoryCleared -> + snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed)) + is MangaHistoryScreenModel.Event.OpenChapter -> openChapter(context, e.chapter) + } + } + } + + LaunchedEffect(Unit) { + resumeLastChapterReadEvent.consumeAsFlow().collectLatest { + openChapter(context, screenModel.getNextChapter()) + } + } + }, + actions = + listOf( + AppBar.Action( + title = stringResource(R.string.pref_clear_history), + icon = Icons.Outlined.DeleteSweep, + onClick = { screenModel.setDialog(MangaHistoryScreenModel.Dialog.DeleteAll) }, + ), + ), + navigateUp = navigateUp, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt index ea33089b6..782460270 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt @@ -44,8 +44,8 @@ import eu.kanade.presentation.util.isTabletUi import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.animelib.AnimelibTab import eu.kanade.tachiyomi.ui.browse.BrowseTab -import eu.kanade.tachiyomi.ui.download.manga.DownloadQueueScreen -import eu.kanade.tachiyomi.ui.history.HistoryTab +import eu.kanade.tachiyomi.ui.download.DownloadsTab +import eu.kanade.tachiyomi.ui.history.HistoriesTab import eu.kanade.tachiyomi.ui.library.LibraryTab import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.more.MoreTab @@ -69,33 +69,33 @@ object HomeScreen : Screen { private const val TabFadeDuration = 200 - private val preferences: LibraryPreferences by injectLazy() + private val libraryPreferences: LibraryPreferences by injectLazy() private val tabsNoHistory = listOf( AnimelibTab, LibraryTab, - UpdatesTab, + UpdatesTab(fromMore = false, inMiddle = true), BrowseTab(), - MoreTab + MoreTab, ) private val tabsNoUpdates = listOf( AnimelibTab, LibraryTab, - HistoryTab, + HistoriesTab(false), BrowseTab(), - MoreTab + MoreTab, ) private val tabsNoManga = listOf( AnimelibTab, - UpdatesTab, - HistoryTab, + UpdatesTab(fromMore = false, inMiddle = false), + HistoriesTab(false), BrowseTab(), - MoreTab + MoreTab, ) - private val tabs = when (preferences.bottomNavStyle().get()) { + private val tabs = when (libraryPreferences.bottomNavStyle().get()) { 0 -> tabsNoHistory 1 -> tabsNoUpdates else -> tabsNoManga @@ -178,8 +178,11 @@ object HomeScreen : Screen { tabNavigator.current = when (it) { is Tab.Animelib -> AnimelibTab is Tab.Library -> LibraryTab - Tab.Updates -> UpdatesTab - Tab.History -> HistoryTab + is Tab.Updates -> UpdatesTab( + libraryPreferences.bottomNavStyle().get() == 1, + libraryPreferences.bottomNavStyle().get() == 0, + ) + is Tab.History -> HistoriesTab(false) is Tab.Browse -> BrowseTab(it.toExtensions) is Tab.More -> MoreTab } @@ -191,7 +194,7 @@ object HomeScreen : Screen { navigator.push(MangaScreen(it.mangaIdToOpen)) } if (it is Tab.More && it.toDownloads) { - navigator.push(DownloadQueueScreen) + navigator.push(DownloadsTab()) } } } @@ -260,14 +263,14 @@ object HomeScreen : Screen { BadgedBox( badge = { when { - tab is UpdatesTab -> { + UpdatesTab::class.isInstance(tab) -> { val count by produceState(initialValue = 0) { val pref = Injekt.get() combine( - pref.newShowUpdatesCount().changes(), + pref.newAnimeUpdatesCount().changes(), pref.newMangaUpdatesCount().changes(), - ) { show, count -> if (show) count else 0 } - .collectLatest { value = it } + ) { countAnime, countManga -> countAnime + countManga } + .collectLatest { value = if (pref.newShowUpdatesCount().get()) it else 0 } } if (count > 0) { Badge { @@ -285,7 +288,11 @@ object HomeScreen : Screen { } BrowseTab::class.isInstance(tab) -> { val count by produceState(initialValue = 0) { - Injekt.get().extensionUpdatesCount().changes() + val pref = Injekt.get() + combine( + pref.extensionUpdatesCount().changes(), + pref.animeextensionUpdatesCount().changes(), + ) { extCount, animeExtCount -> extCount + animeExtCount } .collectLatest { value = it } } if (count > 0) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index eb9d79969..6b19d89bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -38,6 +38,7 @@ import eu.kanade.presentation.manga.DownloadAction import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.download.DownloadCache import eu.kanade.tachiyomi.data.download.DownloadManager +import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.model.SManga @@ -406,7 +407,7 @@ class LibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedServices = trackManager.services.filter { it.isLogged } + val loggedServices = trackManager.services.filter { it.isLogged && it is MangaTrackService } return if (loggedServices.isNotEmpty()) { val prefFlows = loggedServices .map { libraryPreferences.filterTracking(it.id.toInt()).changes() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt index b9860d842..d4705b7e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt @@ -31,6 +31,7 @@ import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.domain.category.model.Category import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.display +import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.isLocal import eu.kanade.presentation.components.ChangeCategoryDialog @@ -47,7 +48,7 @@ import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen -import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaScreen @@ -57,16 +58,21 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import uy.kohesive.injekt.injectLazy object LibraryTab : Tab { + val libraryPreferences: LibraryPreferences by injectLazy() + private val fromMore = libraryPreferences.bottomNavStyle().get() == 2 + override val options: TabOptions @Composable get() { val isSelected = LocalTabNavigator.current.current.key == key val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter) + val index: UShort = if (fromMore) 5u else 1u return TabOptions( - index = 0u, + index = index, title = stringResource(R.string.label_library), icon = rememberAnimatedVectorPainter(image, isSelected), ) @@ -100,6 +106,8 @@ object LibraryTab : Tab { scope.launch { sendSettingsSheetIntent(state.categories[screenModel.activeCategoryIndex]) } } + val navigateUp: (() -> Unit)? = if (fromMore) navigator::pop else null + Scaffold( topBar = { scrollBehavior -> val title = state.getToolbarTitle( @@ -130,6 +138,7 @@ object LibraryTab : Tab { searchQuery = state.searchQuery, onSearchQueryChange = screenModel::search, scrollBehavior = scrollBehavior.takeIf { !tabVisible }, // For scroll overlay when no tab + navigateUp = navigateUp, ) }, bottomBar = { @@ -208,7 +217,7 @@ object LibraryTab : Tab { onDismissRequest = onDismissRequest, onEditCategories = { screenModel.clearSelection() - navigator.push(CategoryScreen()) + navigator.push(CategoriesTab(true)) }, onConfirm = { include, exclude -> screenModel.clearSelection() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 0ebae799a..3a5507dd8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -66,7 +66,6 @@ import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.animeextension.api.AnimeExtensionGithubApi import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.EpisodeCache @@ -75,6 +74,8 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.updater.AppUpdateChecker import eu.kanade.tachiyomi.data.updater.AppUpdateResult import eu.kanade.tachiyomi.data.updater.RELEASE_URL +import eu.kanade.tachiyomi.ui.animelib.AnimelibSettingsSheet +import eu.kanade.tachiyomi.ui.animelib.AnimelibTab import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen @@ -83,7 +84,9 @@ import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet import eu.kanade.tachiyomi.ui.library.LibraryTab import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.more.NewUpdateScreen +import eu.kanade.tachiyomi.ui.player.ExternalIntents import eu.kanade.tachiyomi.util.Constants +import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim import eu.kanade.tachiyomi.util.system.logcat @@ -116,13 +119,16 @@ class MainActivity : BaseActivity() { private val animeDownloadCache: AnimeDownloadCache by injectLazy() private val downloadCache: DownloadCache by injectLazy() + private val externalIntents: ExternalIntents by injectLazy() + // To be checked by splash screen. If true then splash screen will be removed. var ready = false /** * Sheet containing filter/sort/display items. */ - private var settingsSheet: LibrarySettingsSheet? = null + private var animeSettingsSheet: AnimelibSettingsSheet? = null + private var mangaSettingsSheet: LibrarySettingsSheet? = null private var isHandlingShortcut: Boolean = false private lateinit var navigator: Navigator @@ -160,9 +166,14 @@ class MainActivity : BaseActivity() { // Draw edge-to-edge WindowCompat.setDecorFitsSystemWindows(window, false) - settingsSheet = LibrarySettingsSheet(this) + animeSettingsSheet = AnimelibSettingsSheet(this) + AnimelibTab.openSettingsSheetEvent + .onEach(::showAnimeSettingsSheet) + .launchIn(lifecycleScope) + + mangaSettingsSheet = LibrarySettingsSheet(this) LibraryTab.openSettingsSheetEvent - .onEach(::showSettingsSheet) + .onEach(::showMangaSettingsSheet) .launchIn(lifecycleScope) setComposeContent { @@ -302,11 +313,23 @@ class MainActivity : BaseActivity() { } } - private fun showSettingsSheet(category: Category? = null) { + private fun showAnimeSettingsSheet(category: Category? = null) { if (category != null) { - settingsSheet?.show(category) + animeSettingsSheet?.show(category) } else { - lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() } + lifecycleScope.launch { + AnimelibTab.requestOpenSettingsSheet() + } + } + } + + private fun showMangaSettingsSheet(category: Category? = null) { + if (category != null) { + mangaSettingsSheet?.show(category) + } else { + lifecycleScope.launch { + LibraryTab.requestOpenSettingsSheet() + } } } @@ -475,8 +498,10 @@ class MainActivity : BaseActivity() { } override fun onDestroy() { - settingsSheet?.sheetScope?.cancel() - settingsSheet = null + animeSettingsSheet?.sheetScope?.cancel() + mangaSettingsSheet?.sheetScope?.cancel() + animeSettingsSheet = null + mangaSettingsSheet = null super.onDestroy() } @@ -494,6 +519,12 @@ class MainActivity : BaseActivity() { registerSecureActivity(this) } + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + launchIO { externalIntents.onActivityResult(requestCode, resultCode, data) } + super.onActivityResult(requestCode, resultCode, data) + } + companion object { // Splash screen private const val SPLASH_MIN_DURATION = 500 // ms @@ -517,29 +548,5 @@ class MainActivity : BaseActivity() { const val INTENT_ANIMESEARCH = "eu.kanade.tachiyomi.ANIMESEARCH" const val INTENT_SEARCH_QUERY = "query" const val INTENT_SEARCH_FILTER = "filter" - - private val startScreenArrayDefault = intArrayOf( - R.id.nav_animelib, - R.id.nav_animelib, - R.id.nav_library, - R.id.nav_updates, - R.id.nav_browse, - ) - - private val startScreenArrayHistory = intArrayOf( - R.id.nav_animelib, - R.id.nav_animelib, - R.id.nav_library, - R.id.nav_history, - R.id.nav_browse, - ) - - private val startScreenArrayNoManga = intArrayOf( - R.id.nav_animelib, - R.id.nav_animelib, - R.id.nav_updates, - R.id.nav_history, - R.id.nav_browse, - ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index c46606030..8c7836768 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -44,7 +44,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen -import eu.kanade.tachiyomi.ui.category.CategoryScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.reader.ReaderActivity @@ -138,7 +138,7 @@ class MangaScreen( ChangeCategoryDialog( initialSelection = dialog.initialSelection, onDismissRequest = onDismissRequest, - onEditCategories = { navigator.push(CategoryScreen()) }, + onEditCategories = { navigator.push(CategoriesTab(true)) }, onConfirm = { include, _ -> screenModel.moveMangaToCategoriesAndAddToLibrary(dialog.manga, include) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index b0f2fabff..672ddd94b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -103,7 +103,7 @@ class MangaInfoScreenModel( private val successState: MangaScreenState.Success? get() = state.value as? MangaScreenState.Success - private val loggedServices by lazy { trackManager.services.filter { it.isLogged && it is MangaTrackService} } + private val loggedServices by lazy { trackManager.services.filter { it.isLogged && it is MangaTrackService } } val manga: Manga? get() = successState?.manga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt index e667cf284..08004b30e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreTab.kt @@ -19,16 +19,21 @@ import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions import eu.kanade.core.prefs.asState import eu.kanade.domain.base.BasePreferences +import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.presentation.more.MoreScreen import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager +import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService -import eu.kanade.tachiyomi.ui.category.CategoryScreen -import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen -import eu.kanade.tachiyomi.ui.download.manga.DownloadQueueScreen +import eu.kanade.tachiyomi.ui.category.CategoriesTab +import eu.kanade.tachiyomi.ui.download.DownloadsTab +import eu.kanade.tachiyomi.ui.history.HistoriesTab +import eu.kanade.tachiyomi.ui.library.LibraryTab import eu.kanade.tachiyomi.ui.setting.SettingsScreen -import eu.kanade.tachiyomi.ui.stats.StatsScreen +import eu.kanade.tachiyomi.ui.stats.StatsTab +import eu.kanade.tachiyomi.ui.updates.UpdatesTab import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid import kotlinx.coroutines.flow.MutableStateFlow @@ -38,6 +43,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy object MoreTab : Tab { @@ -70,9 +76,10 @@ object MoreTab : Tab { incognitoMode = screenModel.incognitoMode, onIncognitoModeChange = { screenModel.incognitoMode = it }, isFDroid = context.isInstalledFromFDroid(), - onClickDownloadQueue = { navigator.push(DownloadQueueScreen) }, - onClickCategories = { navigator.push(CategoryScreen()) }, - onClickStats = { navigator.push(StatsScreen()) }, + onClickAlt = { navigator.push(altOpen) }, + onClickDownloadQueue = { navigator.push(DownloadsTab()) }, + onClickCategories = { navigator.push(CategoriesTab()) }, + onClickStats = { navigator.push(StatsTab()) }, onClickBackupAndRestore = { navigator.push(SettingsScreen.toBackupScreen()) }, onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) }, onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) }, @@ -80,8 +87,17 @@ object MoreTab : Tab { } } +private val libraryPreferences: LibraryPreferences by injectLazy() + +private val altOpen = when (libraryPreferences.bottomNavStyle().get()) { + 0 -> HistoriesTab(true) + 1 -> UpdatesTab(fromMore = true, inMiddle = false) + else -> LibraryTab +} + private class MoreScreenModel( private val downloadManager: DownloadManager = Injekt.get(), + private val animeDownloadManager: AnimeDownloadManager = Injekt.get(), preferences: BasePreferences = Injekt.get(), ) : ScreenModel { @@ -97,15 +113,23 @@ private class MoreScreenModel( combine( DownloadService.isRunning, downloadManager.queue.updates, - ) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) } - .collectLatest { (isDownloading, downloadQueueSize) -> - val pendingDownloadExists = downloadQueueSize != 0 - _state.value = when { - !pendingDownloadExists -> DownloadQueueState.Stopped - !isDownloading && !pendingDownloadExists -> DownloadQueueState.Paused(0) - !isDownloading && pendingDownloadExists -> DownloadQueueState.Paused(downloadQueueSize) - else -> DownloadQueueState.Downloading(downloadQueueSize) - } + ) { isRunningManga, mangaDownloadQueue -> Pair(isRunningManga, mangaDownloadQueue.size) } + .collectLatest { (isDownloadingManga, mangaDownloadQueueSize) -> + combine( + AnimeDownloadService.isRunning, + animeDownloadManager.queue.updates, + ) { isRunningAnime, animeDownloadQueue -> Pair(isRunningAnime, animeDownloadQueue.size) } + .collectLatest { (isDownloadingAnime, animeDownloadQueueSize) -> + val isDownloading = isDownloadingAnime || isDownloadingManga + val downloadQueueSize = mangaDownloadQueueSize + animeDownloadQueueSize + val pendingDownloadExists = downloadQueueSize != 0 + _state.value = when { + !pendingDownloadExists -> DownloadQueueState.Stopped + !isDownloading && !pendingDownloadExists -> DownloadQueueState.Paused(0) + !isDownloading && pendingDownloadExists -> DownloadQueueState.Paused(downloadQueueSize) + else -> DownloadQueueState.Downloading(downloadQueueSize) + } + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/ExternalIntents.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/ExternalIntents.kt index 85a737efd..7f9b9f79a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/ExternalIntents.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/ExternalIntents.kt @@ -10,6 +10,8 @@ import android.net.Uri import android.os.Build import androidx.core.content.FileProvider import androidx.core.net.toUri +import eu.kanade.core.util.asFlow +import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.anime.model.Anime import eu.kanade.domain.animehistory.interactor.UpsertAnimeHistory import eu.kanade.domain.animehistory.model.AnimeHistoryUpdate @@ -23,20 +25,20 @@ import eu.kanade.domain.episode.interactor.UpdateEpisode import eu.kanade.domain.episode.model.Episode import eu.kanade.domain.episode.model.EpisodeUpdate import eu.kanade.domain.episode.model.toDbEpisode +import eu.kanade.domain.track.service.DelayedTrackingUpdateJob import eu.kanade.domain.track.service.TrackPreferences +import eu.kanade.domain.track.store.DelayedTrackingStore import eu.kanade.tachiyomi.animesource.AnimeSource +import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.animesource.LocalAnimeSource import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.database.models.toDomainEpisode +import eu.kanade.tachiyomi.data.track.AnimeTrackService import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore -import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob -import eu.kanade.tachiyomi.ui.anime.AnimeController.Companion.EXT_ANIME -import eu.kanade.tachiyomi.ui.anime.AnimeController.Companion.EXT_EPISODE -import eu.kanade.tachiyomi.ui.anime.AnimeController.Companion.REQUEST_EXTERNAL -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import eu.kanade.tachiyomi.util.Constants.REQUEST_EXTERNAL import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.isOnline @@ -44,6 +46,7 @@ import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.first import logcat.LogPriority import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -52,9 +55,17 @@ import java.io.File import java.util.Date import eu.kanade.tachiyomi.data.database.models.Episode as DbEpisode -class ExternalIntents(val anime: Anime, val source: AnimeSource) { +class ExternalIntents { + + lateinit var anime: Anime + lateinit var episode: Episode + lateinit var source: AnimeSource + suspend fun getExternalIntent(context: Context, animeId: Long?, episodeId: Long?): Intent? { + anime = getAnime.await(animeId!!) ?: return null + source = sourceManager.get(anime.source) ?: return null + episode = getEpisodeByAnimeId.await(anime.id).find { it.id == episodeId } ?: return null + val video = EpisodeLoader.getLinks(episode.toDbEpisode(), anime, source).asFlow().first()[0] - fun getExternalIntent(episode: Episode, video: Video, context: Context): Intent? { val videoUrl = if (video.videoUrl == null) { makeErrorToast(context, Exception("video URL is null.")) return null @@ -207,165 +218,168 @@ class ExternalIntents(val anime: Anime, val source: AnimeSource) { } } - companion object { - fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == REQUEST_EXTERNAL && resultCode == Activity.RESULT_OK) { - val anime = EXT_ANIME ?: return - val currentExtEpisode = EXT_EPISODE ?: return - val currentPosition: Long - val duration: Long - val cause = data!!.getStringExtra("end_by") ?: "" - if (cause.isNotEmpty()) { - val positionExtra = data.extras?.get("position") - currentPosition = if (positionExtra is Int) { - positionExtra.toLong() - } else { - positionExtra as? Long ?: 0L - } - val durationExtra = data.extras?.get("duration") - duration = if (durationExtra is Int) { - durationExtra.toLong() - } else { - durationExtra as? Long ?: 0L - } + @Suppress("DEPRECATION") + suspend fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == REQUEST_EXTERNAL && resultCode == Activity.RESULT_OK) { + val anime = anime + val currentExtEpisode = episode + val currentPosition: Long + val duration: Long + val cause = data!!.getStringExtra("end_by") ?: "" + if (cause.isNotEmpty()) { + val positionExtra = data.extras?.get("position") + currentPosition = if (positionExtra is Int) { + positionExtra.toLong() } else { - if (data.extras?.get("extra_position") != null) { - currentPosition = data.getLongExtra("extra_position", 0L) - duration = data.getLongExtra("extra_duration", 0L) - } else { - currentPosition = data.getIntExtra("position", 0).toLong() - duration = data.getIntExtra("duration", 0).toLong() - } + positionExtra as? Long ?: 0L } - launchIO { - if (cause == "playback_completion" || (currentPosition == duration && duration == 0L)) { - saveEpisodeProgress(currentExtEpisode, anime, currentExtEpisode.totalSeconds, currentExtEpisode.totalSeconds) - } else { - saveEpisodeProgress(currentExtEpisode, anime, currentPosition, duration) - } - saveEpisodeHistory(currentExtEpisode) + val durationExtra = data.extras?.get("duration") + duration = if (durationExtra is Int) { + durationExtra.toLong() + } else { + durationExtra as? Long ?: 0L + } + } else { + if (data.extras?.get("extra_position") != null) { + currentPosition = data.getLongExtra("extra_position", 0L) + duration = data.getLongExtra("extra_duration", 0L) + } else { + currentPosition = data.getIntExtra("position", 0).toLong() + duration = data.getIntExtra("duration", 0).toLong() } } - } - - private val upsertHistory: UpsertAnimeHistory = Injekt.get() - private val updateEpisode: UpdateEpisode = Injekt.get() - private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get() - private val getTracks: GetAnimeTracks = Injekt.get() - private val insertTrack: InsertAnimeTrack = Injekt.get() - private val downloadManager: AnimeDownloadManager by injectLazy() - private val delayedTrackingStore: DelayedTrackingStore = Injekt.get() - private val playerPreferences: PlayerPreferences = Injekt.get() - private val downloadPreferences: DownloadPreferences = Injekt.get() - private val trackPreferences: TrackPreferences = Injekt.get() - private val basePreferences: BasePreferences by injectLazy() - - private suspend fun saveEpisodeHistory(episode: Episode) { - if (basePreferences.incognitoMode().get()) return - upsertHistory.await( - AnimeHistoryUpdate(episode.id, Date()), - ) - } - - private suspend fun saveEpisodeProgress(domainEpisode: Episode?, anime: Anime, seconds: Long, totalSeconds: Long) { - if (basePreferences.incognitoMode().get()) return - val episode = domainEpisode?.toDbEpisode() ?: return - if (totalSeconds > 0L) { - episode.last_second_seen = seconds - episode.total_seconds = totalSeconds - val progress = playerPreferences.progressPreference().get() - if (!episode.seen) episode.seen = episode.last_second_seen >= episode.total_seconds * progress - updateEpisode.await( - EpisodeUpdate( - id = episode.id!!, - seen = episode.seen, - bookmark = episode.bookmark, - lastSecondSeen = episode.last_second_seen, - totalSeconds = episode.total_seconds, - ), - ) - if (trackPreferences.autoUpdateTrack().get() && episode.seen) { - updateTrackEpisodeSeen(episode, anime) - } - if (episode.seen) { - deleteEpisodeIfNeeded(episode.toDomainEpisode()!!, anime) - deleteEpisodeFromDownloadQueue(episode) - } - } - } - - private fun deleteEpisodeFromDownloadQueue(episode: DbEpisode) { - downloadManager.getEpisodeDownloadOrNull(episode)?.let { download -> - downloadManager.deletePendingDownload(download) - } - } - - private suspend fun deleteEpisodeIfNeeded(episode: Episode, anime: Anime) { - // Determine which chapter should be deleted and enqueue - val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) { - Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.sourceOrder.compareTo(c1.sourceOrder) } - Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episodeNumber.compareTo(c2.episodeNumber) } - Anime.EPISODE_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.dateUpload.compareTo(c2.dateUpload) } - else -> throw NotImplementedError("Unknown sorting method") - } - - val episodes = getEpisodeByAnimeId.await(anime.id) - .sortedWith { e1, e2 -> sortFunction(e1, e2) } - - val currentEpisodePosition = episodes.indexOf(episode) - val removeAfterReadSlots = downloadPreferences.removeAfterReadSlots().get() - val episodeToDelete = episodes.getOrNull(currentEpisodePosition - removeAfterReadSlots) - - // Check if deleting option is enabled and chapter exists - if (removeAfterReadSlots != -1 && episodeToDelete != null) { - enqueueDeleteSeenEpisodes(episodeToDelete, anime) - } - } - - private fun updateTrackEpisodeSeen(episode: DbEpisode, anime: Anime) { - if (!trackPreferences.autoUpdateTrack().get()) return - - val episodeSeen = episode.episode_number.toDouble() - - val trackManager = Injekt.get() - val context = Injekt.get() - launchIO { - getTracks.await(anime.id) - .mapNotNull { track -> - val service = trackManager.getService(track.syncId) - if (service != null && service.isLogged && episodeSeen > track.lastEpisodeSeen) { - val updatedTrack = track.copy(lastEpisodeSeen = episodeSeen) + if (cause == "playback_completion" || (currentPosition == duration && duration == 0L)) { + saveEpisodeProgress(currentExtEpisode, anime, currentExtEpisode.totalSeconds, currentExtEpisode.totalSeconds) + } else { + saveEpisodeProgress(currentExtEpisode, anime, currentPosition, duration) + } + saveEpisodeHistory(currentExtEpisode) + } + } + } - // We want these to execute even if the presenter is destroyed and leaks - // for a while. The view can still be garbage collected. - async { - runCatching { - if (context.isOnline()) { - service.update(updatedTrack.toDbTrack(), true) - insertTrack.await(updatedTrack) - } else { - delayedTrackingStore.addItem(updatedTrack) - DelayedTrackingUpdateJob.setupTask(context) - } + private val upsertHistory: UpsertAnimeHistory = Injekt.get() + private val updateEpisode: UpdateEpisode = Injekt.get() + private val getAnime: GetAnime = Injekt.get() + private val sourceManager: AnimeSourceManager = Injekt.get() + private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get() + private val getTracks: GetAnimeTracks = Injekt.get() + private val insertTrack: InsertAnimeTrack = Injekt.get() + private val downloadManager: AnimeDownloadManager by injectLazy() + private val delayedTrackingStore: DelayedTrackingStore = Injekt.get() + private val playerPreferences: PlayerPreferences = Injekt.get() + private val downloadPreferences: DownloadPreferences = Injekt.get() + private val trackPreferences: TrackPreferences = Injekt.get() + private val basePreferences: BasePreferences by injectLazy() + + private suspend fun saveEpisodeHistory(episode: Episode) { + if (basePreferences.incognitoMode().get()) return + upsertHistory.await( + AnimeHistoryUpdate(episode.id, Date()), + ) + } + + private suspend fun saveEpisodeProgress(domainEpisode: Episode?, anime: Anime, seconds: Long, totalSeconds: Long) { + if (basePreferences.incognitoMode().get()) return + val episode = domainEpisode?.toDbEpisode() ?: return + if (totalSeconds > 0L) { + episode.last_second_seen = seconds + episode.total_seconds = totalSeconds + val progress = playerPreferences.progressPreference().get() + if (!episode.seen) episode.seen = episode.last_second_seen >= episode.total_seconds * progress + updateEpisode.await( + EpisodeUpdate( + id = episode.id!!, + seen = episode.seen, + bookmark = episode.bookmark, + lastSecondSeen = episode.last_second_seen, + totalSeconds = episode.total_seconds, + ), + ) + if (trackPreferences.autoUpdateTrack().get() && episode.seen) { + updateTrackEpisodeSeen(episode, anime) + } + if (episode.seen) { + deleteEpisodeIfNeeded(episode.toDomainEpisode()!!, anime) + } + } + } + + private suspend fun deleteEpisodeIfNeeded(episode: Episode, anime: Anime) { + // Determine which chapter should be deleted and enqueue + val sortFunction: (Episode, Episode) -> Int = when (anime.sorting) { + Anime.EPISODE_SORTING_SOURCE -> { c1, c2 -> c2.sourceOrder.compareTo(c1.sourceOrder) } + Anime.EPISODE_SORTING_NUMBER -> { c1, c2 -> c1.episodeNumber.compareTo(c2.episodeNumber) } + Anime.EPISODE_SORTING_UPLOAD_DATE -> { c1, c2 -> c1.dateUpload.compareTo(c2.dateUpload) } + else -> throw NotImplementedError("Unknown sorting method") + } + + val episodes = getEpisodeByAnimeId.await(anime.id) + .sortedWith { e1, e2 -> sortFunction(e1, e2) } + + val currentEpisodePosition = episodes.indexOf(episode) + val removeAfterSeenSlots = downloadPreferences.removeAfterReadSlots().get() + val episodeToDelete = episodes.getOrNull(currentEpisodePosition - removeAfterSeenSlots) + + // Check if deleting option is enabled and chapter exists + if (removeAfterSeenSlots != -1 && episodeToDelete != null) { + enqueueDeleteSeenEpisodes(episodeToDelete, anime) + } + } + + private fun updateTrackEpisodeSeen(episode: DbEpisode, anime: Anime) { + if (!trackPreferences.autoUpdateTrack().get()) return + + val episodeSeen = episode.episode_number.toDouble() + + val trackManager = Injekt.get() + val context = Injekt.get() + + launchIO { + getTracks.await(anime.id) + .mapNotNull { track -> + val service = trackManager.getService(track.syncId) + if (service != null && service.isLogged && + service is AnimeTrackService && episodeSeen > track.lastEpisodeSeen + ) { + val updatedTrack = track.copy(lastEpisodeSeen = episodeSeen) + + // We want these to execute even if the presenter is destroyed and leaks + // for a while. The view can still be garbage collected. + async { + runCatching { + if (context.isOnline()) { + service.animeService.update(updatedTrack.toDbTrack(), true) + insertTrack.await(updatedTrack) + } else { + delayedTrackingStore.addAnimeItem(updatedTrack) + DelayedTrackingUpdateJob.setupTask(context) } } - } else { - null } + } else { + null } - .awaitAll() - .mapNotNull { it.exceptionOrNull() } - .forEach { logcat(LogPriority.INFO, it) } - } + } + .awaitAll() + .mapNotNull { it.exceptionOrNull() } + .forEach { logcat(LogPriority.INFO, it) } } + } - private fun enqueueDeleteSeenEpisodes(episode: Episode, anime: Anime) { - if (!episode.seen) return + private fun enqueueDeleteSeenEpisodes(episode: Episode, anime: Anime) { + if (!episode.seen) return - launchIO { - downloadManager.enqueueDeleteEpisodes(listOf(episode.toDbEpisode()), anime) - } + launchIO { + downloadManager.enqueueEpisodesToDelete(listOf(episode), anime) + } + } + + companion object { + private val externalIntents: ExternalIntents by injectLazy() + suspend fun newIntent(context: Context, animeId: Long?, episodeId: Long?): Intent? { + return externalIntents.getExternalIntent(context, animeId, episodeId) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/Gestures.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/Gestures.kt index 707aa16ce..848b8e93a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/Gestures.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/Gestures.kt @@ -4,7 +4,7 @@ import android.annotation.SuppressLint import android.view.GestureDetector import android.view.MotionEvent import android.view.View -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import uy.kohesive.injekt.injectLazy import kotlin.math.abs diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt index 33c40bf93..7ffca032c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt @@ -54,7 +54,7 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.databinding.PlayerActivityBinding import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.ui.base.activity.BaseActivity -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.util.AniSkipApi import eu.kanade.tachiyomi.util.SkipType import eu.kanade.tachiyomi.util.Stamp @@ -79,12 +79,12 @@ import java.io.InputStream import kotlin.math.abs import kotlin.math.roundToInt -class PlayerActivity : BaseActivity(), +class PlayerActivity : + BaseActivity(), MPVLib.EventObserver, MPVLib.LogObserver { companion object { - fun newIntent(context: Context, animeId: Long?, episodeId: Long?): Intent { return Intent(context, PlayerActivity::class.java).apply { putExtra("anime", animeId) @@ -110,7 +110,9 @@ class PlayerActivity : BaseActivity(), viewModel.saveCurrentEpisodeWatchingProgress() viewModel.mutableState.update { it.copy(anime = null) } - launchIO { viewModel.init(anime, episode) } + launchIO { + viewModel.init(anime, episode).first?.let { setVideoList(it) } + } super.onNewIntent(intent) } @@ -362,12 +364,13 @@ class PlayerActivity : BaseActivity(), lifecycleScope.launchNonCancellable { val initResult = viewModel.init(anime, episode) - if (!initResult.getOrDefault(false)) { - val exception = initResult.exceptionOrNull() ?: IllegalStateException("Unknown err") + if (!initResult.second.getOrDefault(false)) { + val exception = initResult.second.exceptionOrNull() ?: IllegalStateException("Unknown err") withUIContext { setInitialEpisodeError(exception) } } + lifecycleScope.launch { setVideoList(initResult.first!!) } } } val dm = DisplayMetrics() @@ -512,8 +515,11 @@ class PlayerActivity : BaseActivity(), internal fun switchEpisode(previous: Boolean, autoPlay: Boolean = false) { lifecycleScope.launch { val switchMethod = - if (previous && !autoPlay) viewModel.previousEpisode() - else viewModel.nextEpisode() + if (previous && !autoPlay) { + viewModel.previousEpisode() + } else { + viewModel.nextEpisode() + } val errorRes = if (previous) R.string.no_previous_episode else R.string.no_next_episode @@ -528,7 +534,7 @@ class PlayerActivity : BaseActivity(), showLoadingIndicator(false) } else -> { - if(switchMethod.first != null) { + if (switchMethod.first != null) { when { switchMethod.first!!.isEmpty() -> setInitialEpisodeError(Exception("Video list is empty.")) else -> setVideoList(switchMethod.first!!) @@ -1002,14 +1008,14 @@ class PlayerActivity : BaseActivity(), * will call [onShareImageResult] with the path the image was saved on when it's ready. */ fun shareImage() { - viewModel.shareImage(takeScreenshot(),player.timePos) + viewModel.shareImage({ takeScreenshot()!! }, player.timePos) } /** * Called from the presenter when a screenshot is ready to be shared. It shows Android's * default sharing tool. */ - fun onShareImageResult(uri: Uri, seconds: String) { + private fun onShareImageResult(uri: Uri, seconds: String) { val anime = viewModel.anime ?: return val episode = viewModel.currentEpisode ?: return @@ -1025,7 +1031,7 @@ class PlayerActivity : BaseActivity(), * external storage to the presenter. */ fun saveImage() { - viewModel.saveImage(takeScreenshot(),player.timePos) + viewModel.saveImage({ takeScreenshot()!! }, player.timePos) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerControlsView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerControlsView.kt index 49e09401d..a0c652de4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerControlsView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerControlsView.kt @@ -17,7 +17,7 @@ import androidx.core.view.isVisible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.PlayerControlsBinding import eu.kanade.tachiyomi.databinding.PrefSkipIntroLengthBinding -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import `is`.xyz.mpv.MPVLib import `is`.xyz.mpv.PickerDialog import `is`.xyz.mpv.SpeedPickerDialog @@ -251,7 +251,6 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr binding.playbackPositionTxt.text = Utils.prettyTime(position) } activity.viewModel.onSecondReached(position, duration) - } binding.playbackSeekbar.progress = position diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt index 5a6d447ef..579b29348 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt @@ -40,11 +40,8 @@ import eu.kanade.tachiyomi.data.saver.Location import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.reader.SaveImageNotifier -import eu.kanade.tachiyomi.ui.reader.model.InsertPage -import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.ui.reader.model.StencilPage import eu.kanade.tachiyomi.util.AniSkipApi import eu.kanade.tachiyomi.util.Stamp import eu.kanade.tachiyomi.util.editCover @@ -73,7 +70,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import logcat.LogPriority import uy.kohesive.injekt.Injekt @@ -190,7 +186,6 @@ class PlayerViewModel( } } - /** * Called when the user pressed the back button and is going to leave the player. Used to * trigger deletion of the downloaded episodes. @@ -231,16 +226,12 @@ class PlayerViewModel( return anime == null } - private fun needsInit(newAnimeId: Long): Boolean { - return animeId != newAnimeId - } - /** * Initializes this presenter with the given [animeId] and [initialEpisodeId]. This method will * fetch the anime from the database and initialize the initial episode. */ - suspend fun init(animeId: Long, initialEpisodeId: Long): Result { - if (!needsInit()) return Result.success(true) + suspend fun init(animeId: Long, initialEpisodeId: Long): Pair?, Result> { + if (!needsInit()) return Pair(currentVideoList, Result.success(true)) return withIOContext { try { val anime = getAnime.await(animeId) @@ -249,20 +240,23 @@ class PlayerViewModel( if (episodeId == -1L) episodeId = initialEpisodeId checkTrackers(anime) - - val currentEpisode = currentEpisode ?: throw Exception("No episode loaded.") val source = sourceManager.getOrStub(anime.source) episodeList = initEpisodeList() - currentVideoList = EpisodeLoader.getLinks(currentEpisode, anime, source).asFlow().first() + currentEpisode = episodeList.first { initialEpisodeId == it.id } - Result.success(true) + val currentEpisode = currentEpisode ?: throw Exception("No episode loaded.") + + currentVideoList = EpisodeLoader.getLinks(currentEpisode, anime, source).asFlow().first() + episodeId = currentEpisode.id!! + + Pair(currentVideoList, Result.success(true)) } else { // Unlikely but okay - Result.success(false) + Pair(currentVideoList, Result.success(false)) } } catch (e: Throwable) { - Result.failure(e) + Pair(currentVideoList, Result.failure(e)) } } } @@ -273,7 +267,7 @@ class PlayerViewModel( return source is AnimeHttpSource && !EpisodeLoader.isDownloaded(episode, anime) } - suspend fun nextEpisode(): Pair?,String?>? { + suspend fun nextEpisode(): Pair?, String?>? { val anime = anime ?: return null val source = sourceManager.getOrStub(anime.source) @@ -281,18 +275,20 @@ class PlayerViewModel( if (index == episodeList.lastIndex) return null currentEpisode = episodeList[index + 1] - return withIOContext { + return withIOContext { try { val currentEpisode = currentEpisode ?: throw Exception("No episode loaded.") currentVideoList = EpisodeLoader.getLinks(currentEpisode, anime, source).asFlow().first() + episodeId = currentEpisode.id!! } catch (e: Exception) { logcat(LogPriority.ERROR, e) { e.message ?: "Error getting links." } } - Pair(currentVideoList, anime.title + " - " + episodeList[index + 1].name ) + + Pair(currentVideoList, anime.title + " - " + episodeList[index + 1].name) } } - suspend fun previousEpisode(): Pair?,String?>? { + suspend fun previousEpisode(): Pair?, String?>? { val anime = anime ?: return null val source = sourceManager.getOrStub(anime.source) @@ -304,10 +300,11 @@ class PlayerViewModel( try { val currentEpisode = currentEpisode ?: throw Exception("No episode loaded.") currentVideoList = EpisodeLoader.getLinks(currentEpisode, anime, source).asFlow().first() + episodeId = currentEpisode.id!! } catch (e: Exception) { logcat(LogPriority.ERROR, e) { e.message ?: "Error getting links." } } - Pair(currentVideoList, anime.title + " - " + episodeList[index + 1].name) + Pair(currentVideoList, anime.title + " - " + episodeList[index - 1].name) } } @@ -322,20 +319,18 @@ class PlayerViewModel( if (episodeId == -1L) return val episodes = runBlocking { getEpisodeByAnimeId.await(anime.id) } - val selectedEpisode = episodes.find { it.id == episodeId }!!.toDbEpisode() - val seconds = position * 1000L val totalSeconds = duration * 1000L + // Save last second seen and mark as seen if needed + currentEpisode.last_second_seen = seconds + currentEpisode.total_seconds = totalSeconds - // Save last page read and mark as read if needed - selectedEpisode.last_second_seen = seconds - selectedEpisode.total_seconds = totalSeconds val progress = playerPreferences.progressPreference().get() val shouldTrack = !incognitoMode || hasTrackers - if (selectedEpisode.last_second_seen >= selectedEpisode.total_seconds * progress && shouldTrack) { - selectedEpisode.seen = true - updateTrackEpisodeSeen(selectedEpisode) - deleteEpisodeIfNeeded(selectedEpisode) + if (currentEpisode.last_second_seen >= currentEpisode.total_seconds * progress && shouldTrack) { + currentEpisode.seen = true + updateTrackEpisodeSeen(currentEpisode) + deleteEpisodeIfNeeded(currentEpisode) } saveWatchingProgress(currentEpisode) @@ -357,8 +352,11 @@ class PlayerViewModel( val currentEpisode = currentEpisode ?: return val nextEpisode = episodeList[getCurrentEpisodeIndex() + 1] viewModelScope.launchIO { - if (EpisodeLoader.isDownloaded(currentEpisode, anime) - && EpisodeLoader.isDownloaded(nextEpisode, anime)) return@launchIO + if (EpisodeLoader.isDownloaded(currentEpisode, anime) && + EpisodeLoader.isDownloaded(nextEpisode, anime) + ) { + return@launchIO + } val episodesToDownload = getNextEpisodes.await(anime.id, nextEpisode.id!!) .take(amount) downloadManager.downloadEpisodes( @@ -450,8 +448,8 @@ class PlayerViewModel( viewModelScope.launchNonCancellable { updateEpisode.await( EpisodeUpdate( - id = episode.id!!.toLong(), - bookmark = bookmarked, + id = episode.id!!.toLong(), + bookmark = bookmarked, ), ) } @@ -461,7 +459,7 @@ class PlayerViewModel( * Saves the screenshot on the pictures directory and notifies the UI of the result. * There's also a notification to allow sharing the image somewhere else or deleting it. */ - fun saveImage(image: InputStream?, timePos: Int?) { + fun saveImage(imageStream: () -> InputStream, timePos: Int?) { val anime = anime ?: return val context = Injekt.get() @@ -473,7 +471,6 @@ class PlayerViewModel( // Pictures directory. val relativePath = DiskUtil.buildValidFilename(anime.title) - val imageStream = { image!! } // Copy file in background. viewModelScope.launchNonCancellable { @@ -503,7 +500,7 @@ class PlayerViewModel( * get a path to the file and it has to be decompressed somewhere first. Only the last shared * image will be kept so it won't be taking lots of internal disk space. */ - fun shareImage(image: InputStream?, timePos: Int?) { + fun shareImage(imageStream: () -> InputStream, timePos: Int?) { val anime = anime ?: return val context = Injekt.get() @@ -511,7 +508,6 @@ class PlayerViewModel( val seconds = timePos?.let { Utils.prettyTime(it) } ?: return val filename = generateFilename(anime, seconds) ?: return - val imageStream = { image!! } try { viewModelScope.launchIO { @@ -606,7 +602,6 @@ class PlayerViewModel( } } - /** * Enqueues this [episode] to be deleted when [deletePendingEpisodes] is called. The download * manager handles persisting it across process deaths. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/setting/PlayerPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/player/setting/PlayerPreferences.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt index 05caa8d2b..53fe21db1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/setting/PlayerPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.player.setting +package eu.kanade.tachiyomi.ui.player.settings import android.os.Build import eu.kanade.tachiyomi.core.preference.PreferenceStore diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt deleted file mode 100644 index b4e0f384e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreen.kt +++ /dev/null @@ -1,50 +0,0 @@ -package eu.kanade.tachiyomi.ui.stats - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.res.stringResource -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.core.screen.uniqueScreenKey -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow -import eu.kanade.presentation.components.AppBar -import eu.kanade.presentation.components.LoadingScreen -import eu.kanade.presentation.components.Scaffold -import eu.kanade.presentation.more.stats.StatsScreenContent -import eu.kanade.presentation.more.stats.StatsScreenState -import eu.kanade.tachiyomi.R - -class StatsScreen : Screen { - - override val key = uniqueScreenKey - - @Composable - override fun Content() { - val navigator = LocalNavigator.currentOrThrow - - val screenModel = rememberScreenModel { StatsScreenModel() } - val state by screenModel.state.collectAsState() - - if (state is StatsScreenState.Loading) { - LoadingScreen() - return - } - - Scaffold( - topBar = { scrollBehavior -> - AppBar( - title = stringResource(R.string.label_stats), - navigateUp = navigator::pop, - scrollBehavior = scrollBehavior, - ) - }, - ) { paddingValues -> - StatsScreenContent( - state = state as StatsScreenState.Success, - paddingValues = paddingValues, - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsTab.kt new file mode 100644 index 000000000..d78aa5c82 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsTab.kt @@ -0,0 +1,53 @@ +package eu.kanade.tachiyomi.ui.stats + +import androidx.compose.animation.graphics.res.animatedVectorResource +import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter +import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.navigator.tab.LocalTabNavigator +import cafe.adriel.voyager.navigator.tab.TabOptions +import eu.kanade.presentation.components.TabbedScreen +import eu.kanade.presentation.util.Tab +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.stats.anime.animeStatsTab +import eu.kanade.tachiyomi.ui.stats.manga.mangaStatsTab + +data class StatsTab( + private val isManga: Boolean = false, +) : Tab { + + override val options: TabOptions + @Composable + get() { + val isSelected = LocalTabNavigator.current.current.key == key + val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter) + return TabOptions( + index = 8u, + title = stringResource(R.string.label_stats), + icon = rememberAnimatedVectorPainter(image, isSelected), + ) + } + + @Composable + override fun Content() { + val context = LocalContext.current + + TabbedScreen( + titleRes = R.string.label_stats, + tabs = listOf( + animeStatsTab(), + mangaStatsTab(), + ), + startIndex = 1.takeIf { isManga }, + + ) + + LaunchedEffect(Unit) { + (context as? MainActivity)?.ready = true + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt new file mode 100644 index 000000000..9caeb123c --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt @@ -0,0 +1,168 @@ +package eu.kanade.tachiyomi.ui.stats.anime + +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.coroutineScope +import eu.kanade.core.util.fastCountNot +import eu.kanade.core.util.fastDistinctBy +import eu.kanade.core.util.fastFilter +import eu.kanade.core.util.fastFilterNot +import eu.kanade.core.util.fastMapNotNull +import eu.kanade.domain.anime.interactor.GetAnimelibAnime +import eu.kanade.domain.anime.model.isLocal +import eu.kanade.domain.animelib.model.AnimelibAnime +import eu.kanade.domain.animetrack.interactor.GetAnimeTracks +import eu.kanade.domain.animetrack.model.AnimeTrack +import eu.kanade.domain.episode.interactor.GetEpisodeByAnimeId +import eu.kanade.domain.library.service.LibraryPreferences +import eu.kanade.presentation.more.stats.StatsScreenState +import eu.kanade.presentation.more.stats.data.StatsData +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager +import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD +import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED +import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ +import eu.kanade.tachiyomi.data.track.AnimeTrackService +import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.util.lang.launchIO +import kotlinx.coroutines.flow.update +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class AnimeStatsScreenModel( + private val downloadManager: AnimeDownloadManager = Injekt.get(), + private val getAnimelibAnime: GetAnimelibAnime = Injekt.get(), + private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(), + private val getTracks: GetAnimeTracks = Injekt.get(), + private val preferences: LibraryPreferences = Injekt.get(), + private val trackManager: TrackManager = Injekt.get(), +) : StateScreenModel(StatsScreenState.Loading) { + + private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged && it is AnimeTrackService } } + + init { + coroutineScope.launchIO { + val animelibAnime = getAnimelibAnime.await() + + val distinctLibraryAnime = animelibAnime.fastDistinctBy { it.id } + + val animeTrackMap = getAnimeTrackMap(distinctLibraryAnime) + val scoredAnimeTrackerMap = getScoredAnimeTrackMap(animeTrackMap) + + val meanScore = getTrackMeanScore(scoredAnimeTrackerMap) + + val overviewStatData = StatsData.AnimeOverview( + libraryAnimeCount = distinctLibraryAnime.size, + completedAnimeCount = distinctLibraryAnime.count { + it.anime.status.toInt() == SAnime.COMPLETED && it.unseenCount == 0L + }, + totalSeenDuration = getWatchTime(distinctLibraryAnime), + ) + + val titlesStatData = StatsData.AnimeTitles( + globalUpdateItemCount = getGlobalUpdateItemCount(animelibAnime), + startedAnimeCount = distinctLibraryAnime.count { it.hasStarted }, + localAnimeCount = distinctLibraryAnime.count { it.anime.isLocal() }, + ) + + val chaptersStatData = StatsData.Episodes( + totalEpisodeCount = distinctLibraryAnime.sumOf { it.totalEpisodes }.toInt(), + readEpisodeCount = distinctLibraryAnime.sumOf { it.seenCount }.toInt(), + downloadCount = downloadManager.getDownloadCount(), + ) + + val trackersStatData = StatsData.Trackers( + trackedTitleCount = animeTrackMap.count { it.value.isNotEmpty() }, + meanScore = meanScore, + trackerCount = loggedServices.size, + ) + + mutableState.update { + StatsScreenState.SuccessAnime( + overview = overviewStatData, + titles = titlesStatData, + episodes = chaptersStatData, + trackers = trackersStatData, + ) + } + } + } + + private fun getGlobalUpdateItemCount(libraryAnime: List): Int { + val includedCategories = preferences.animelibUpdateCategories().get().map { it.toLong() } + val includedAnime = if (includedCategories.isNotEmpty()) { + libraryAnime.filter { it.category in includedCategories } + } else { + libraryAnime + } + + val excludedCategories = preferences.animelibUpdateCategoriesExclude().get().map { it.toLong() } + val excludedMangaIds = if (excludedCategories.isNotEmpty()) { + libraryAnime.fastMapNotNull { anime -> + anime.id.takeIf { anime.category in excludedCategories } + } + } else { + emptyList() + } + + val updateRestrictions = preferences.libraryUpdateMangaRestriction().get() + return includedAnime + .fastFilterNot { it.anime.id in excludedMangaIds } + .fastDistinctBy { it.anime.id } + .fastCountNot { + (MANGA_NON_COMPLETED in updateRestrictions && it.anime.status.toInt() == SAnime.COMPLETED) || + (MANGA_HAS_UNREAD in updateRestrictions && it.unseenCount != 0L) || + (MANGA_NON_READ in updateRestrictions && it.totalEpisodes > 0 && !it.hasStarted) + } + } + + private suspend fun getAnimeTrackMap(libraryAnime: List): Map> { + val loggedServicesIds = loggedServices.map { it.id }.toHashSet() + return libraryAnime.associate { anime -> + val tracks = getTracks.await(anime.id) + .fastFilter { it.syncId in loggedServicesIds } + + anime.id to tracks + } + } + + private suspend fun getWatchTime(libraryAnimeList: List): Long { + var watchTime = 0L + libraryAnimeList.forEach { libraryAnime -> + getEpisodeByAnimeId.await(libraryAnime.anime.id).forEach { episode -> + watchTime += if (episode.seen) { + episode.totalSeconds + } else { + episode.lastSecondSeen + } + } + } + + return watchTime + } + + private fun getScoredAnimeTrackMap(animeTrackMap: Map>): Map> { + return animeTrackMap.mapNotNull { (animeId, tracks) -> + val trackList = tracks.mapNotNull { track -> + track.takeIf { it.score > 0.0 } + } + if (trackList.isEmpty()) return@mapNotNull null + animeId to trackList + }.toMap() + } + + private fun getTrackMeanScore(scoredAnimeTrackMap: Map>): Double { + return scoredAnimeTrackMap + .map { (_, tracks) -> + tracks.map { + get10PointScore(it) + }.average() + } + .fastFilter { !it.isNaN() } + .average() + } + + private fun get10PointScore(track: AnimeTrack): Float { + val service = trackManager.getService(track.syncId)!! + return service.animeService.get10PointScore(track) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsTab.kt new file mode 100644 index 000000000..6cd41a737 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsTab.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.ui.stats.anime + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.more.stats.AnimeStatsScreenContent +import eu.kanade.presentation.more.stats.StatsScreenState +import eu.kanade.tachiyomi.R + +@Composable +fun Screen.animeStatsTab(): TabContent { + val navigator = LocalNavigator.currentOrThrow + + val screenModel = rememberScreenModel { AnimeStatsScreenModel() } + val state by screenModel.state.collectAsState() + + if (state is StatsScreenState.Loading) { + LoadingScreen() + } + + return TabContent( + titleRes = R.string.label_anime, + content = { contentPadding, _ -> + + if (state is StatsScreenState.Loading) { + LoadingScreen() + } else { + AnimeStatsScreenContent( + state = state as StatsScreenState.SuccessAnime, + paddingValues = contentPadding, + ) + } + }, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt similarity index 93% rename from app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt index eebe91d73..91291340e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.stats +package eu.kanade.tachiyomi.ui.stats.manga import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope @@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ +import eu.kanade.tachiyomi.data.track.MangaTrackService import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.lang.launchIO @@ -27,7 +28,7 @@ import kotlinx.coroutines.flow.update import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -class StatsScreenModel( +class MangaStatsScreenModel( private val downloadManager: DownloadManager = Injekt.get(), private val getLibraryManga: GetLibraryManga = Injekt.get(), private val getTotalReadDuration: GetTotalReadDuration = Injekt.get(), @@ -36,7 +37,7 @@ class StatsScreenModel( private val trackManager: TrackManager = Injekt.get(), ) : StateScreenModel(StatsScreenState.Loading) { - private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged } } + private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged && it is MangaTrackService } } init { coroutineScope.launchIO { @@ -49,7 +50,7 @@ class StatsScreenModel( val meanScore = getTrackMeanScore(scoredMangaTrackerMap) - val overviewStatData = StatsData.Overview( + val overviewStatData = StatsData.MangaOverview( libraryMangaCount = distinctLibraryManga.size, completedMangaCount = distinctLibraryManga.count { it.manga.status.toInt() == SManga.COMPLETED && it.unreadCount == 0L @@ -57,7 +58,7 @@ class StatsScreenModel( totalReadDuration = getTotalReadDuration.await(), ) - val titlesStatData = StatsData.Titles( + val titlesStatData = StatsData.MangaTitles( globalUpdateItemCount = getGlobalUpdateItemCount(libraryManga), startedMangaCount = distinctLibraryManga.count { it.hasStarted }, localMangaCount = distinctLibraryManga.count { it.manga.isLocal() }, @@ -76,7 +77,7 @@ class StatsScreenModel( ) mutableState.update { - StatsScreenState.Success( + StatsScreenState.SuccessManga( overview = overviewStatData, titles = titlesStatData, chapters = chaptersStatData, @@ -147,6 +148,6 @@ class StatsScreenModel( private fun get10PointScore(track: Track): Float { val service = trackManager.getService(track.syncId)!! - return service.get10PointScore(track) + return service.mangaService.get10PointScore(track) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsTab.kt new file mode 100644 index 000000000..67e5faf01 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsTab.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.ui.stats.manga + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.LoadingScreen +import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.more.stats.MangaStatsScreenContent +import eu.kanade.presentation.more.stats.StatsScreenState +import eu.kanade.tachiyomi.R + +@Composable +fun Screen.mangaStatsTab(): TabContent { + val navigator = LocalNavigator.currentOrThrow + + val screenModel = rememberScreenModel { MangaStatsScreenModel() } + val state by screenModel.state.collectAsState() + + if (state is StatsScreenState.Loading) { + LoadingScreen() + } + + return TabContent( + titleRes = R.string.label_manga, + content = { contentPadding, _ -> + + if (state is StatsScreenState.Loading) { + LoadingScreen() + } else { + MangaStatsScreenContent( + state = state as StatsScreenState.SuccessManga, + paddingValues = contentPadding, + ) + } + }, + navigateUp = navigator::pop, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt index dc8c7b5c3..5632a36e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt @@ -4,117 +4,58 @@ import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import cafe.adriel.voyager.core.model.rememberScreenModel -import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator -import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.tab.LocalTabNavigator import cafe.adriel.voyager.navigator.tab.TabOptions -import eu.kanade.presentation.updates.UpdateScreen -import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog +import eu.kanade.presentation.components.TabbedScreen import eu.kanade.presentation.util.Tab import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.ui.download.manga.DownloadQueueScreen -import eu.kanade.tachiyomi.ui.home.HomeScreen +import eu.kanade.tachiyomi.ui.download.DownloadsTab import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.manga.MangaScreen -import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event -import kotlinx.coroutines.flow.collectLatest +import eu.kanade.tachiyomi.ui.updates.anime.animeUpdatesTab +import eu.kanade.tachiyomi.ui.updates.manga.mangaUpdatesTab -object UpdatesTab : Tab { +data class UpdatesTab( + private val fromMore: Boolean, + private val inMiddle: Boolean, +) : Tab { override val options: TabOptions @Composable get() { val isSelected = LocalTabNavigator.current.current.key == key val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter) + val index: UShort = if (fromMore) 5u else if (inMiddle) 2u else 1u return TabOptions( - index = 1u, + index = index, title = stringResource(R.string.label_recent_updates), icon = rememberAnimatedVectorPainter(image, isSelected), ) } - override suspend fun onReselect(navigator: Navigator) { - navigator.push(DownloadQueueScreen) + navigator.push(DownloadsTab()) } @Composable override fun Content() { val context = LocalContext.current - val navigator = LocalNavigator.currentOrThrow - val screenModel = rememberScreenModel { UpdatesScreenModel() } - val state by screenModel.state.collectAsState() - UpdateScreen( - state = state, - snackbarHostState = screenModel.snackbarHostState, - lastUpdated = screenModel.lastUpdated, - relativeTime = screenModel.relativeTime, - onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) }, - onSelectAll = screenModel::toggleAllSelection, - onInvertSelection = screenModel::invertSelection, - onUpdateLibrary = screenModel::updateLibrary, - onDownloadChapter = screenModel::downloadChapters, - onMultiBookmarkClicked = screenModel::bookmarkUpdates, - onMultiMarkAsReadClicked = screenModel::markUpdatesRead, - onMultiDeleteClicked = screenModel::showConfirmDeleteChapters, - onUpdateSelected = screenModel::toggleSelection, - onOpenChapter = { - val intent = ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId) - context.startActivity(intent) - }, + TabbedScreen( + titleRes = R.string.label_recent_updates, + tabs = listOf( + animeUpdatesTab(context, fromMore), + mangaUpdatesTab(context, fromMore), + ), ) - val onDismissDialog = { screenModel.setDialog(null) } - when (val dialog = state.dialog) { - is UpdatesScreenModel.Dialog.DeleteConfirmation -> { - UpdatesDeleteConfirmationDialog( - onDismissRequest = onDismissDialog, - onConfirm = { screenModel.deleteChapters(dialog.toDelete) }, - ) - } - null -> {} - } - LaunchedEffect(Unit) { - screenModel.events.collectLatest { event -> - when (event) { - Event.InternalError -> screenModel.snackbarHostState.showSnackbar(context.getString(R.string.internal_error)) - is Event.LibraryUpdateTriggered -> { - val msg = if (event.started) { - R.string.updating_library - } else { - R.string.update_already_running - } - screenModel.snackbarHostState.showSnackbar(context.getString(msg)) - } - } - } - } - - LaunchedEffect(state.selectionMode) { - HomeScreen.showBottomNav(!state.selectionMode) - } - - LaunchedEffect(state.isLoading) { - if (!state.isLoading) { - (context as? MainActivity)?.ready = true - } - } - DisposableEffect(Unit) { - screenModel.resetNewUpdatesCount() - - onDispose { - screenModel.resetNewUpdatesCount() - } + (context as? MainActivity)?.ready = true } } } + +private const val TAB_ANIME = 0 +private const val TAB_MANGA = 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt similarity index 92% rename from app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt index 61bce847f..c792a5ca4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/animeupdates/AnimeUpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.animeupdates +package eu.kanade.tachiyomi.ui.updates.anime import android.app.Application import android.content.Context @@ -11,26 +11,24 @@ import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.core.prefs.asState import eu.kanade.core.util.addOrRemove import eu.kanade.core.util.insertSeparators +import eu.kanade.domain.anime.interactor.GetAnime +import eu.kanade.domain.animeupdates.interactor.GetAnimeUpdates +import eu.kanade.domain.animeupdates.model.AnimeUpdatesWithRelations import eu.kanade.domain.episode.interactor.GetEpisode import eu.kanade.domain.episode.interactor.SetSeenStatus import eu.kanade.domain.episode.interactor.UpdateEpisode import eu.kanade.domain.episode.model.EpisodeUpdate import eu.kanade.domain.library.service.LibraryPreferences -import eu.kanade.domain.anime.interactor.GetAnime import eu.kanade.domain.ui.UiPreferences -import eu.kanade.domain.animeupdates.interactor.GetAnimeUpdates -import eu.kanade.domain.animeupdates.model.AnimeUpdatesWithRelations -import eu.kanade.domain.episode.model.Episode -import eu.kanade.presentation.components.EpisodeDownloadAction import eu.kanade.presentation.animeupdates.AnimeUpdatesUiModel +import eu.kanade.presentation.components.EpisodeDownloadAction +import eu.kanade.tachiyomi.animesource.AnimeSourceManager import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadCache import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadManager import eu.kanade.tachiyomi.data.animedownload.AnimeDownloadService import eu.kanade.tachiyomi.data.animedownload.model.AnimeDownload import eu.kanade.tachiyomi.data.animelib.AnimelibUpdateService -import eu.kanade.tachiyomi.animesource.AnimeSourceManager -import eu.kanade.tachiyomi.ui.player.PlayerActivity -import eu.kanade.tachiyomi.ui.player.setting.PlayerPreferences +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.toDateKey @@ -281,32 +279,6 @@ class AnimeUpdatesScreenModel( toggleAllSelection(false) } - fun openEpisode(updatesItem: List, context: Context, altPlayer: Boolean = false) { - coroutineScope.launchNonCancellable { - updatesItem - .groupBy { it.update.animeId } - .entries - .forEach { (animeId, updates) -> - val anime = getAnime.await(animeId) ?: return@forEach - val source = sourceManager.get(anime.source) ?: return@forEach - val episode = updates.firstNotNullOf { getEpisode.await(it.update.episodeId) } - - openEpisodeInternal(episode, context) - // if (useExternalPlayer != altPlayer) { - // openEpisodeExternal(episode, anime, source) - // } - } - } - } - - private fun openEpisodeInternal(episode: Episode, context: Context) { - context.startActivity(PlayerActivity.newIntent(context, episode.animeId, episode.id)) - } - - // private fun openEpisodeExternal(episode: Episode, anime: Anime, source: AnimeSource) { - // TODO: add external player setting - // } - fun showConfirmDeleteEpisodes(updatesItem: List) { setDialog(Dialog.DeleteConfirmation(updatesItem)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt new file mode 100644 index 000000000..e87ea7846 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/anime/AnimeUpdatesTab.kt @@ -0,0 +1,164 @@ +package eu.kanade.tachiyomi.ui.updates.anime + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FlipToBack +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.SelectAll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.animeupdates.AnimeUpdateScreen +import eu.kanade.presentation.animeupdates.AnimeUpdatesDeleteConfirmationDialog +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.TabContent +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.anime.AnimeScreen +import eu.kanade.tachiyomi.ui.home.HomeScreen +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.player.ExternalIntents +import eu.kanade.tachiyomi.ui.player.PlayerActivity +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import eu.kanade.tachiyomi.util.lang.launchIO +import kotlinx.coroutines.flow.collectLatest +import uy.kohesive.injekt.injectLazy + +@Composable +fun Screen.animeUpdatesTab( + context: Context, + fromMore: Boolean, +): TabContent { + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { AnimeUpdatesScreenModel() } + val scope = rememberCoroutineScope() + val state by screenModel.state.collectAsState() + + val navigateUp: (() -> Unit)? = if (fromMore) navigator::pop else null + + fun openEpisodeInternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(PlayerActivity.newIntent(context, animeId, episodeId)) + } + + suspend fun openEpisodeExternal(context: Context, animeId: Long, episodeId: Long) { + context.startActivity(ExternalIntents.newIntent(context, animeId, episodeId)) + } + + suspend fun openEpisode(updateItem: AnimeUpdatesItem, altPlayer: Boolean = false) { + val playerPreferences: PlayerPreferences by injectLazy() + val update = updateItem.update + if (playerPreferences.alwaysUseExternalPlayer().get() != altPlayer) { + openEpisodeExternal(context, update.animeId, update.episodeId) + } else { + openEpisodeInternal(context, update.animeId, update.episodeId) + } + } + + return TabContent( + titleRes = R.string.label_animeupdates, + searchEnabled = false, + content = { contentPadding, _ -> + AnimeUpdateScreen( + state = state, + snackbarHostState = screenModel.snackbarHostState, + contentPadding = contentPadding, + lastUpdated = screenModel.lastUpdated, + relativeTime = screenModel.relativeTime, + onClickCover = { item -> navigator.push(AnimeScreen(item.update.animeId)) }, + onSelectAll = screenModel::toggleAllSelection, + onInvertSelection = screenModel::invertSelection, + onUpdateLibrary = screenModel::updateLibrary, + onDownloadEpisode = screenModel::downloadEpisodes, + onMultiBookmarkClicked = screenModel::bookmarkUpdates, + onMultiMarkAsSeenClicked = screenModel::markUpdatesSeen, + onMultiDeleteClicked = screenModel::showConfirmDeleteEpisodes, + onUpdateSelected = screenModel::toggleSelection, + onOpenEpisode = { updateItem: AnimeUpdatesItem, altPlayer: Boolean -> + scope.launchIO { + openEpisode(updateItem, altPlayer) + } + Unit + }, + ) + + val onDismissDialog = { screenModel.setDialog(null) } + when (val dialog = state.dialog) { + is AnimeUpdatesScreenModel.Dialog.DeleteConfirmation -> { + AnimeUpdatesDeleteConfirmationDialog( + onDismissRequest = onDismissDialog, + onConfirm = { screenModel.deleteEpisodes(dialog.toDelete) }, + ) + } + null -> {} + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + when (event) { + AnimeUpdatesScreenModel.Event.InternalError -> screenModel.snackbarHostState.showSnackbar( + context.getString( + R.string.internal_error, + ), + ) + is AnimeUpdatesScreenModel.Event.LibraryUpdateTriggered -> { + val msg = if (event.started) { + R.string.updating_library + } else { + R.string.update_already_running + } + screenModel.snackbarHostState.showSnackbar(context.getString(msg)) + } + } + } + } + + LaunchedEffect(state.selectionMode) { + HomeScreen.showBottomNav(!state.selectionMode) + } + + LaunchedEffect(state.isLoading) { + if (!state.isLoading) { + (context as? MainActivity)?.ready = true + } + } + DisposableEffect(Unit) { + screenModel.resetNewUpdatesCount() + + onDispose { + screenModel.resetNewUpdatesCount() + } + } + }, + actions = + if (screenModel.state.collectAsState().value.selected.isNotEmpty()) { + listOf( + AppBar.Action( + title = stringResource(R.string.action_select_all), + icon = Icons.Outlined.SelectAll, + onClick = { screenModel.toggleAllSelection(true) }, + ), + AppBar.Action( + title = stringResource(R.string.action_select_inverse), + icon = Icons.Outlined.FlipToBack, + onClick = { screenModel.invertSelection() }, + ), + ) + } else { + listOf( + AppBar.Action( + title = stringResource(R.string.action_update_library), + icon = Icons.Outlined.Refresh, + onClick = { screenModel.updateLibrary() }, + ), + ) + }, + navigateUp = navigateUp, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt similarity index 98% rename from app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt index 080fc63de..5224db171 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesScreenModel.kt @@ -1,4 +1,4 @@ -package eu.kanade.tachiyomi.ui.updates +package eu.kanade.tachiyomi.ui.updates.manga import android.app.Application import android.content.Context @@ -50,7 +50,7 @@ import java.text.DateFormat import java.util.Calendar import java.util.Date -class UpdatesScreenModel( +class MangaUpdatesScreenModel( private val sourceManager: SourceManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(), private val downloadCache: DownloadCache = Injekt.get(), @@ -105,7 +105,7 @@ class UpdatesScreenModel( coroutineScope.launchIO { merge(downloadManager.queue.statusFlow(), downloadManager.queue.progressFlow()) .catch { logcat(LogPriority.ERROR, it) } - .collect(this@UpdatesScreenModel::updateDownloadState) + .collect(this@MangaUpdatesScreenModel::updateDownloadState) } } @@ -384,7 +384,7 @@ class UpdatesScreenModel( data class UpdatesState( val isLoading: Boolean = true, val items: List = emptyList(), - val dialog: UpdatesScreenModel.Dialog? = null, + val dialog: MangaUpdatesScreenModel.Dialog? = null, ) { val selected = items.filter { it.selected } val selectionMode = selected.isNotEmpty() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt new file mode 100644 index 000000000..b54372652 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/manga/MangaUpdatesTab.kt @@ -0,0 +1,139 @@ +package eu.kanade.tachiyomi.ui.updates.manga + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.FlipToBack +import androidx.compose.material.icons.outlined.Refresh +import androidx.compose.material.icons.outlined.SelectAll +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.TabContent +import eu.kanade.presentation.updates.UpdateScreen +import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.ui.home.HomeScreen +import eu.kanade.tachiyomi.ui.main.MainActivity +import eu.kanade.tachiyomi.ui.manga.MangaScreen +import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import kotlinx.coroutines.flow.collectLatest + +@Composable +fun Screen.mangaUpdatesTab( + context: Context, + fromMore: Boolean, +): TabContent { + val navigator = LocalNavigator.currentOrThrow + val screenModel = rememberScreenModel { MangaUpdatesScreenModel() } + val state by screenModel.state.collectAsState() + + val navigateUp: (() -> Unit)? = if (fromMore) navigator::pop else null + + return TabContent( + titleRes = R.string.label_updates, + searchEnabled = false, + content = { contentPadding, _ -> + UpdateScreen( + state = state, + snackbarHostState = screenModel.snackbarHostState, + contentPadding = contentPadding, + lastUpdated = screenModel.lastUpdated, + relativeTime = screenModel.relativeTime, + onClickCover = { item -> navigator.push(MangaScreen(item.update.mangaId)) }, + onSelectAll = screenModel::toggleAllSelection, + onInvertSelection = screenModel::invertSelection, + onUpdateLibrary = screenModel::updateLibrary, + onDownloadChapter = screenModel::downloadChapters, + onMultiBookmarkClicked = screenModel::bookmarkUpdates, + onMultiMarkAsReadClicked = screenModel::markUpdatesRead, + onMultiDeleteClicked = screenModel::showConfirmDeleteChapters, + onUpdateSelected = screenModel::toggleSelection, + onOpenChapter = { + val intent = + ReaderActivity.newIntent(context, it.update.mangaId, it.update.chapterId) + context.startActivity(intent) + }, + ) + + val onDismissDialog = { screenModel.setDialog(null) } + when (val dialog = state.dialog) { + is MangaUpdatesScreenModel.Dialog.DeleteConfirmation -> { + UpdatesDeleteConfirmationDialog( + onDismissRequest = onDismissDialog, + onConfirm = { screenModel.deleteChapters(dialog.toDelete) }, + ) + } + null -> {} + } + + LaunchedEffect(Unit) { + screenModel.events.collectLatest { event -> + when (event) { + MangaUpdatesScreenModel.Event.InternalError -> screenModel.snackbarHostState.showSnackbar( + context.getString( + R.string.internal_error, + ), + ) + is MangaUpdatesScreenModel.Event.LibraryUpdateTriggered -> { + val msg = if (event.started) { + R.string.updating_library + } else { + R.string.update_already_running + } + screenModel.snackbarHostState.showSnackbar(context.getString(msg)) + } + } + } + } + + LaunchedEffect(state.selectionMode) { + HomeScreen.showBottomNav(!state.selectionMode) + } + + LaunchedEffect(state.isLoading) { + if (!state.isLoading) { + (context as? MainActivity)?.ready = true + } + } + DisposableEffect(Unit) { + screenModel.resetNewUpdatesCount() + + onDispose { + screenModel.resetNewUpdatesCount() + } + } + }, + actions = + if (screenModel.state.collectAsState().value.selected.isNotEmpty()) { + listOf( + AppBar.Action( + title = stringResource(R.string.action_select_all), + icon = Icons.Outlined.SelectAll, + onClick = { screenModel.toggleAllSelection(true) }, + ), + AppBar.Action( + title = stringResource(R.string.action_select_inverse), + icon = Icons.Outlined.FlipToBack, + onClick = { screenModel.invertSelection() }, + ), + ) + } else { + listOf( + AppBar.Action( + title = stringResource(R.string.action_update_library), + icon = Icons.Outlined.Refresh, + onClick = { screenModel.updateLibrary() }, + ), + ) + }, + navigateUp = navigateUp, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt deleted file mode 100644 index b068b047f..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiBottomNavigationView.kt +++ /dev/null @@ -1,189 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.TimeInterpolator -import android.content.Context -import android.os.Parcel -import android.os.Parcelable -import android.util.AttributeSet -import android.view.ViewPropertyAnimator -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.max -import androidx.customview.view.AbsSavedState -import androidx.interpolator.view.animation.FastOutLinearInInterpolator -import androidx.interpolator.view.animation.LinearOutSlowInInterpolator -import com.google.android.material.bottomnavigation.BottomNavigationView -import eu.kanade.domain.library.service.LibraryPreferences -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale -import eu.kanade.tachiyomi.util.system.pxToDp -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class TachiyomiBottomNavigationView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = R.attr.bottomNavigationStyle, - defStyleRes: Int = R.style.Widget_Design_BottomNavigationView, -) : BottomNavigationView(context, attrs, defStyleAttr, defStyleRes) { - - override fun inflateMenu(resId: Int) { - val libraryPreferences: LibraryPreferences = Injekt.get() - when (libraryPreferences.bottomNavStyle().get()) { - 1 -> super.inflateMenu(R.menu.main_nav_history) - 2 -> super.inflateMenu(R.menu.main_nav_no_manga) - else -> super.inflateMenu(resId) - } - } - - private var currentAnimator: ViewPropertyAnimator? = null - - private var currentState = STATE_UP - - override fun onSaveInstanceState(): Parcelable { - val superState = super.onSaveInstanceState() - return SavedState(superState).also { - it.currentState = currentState - it.translationY = translationY - } - } - - override fun onRestoreInstanceState(state: Parcelable?) { - if (state is SavedState) { - super.onRestoreInstanceState(state.superState) - super.setTranslationY(state.translationY) - currentState = state.currentState - } else { - super.onRestoreInstanceState(state) - } - } - - override fun setTranslationY(translationY: Float) { - // Disallow translation change when state down - if (currentState == STATE_DOWN) return - super.setTranslationY(translationY) - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - bottomNavPadding = h.pxToDp.dp - } - - /** - * Shows this view up. - */ - fun slideUp() = post { - currentAnimator?.cancel() - clearAnimation() - - currentState = STATE_UP - animateTranslation( - 0F, - SLIDE_UP_ANIMATION_DURATION, - LinearOutSlowInInterpolator(), - ) - bottomNavPadding = height.pxToDp.dp - } - - /** - * Hides this view down. [setTranslationY] won't work until [slideUp] is called. - */ - fun slideDown() = post { - currentAnimator?.cancel() - clearAnimation() - - currentState = STATE_DOWN - animateTranslation( - height.toFloat(), - SLIDE_DOWN_ANIMATION_DURATION, - FastOutLinearInInterpolator(), - ) - bottomNavPadding = 0.dp - } - - private fun animateTranslation(targetY: Float, duration: Long, interpolator: TimeInterpolator) { - currentAnimator = animate() - .translationY(targetY) - .setInterpolator(interpolator) - .setDuration(duration) - .applySystemAnimatorScale(context) - .setListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - postInvalidate() - } - }, - ) - } - - internal class SavedState : AbsSavedState { - var currentState = STATE_UP - var translationY = 0F - - constructor(superState: Parcelable) : super(superState) - - constructor(source: Parcel, loader: ClassLoader?) : super(source, loader) { - currentState = source.readInt() - translationY = source.readFloat() - } - - override fun writeToParcel(out: Parcel, flags: Int) { - super.writeToParcel(out, flags) - out.writeInt(currentState) - out.writeFloat(translationY) - } - - companion object { - @JvmField - val CREATOR: Parcelable.ClassLoaderCreator = object : Parcelable.ClassLoaderCreator { - override fun createFromParcel(source: Parcel, loader: ClassLoader): SavedState { - return SavedState(source, loader) - } - - override fun createFromParcel(source: Parcel): SavedState { - return SavedState(source, null) - } - - override fun newArray(size: Int): Array { - return newArray(size) - } - } - } - } - - companion object { - private const val STATE_DOWN = 1 - private const val STATE_UP = 2 - - private const val SLIDE_UP_ANIMATION_DURATION = 225L - private const val SLIDE_DOWN_ANIMATION_DURATION = 175L - - private var bottomNavPadding by mutableStateOf(0.dp) - - /** - * Merges [bottomNavPadding] to the origin's [PaddingValues] bottom side. - */ - @ReadOnlyComposable - @Composable - fun withBottomNavPadding(origin: PaddingValues = PaddingValues()): PaddingValues { - val layoutDirection = LocalLayoutDirection.current - return PaddingValues( - start = origin.calculateStartPadding(layoutDirection), - top = origin.calculateTopPadding(), - end = origin.calculateEndPadding(layoutDirection), - bottom = max(origin.calculateBottomPadding(), bottomNavPadding), - ) - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiNavigationRailView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiNavigationRailView.kt deleted file mode 100644 index 263b9160e..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiNavigationRailView.kt +++ /dev/null @@ -1,24 +0,0 @@ -package eu.kanade.tachiyomi.widget - -import android.content.Context -import android.util.AttributeSet -import com.google.android.material.navigationrail.NavigationRailView -import eu.kanade.domain.library.service.LibraryPreferences -import eu.kanade.tachiyomi.R -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get - -class TachiyomiNavigationRailView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, -) : NavigationRailView(context, attrs) { - - override fun inflateMenu(resId: Int) { - val libraryPreferences: LibraryPreferences = Injekt.get() - when (libraryPreferences.bottomNavStyle().get()) { - 1 -> super.inflateMenu(R.menu.main_nav_history) - 2 -> super.inflateMenu(R.menu.main_nav_no_manga) - else -> super.inflateMenu(resId) - } - } -} diff --git a/app/src/main/res/drawable/ic_updates_outline_24dp.xml b/app/src/main/res/drawable/ic_updates_outline_24dp.xml new file mode 100644 index 000000000..d5d75c6ae --- /dev/null +++ b/app/src/main/res/drawable/ic_updates_outline_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout-sw720dp/main_activity.xml b/app/src/main/res/layout-sw720dp/main_activity.xml deleted file mode 100644 index 3eed6ef96..000000000 --- a/app/src/main/res/layout-sw720dp/main_activity.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/main_nav.xml b/app/src/main/res/menu/main_nav.xml index 4ee2a0dc3..74667c9a5 100644 --- a/app/src/main/res/menu/main_nav.xml +++ b/app/src/main/res/menu/main_nav.xml @@ -6,18 +6,18 @@ android:title="@string/label_animelib" /> diff --git a/app/src/main/res/menu/main_nav_history.xml b/app/src/main/res/menu/main_nav_history.xml deleted file mode 100644 index 78bf535bb..000000000 --- a/app/src/main/res/menu/main_nav_history.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/menu/main_nav_no_manga.xml b/app/src/main/res/menu/main_nav_no_manga.xml deleted file mode 100644 index 7f04b0f5c..000000000 --- a/app/src/main/res/menu/main_nav_no_manga.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml deleted file mode 100644 index 9a8c3f8cf..000000000 --- a/app/src/main/res/values-iw/strings.xml +++ /dev/null @@ -1,687 +0,0 @@ - - - ืขื“ื›ื•ื ื™ื ืœืชื•ืกืฃ - ืขื“ื›ื•ื ื™ื ืœืคืจืง - ื ืคื•ืฅ - ื”ื”ื•ืจื“ื” ืžื•ืฉื”ื™ืช - ืื™ืŸ ื—ื™ื‘ื•ืจ ืจืฉืช ื–ืžื™ืŸ - ืื™ืŸ ื—ื™ื‘ื•ืจ Wi-Fi ื–ืžื™ืŸ - ืœื ื ื™ืชืŸ ืœื”ื•ืจื™ื“ ืืช ื”ืคืจืง ื‘ื’ืœืœ ืฉื’ื™ืื” ื‘ืœืชื™ ืฆืคื•ื™ื” - ืฉื’ื™ืื” - ืื ื ืขื“ื›ืŸ ืืช ืืคืœื™ืงืฆื™ื™ืช WebView ืœืงื‘ืœืช ืชืื™ืžื•ืช ื˜ื•ื‘ื” ื™ื•ืชืจ - ืขืงื™ืคื” ืฉืœ Cloudflare ื ื›ืฉืœื” - ืื™ืŸ ืœืš ืงื˜ื’ื•ืจื™ื•ืช. ืœื—ืฅ ืขืœ ื›ืคืชื•ืจ ื”ืคืœื•ืก ื›ื“ื™ ืœื™ืฆื•ืจ ืื—ื“ ืœืืจื’ื•ืŸ ื”ืกืคืจื™ื™ื” ืฉืœืš. - ื”ืกืคืจื™ื™ื” ืฉืœืš ืจื™ืงื” - ืื™ืŸ ื”ื•ืจื“ื•ืช - ื›ืจื™ื›ื” ืฉืœ ืžื ื’ื” - - ืขื“ื›ื•ืŸ ืœืชื•ืกืฃ ื–ืžื™ืŸ - ืขื“ื›ื•ื ื™ื ืœ-%d ืชื•ืกืคื™ื ื–ืžื™ื ื™ื - ืขื“ื›ื•ื ื™ื ืœ-%d ืชื•ืกืคื™ื ื–ืžื™ื ื™ื - ืขื“ื›ื•ื ื™ื ืœ-%d ืชื•ืกืคื™ื ื–ืžื™ื ื™ื - - ื’ืจืกื” ื—ื“ืฉื” ื–ืžื™ื ื”! - ืฉื’ื™ืื” ื‘ื”ื•ืจื“ื” - ืœื—ืฅ ื›ื“ื™ ืœื”ืชืงื™ืŸ - ืžื•ืจื™ื“โ€ฆ - ืžื—ืคืฉ ืขื“ื›ื•ื ื™ืโ€ฆ - ืื™ืŸ ืขื“ื›ื•ื ื™ื ื—ื“ืฉื™ื ื–ืžื™ื ื™ื - ื”ื•ืจื“ื” - ื‘ื—ืจ ืงื•ื‘ืฅ ื’ื™ื‘ื•ื™ - ื‘ื—ืจ ืชืžื•ื ืช ื›ืจื™ื›ื” - ืื ื ื”ื•ืกืฃ ืืช ื”ืžื ื’ื” ืœืกืคืจื™ื™ื” ืฉืœืš ืœืคื ื™ ืฉืชืขืฉื” ื–ืืช - ืขื“ื›ื•ืŸ ื”ื›ืจื™ื›ื” ื ื›ืฉืœ - - ืคืจืงื™ื %1$s ื•ืื—ื“ ื ื•ืกืฃ - ืคืจืงื™ื %1$s ื•-%2$d ื ื•ืกืคื™ื - ืคืจืงื™ื %1$s ื•-%2$d ื ื•ืกืคื™ื - ืคืจืงื™ื %1$s ื•-%2$d ื ื•ืกืคื™ื - - ืคืจืงื™ื %1$s - ืคืจืง %1$s ื•-%2$d ื ื•ืกืคื™ื - ืคืจืง %1$s - - ืคืจืง ืื—ื“ ื—ื“ืฉ - %1$d ืคืจืงื™ื ื—ื“ืฉื™ื - %1$d ืคืจืงื™ื ื—ื“ืฉื™ื - %1$d ืคืจืงื™ื ื—ื“ืฉื™ื - - - ืขื‘ื•ืจ ื›ื•ืชืจ ืื—ื“ - ืขื‘ื•ืจ %d ื›ื•ืชืจื™ื - ืขื‘ื•ืจ %d ื›ื•ืชืจื™ื - ืขื‘ื•ืจ %d ื›ื•ืชืจื™ื - - ื ืžืฆืื• ืคืจืงื™ื ื—ื“ืฉื™ื - ืžื—ืคืฉ ืคืจืงื™ื ื—ื“ืฉื™ื - ืœื ื ื™ืชืŸ ืœื”ื•ืจื™ื“ ืคืจืงื™ื. ืืคืฉืจ ืœื ืกื•ืช ืฉื•ื‘ ื‘ื“ืฃ ื”ื”ื•ืจื“ื•ืช - ื”ืขืชืง - ื”ืขื‘ืจื” - ื‘ื—ืจ ืžืงื•ืจ ืฉื‘ืจืฆื•ื ืš ืœื”ืขื‘ื™ืจ ืžืžื ื• - ื‘ื—ืจ ื ืชื•ื ื™ื ื›ื“ื™ ืœื›ืœื•ืœ - ืžืขื“ื›ืŸ ืกืคืจื™ื™ื” - ื˜ืขื™ื ืช ื”ืขืžื•ื“ื™ื ื ื›ืฉืœื”: %1$s - ื˜ื•ืขืŸ ื“ืคื™ืโ€ฆ - ืื™ืŸ ืคืจืง ืงื•ื“ื - ืื™ืŸ ืคืจืง ื”ื‘ื - ืงื•ื“ื: - ื”ื‘ื: - ื ื•ื›ื—ื™: - ืกื™ื™ื: - ื”ืื ืœื”ืฉืชืžืฉ ื‘ืชืžื•ื ื” ื–ื• ื›ืฆื™ื•ืจ ื›ืจื™ื›ื”\? - ืœื ื ื™ืชืŸ ื”ื™ื” ืœืคืขื ื— ืืช ื”ืชืžื•ื ื” - ื”ืคืจืง ื”ืงื•ื“ื ืœื ื ืžืฆื - ื”ืคืจืง ื”ื‘ื ืœื ื ืžืฆื - ืขืžื•ื“: %1$d - ื”ื’ื“ืจ ื›ื›ืจื™ื›ื” - ื”ื›ืจื™ื›ื” ืขื•ื“ื›ื ื” - ืกื ืŸ ืžื•ืชืื ืื™ืฉื™ืช - ื”ืชืžื•ื ื” ื ืฉืžืจื” - ืืคืก ืืช ื›ืœ ื”ืคืจืงื™ื ืœืžืื ื’ื” ื–ื• - ืคืขื•ืœื” ื–ื• ืชืกื™ืจ ืืช ื”ืชืืจื™ืš ื”ืงืจื™ืื” ืฉืœ ืคืจืง ื–ื”. ื”ืื ืืชื” ื‘ื˜ื•ื—\? - ื”ืงื˜ื’ื•ืจื™ื•ืช ื ืžื—ืงื• - ืงื˜ื’ื•ืจื™ื” ืขื ืฉื ื–ื” ื›ื‘ืจ ืงื™ื™ืžืช! - ืžื—ื‘ืจ - ืกื•ื’ - ื”ื•ืชื—ืœ - ืกื˜ื˜ื•ืก - ืกื˜ื˜ื•ืก - ื›ื•ืชืจืช - ืฆื™ื•ืŸ - ืงืจื™ืื” ืžื—ื“ืฉ - ืžืชื›ื ืŸ ืœืงืจื•ื - ืžื•ืฉื”ื” - ื‘ื”ืžืชื ื” - ื”ื•ืฉืžื˜ - ื”ื•ืฉืœื - ืžื™ืงื•ื ื”ื”ื•ืจื“ื” ืœื ื—ื•ืงื™ - ื”ืื ืืชื” ื‘ื˜ื•ื— ืฉื‘ืจืฆื•ื ืš ืœืžื—ื•ืง ืืช ื”ืคืจืงื™ื ืฉื ื‘ื—ืจื•\? - ืœื ื ืงืจื - ื”ื›ืœ - ืžื•ืชืื ืื™ืฉื™ืช - 10 ืคืจืงื™ื ื”ื‘ืื™ื - 5 ืคืจืงื™ื ื”ื‘ืื™ื - ืคืจืง ื”ื‘ื - ื”ื•ืจื“ ื›ืžื•ืช ืžื•ืชืืžืช ืื™ืฉื™ืช - ืœืคื™ ืžืกืคืจ ืคืจืง - ืœืคื™ ืžืงื•ืจ - ืžืฆื‘ ืžื™ื•ืŸ - ืžืกืคืจ ืคืจืง - ื›ื•ืชืจืช - ืžื•ืฉื”ื” - ืฉื’ื™ืื” - ืžื•ืจื™ื“ (%1$d/%2$d) - ืคืจืงื™ื %1$s - ืœื”ื•ืกื™ืฃ ืžืื ื’ื” ืœืกืคืจื™ื™ื”\? - ื”ืžืงื•ืจ ืœื ื”ื•ืชืงืŸ: %1$s - ืœืžื—ื•ืง ืคืจืงื™ื ืฉื™ืจื“ื•\? - ื ื•ืกืฃ ืœืกืคืจื™ื™ื” - ื”ืกืจ ืžื”ืกืคืจื™ื™ื” - ื”ื•ืกืจ ืžื”ืกืคืจื™ื™ื” - ื›ื•ืชืจืช - ืœื ื™ื“ื•ืข - ื ืžืฉืš - ืชื™ืื•ืจ - ืžื ื’ื” ื–ื• ื”ื•ืกืจื” ืžืžืกื“ ื”ื ืชื•ื ื™ื. - ื”ืžืื•ื—ืจ ื‘ื™ื•ืชืจ - ื—ื™ืคื•ืฉ ื’ืœื•ื‘ืœื™โ€ฆ - ืื—ืจ - ืžืื ื’ื” ืžืงื•ืžื™ืช - ื‘ื“ื•ืง ืืชืจ ื‘-WebView - ืœื ื ืžืฆืื• ืชื•ืฆืื•ืช - ืื™ืŸ ืชื•ืฆืื•ืช ื ื•ืกืคื•ืช - ืžืงื•ืžื™ - ืžืขื“ื›ืŸ ืงื˜ื’ื•ืจื™ื” - ืฉื’ื™ืื” ืœื ื™ื“ื•ืขื” - ืœื ื™ื›ื•ืœ ืœื”ืชื—ื‘ืจ - ืืชื” ืขื›ืฉื™ื• ืžื ื•ืชืง - ื”ืชื ืชืง - ื”ืชื ืชืง ืž-%1$s\? - ืžื—ื•ื‘ืจ - ืกื™ืกืžื” - ื›ืชื•ื‘ืช ื“ื•ื\"ืœ - ืฉื ืžืฉืชืžืฉ - ื”ืชื—ื‘ืจ ืืœ %1$s - ืขื•ื–ืจ ื‘ืชื™ืงื•ืŸ ื‘ืื’ื™ื. ืœื ื™ื™ืฉืœื—ื• ื ืชื•ื ื™ื ืจื’ื™ืฉื™ื - ืฉืœื— ื“ื•ื—ื•ืช ืงืจื™ืกื” - ืชื•ืกืฃ ื–ื” ื ื—ืชื ืขื ืื™ืฉื•ืจ ืœื ืžื”ื™ืžืŸ ื•ืœื ื”ื•ืคืขืœ. -\n -\nืชื•ืกืฃ ื–ื“ื•ื ื™ ื™ื›ื•ืœ ืœืงืจื•ื ืืช ืื™ืฉื•ืจื™ ื”ื”ืชื—ื‘ืจื•ืช ื”ืžืื•ื—ืกื ื™ื ื‘-Tachiyomi ืื• ืœื”ืจื™ืฅ ืงื•ื“ ืฉืจื™ืจื•ืชื™. -\n -\nืขืœ ื™ื“ื™ ืืžื•ืŸ ื‘ืื™ืฉื•ืจ ื–ื” ืืชื” ืžืงื‘ืœ ืกื™ื›ื•ื ื™ื ืืœื”. - ื”ืกืจ ื”ืชืงื ื” - ืœื ืžืื•ืžืช - ื”ื•ืชืงืŸ - ืžืชืงื™ืŸ - ืžื•ืจื™ื“ - ืžืžืชื™ืŸ - ื”ืชืงืŸ - ืขื“ื›ื•ืŸ - ืขื“ื›ื•ื ื™ื ืžืžืชื™ื ื™ื - ื”ื›ืœ - ื”ื›ืœ - ืฉืืœ ืชืžื™ื“ - ืงื˜ื’ื•ืจื™ื™ืช ื‘ืจื™ืจืช ื”ืžื—ื“ืœ - ื‘ืžืฆื‘ \"ื ื’ืžืจื”\" - ื˜ื•ืขืŸ - ื”ื’ื‘ืœื•ืช ืขื“ื›ื•ื ื™ื ืื•ื˜ื•ืžื˜ื™ื™ื - ืคืขื ื‘ืฉื‘ื•ืข - ืคืขื ื‘ื™ื•ืžื™ื™ื - ืคืขื ื‘ื™ื•ื - ื›ืœ 12 ืฉืขื•ืช - ื›ืœ 6 ืฉืขื•ืช - ื›ื‘ื•ื™ - ืขื“ื›ื•ื ื™ื ืื•ื˜ื•ืžื˜ื™ื™ื - ืขื“ื›ื•ืŸ ื’ืœื•ื‘ืœื™ - ืžืื•ื–ืŸ - ืžืื•ื ืš - ื”ืกืชืจ ืชื•ื›ืŸ ื”ืชืจืื•ืช - ืžืกืš ืžืื•ื‘ื˜ื— ืžืกืชื™ืจ ืืช ืชื•ื›ืŸ ื”ืืคืœื™ืงืฆื™ื” ื‘ืขืช ื”ื—ืœืคืช ืืคืœื™ืงืฆื™ื•ืช ื•ื—ื•ืกื ืฆื™ืœื•ืžื™ ืžืกืš - - ืœืื—ืจ ื“ืงื” ืื—ืช - ืœืื—ืจ ืฉืชื™ ื“ืงื•ืช - ืœืื—ืจ %1$s ื“ืงื•ืช - ืœืื—ืจ %1$s ื“ืงื•ืช - - ืœืขื•ืœื ืœื - ืชืžื™ื“ - ื ืขื™ืœื” ื›ืืฉืจ ืื™ื ื• ืคืขื™ืœ - ื“ื•ืจืฉ ื‘ื™ื˜ื•ืœ ื ืขื™ืœื” - ืื‘ื˜ื—ื” - ื ื™ื”ื•ืœ ื”ืชืจืื•ืช - ืชื‘ื ื™ืช ืชืืจื™ืš - ืคืขื™ืœ - ื›ื‘ื•ื™ - ืžืฆื‘ ื—ืฉื•ืš - ืื•ื“ื•ืช - ืžืชืงื“ื - ื”ื•ืจื“ื•ืช - ืกืคืจื™ื™ื” - ื›ืœืœื™ - ื”ืืคืœื™ืงืฆื™ื” ืื™ื ื” ื–ืžื™ื ื” - ื˜ื•ืขืŸโ€ฆ - ืจืขื ื•ืŸ - ืงื“ื™ืžื” - ื—ื–ืจื” - ืฉื™ื—ื–ื•ืจ - ืฆื•ืจ - ื‘ื˜ืœ ืคืขื•ืœื” ืื—ืจื•ื ื” - ืื™ืคื•ืก - ืฉืžื•ืจ - ืฉืชืฃ - ื”ืชืงืŸ - ืขื‘ื•ืจ ืœืชื—ืชื™ืช ื”ื“ืฃ - ืขื‘ื•ืจ ืœืจืืฉ ื”ื“ืฃ - ื”ื™ืฉืŸ ื‘ื™ื•ืชืจ - ื”ื—ื“ืฉ ื‘ื™ื•ืชืจ - ืกื“ืจ ืžื—ื“ืฉ - ืžื™ื•ืŸ - ื‘ื˜ืœ ื”ื›ืœ - ืชืฆื•ื’ื” - ื™ืจื“ - ืžืขืงื‘ - ืขื•ื“ - ื‘ื™ื˜ื•ืœ - ืคืจืงื™ื ืฉื™ืจื“ื• - ืจืฉื™ืžื” - ืจืฉืช ืงื•ืžืคืงื˜ื™ืช - ืžืฆื‘ ืชืฆื•ื’ื” - ืคืชื— ื‘-WebView - ืคืชื— ื‘ื“ืคื“ืคืŸ - ื”ืžืฉืš - ื”ืกืจ - ื ืกื” ืฉื•ื‘ - ืคืจืง ื”ื‘ื - ืคืจืง ืงื•ื“ื - ื”ืคืกืง - ืขืฆื•ืจ - ื”ืฆื’ ืคืจืงื™ื - ืขืจื•ืš ืืช ื”ื›ืจื™ื›ื” - ืกื“ืจ ืงื˜ื’ื•ืจื™ื•ืช - ืฉื ื” ืฉื ืงื˜ื’ื•ืจื™ื” - ืขืจื•ืš ืงื˜ื’ื•ืจื™ื•ืช - ื”ื•ืกืฃ ืงื˜ื’ื•ืจื™ื” - ื’ืจืกื” - ืื•ืคื˜ื™ืžื™ื–ืฆื™ื™ืช ืกื•ืœืœื” ื›ื‘ืจ ืžื•ืฉื‘ืชืช - ืขื•ื–ืจ ื‘ืขื“ื›ื•ื ื™ ืกืคืจื™ื•ืช ืจืงืข ื•ื’ื™ื‘ื•ื™ื™ื - ื”ืฉื‘ืชืช ื”ืื•ืคื˜ื™ืžื™ื–ืฆื™ื” ืฉืœ ื”ืกื•ืœืœื” - ืขื“ื›ืŸ ืกื˜ื˜ื•ืก, ืฆื™ื•ืŸ ื•ืคืจืง ืื—ืจื•ืŸ ืฉื ืงืจื ืžืฉื™ืจื•ืชื™ ื”ืžืขืงื‘ - ื”ืจืฉื•ืžื•ืช ื ืžื—ืงื• - ื”ืื ืืชื” ื‘ื˜ื•ื—\? ืคืจืงื™ื ืฉื ืงืจืื• ื•ื”ื”ืชืงื“ืžื•ืช ืฉืœ ืžื ื’ื” ืฉืื™ื ื” ื‘ืกืคืจื™ื™ื” ื™ืื‘ื“ื• - ืžื—ืง ืืช ื”ื™ืกื˜ื•ืจื™ื™ืช ื”ืžื ื’ื” ืฉืื™ื ื ืฉืžื•ืจื™ื ื‘ืกืคืจื™ื” ืฉืœืš - ื ืงื” ืืช ืžืกื“ ื”ื ืชื•ื ื™ื - ืขื•ื’ื™ื•ืช ื ื•ืงื• - ื ืงื” ืขื•ื’ื™ื•ืช - ืื™ืจืขื” ืฉื’ื™ืื” ื‘ืžื”ืœืš ื”ื ื™ืงื•ื™ - ื–ื™ื›ืจื•ืŸ ื”ืžื˜ืžื•ืŸ ื ื•ืงื”. %1$d ืงื‘ืฆื™ื ื ืžื—ืงื• - ื‘ืฉื™ืžื•ืฉ: %1$s - ื ืงื” ืืช ื–ื™ื›ืจื•ืŸ ื”ืžื˜ืžื•ืŸ ืฉืœ ื”ืคืจืงื™ื - ืžื™ื™ืฆืจ ื’ื™ื‘ื•ื™ - ืžืฉื—ื–ืจ ื’ื™ื‘ื•ื™ - ืžื” ืืชื” ืจื•ืฆื” ืœื’ื‘ื•ืช\? - ื”ืฉื—ื–ื•ืจ ื”ื•ืฉืœื - ื’ื™ื‘ื•ื™ ื ื•ืฆืจ - ืžืกืคืจ ื’ื™ื‘ื•ื™ื™ื ืžืงืกื™ืžืœื™ - ืชื“ื™ืจื•ืช ื’ื™ื‘ื•ื™ - ื’ื™ื‘ื•ื™ื™ื ืื•ื˜ื•ืžื˜ื™ื™ื - ืžื™ืงื•ื ื’ื™ื‘ื•ื™ - ืฉื—ื–ืจ ืกืคืจื™ื™ื” ืžืงื•ื‘ืฅ ื’ื™ื‘ื•ื™ - ืฉื—ื–ื•ืจ ื’ื™ื‘ื•ื™ - ื ื™ืชืŸ ืœืฉื™ืžื•ืฉ ืขืœ ืžื ืช ืœืฉื—ื–ืจ ืืช ื”ืกืคืจื™ื™ื” ื”ื ื•ื›ื—ื™ืช - ืฆื•ืจ ื’ื™ื‘ื•ื™ - ืฉื™ืจื•ืชื™ื - ืขื“ื›ืŸ ื”ืชืงื“ืžื•ืช ืœืื—ืจ ื”ืงืจื™ืื” - ื”ื•ืจื“ ืคืจืงื™ื ื—ื“ืฉื™ื - ื”ืคืจืง ื”ื—ืžื™ืฉื™ ืžื”ืกื•ืฃ ืฉื ืงืจื - ื”ืคืจืง ื”ืจื‘ื™ืขื™ ืžื”ืกื•ืฃ ืฉื ืงืจื - ื”ืคืจืง ื”ืฉืœื™ืฉื™ ืžื”ืกื•ืฃ ืฉื ืงืจื - ื”ืคืจืง ื”ืฉื ื™ ืžื”ืกื•ืฃ ืฉื ืงืจื - ืคืจืง ืฉื ืงืจื ื‘ืคืขื ื”ืื—ืจื•ื ื” - ืžื™ืงื•ื ืžื•ืชืื ืื™ืฉื™ืช - ืื•ื˜ื•ืžื˜ื™ ืœืื—ืจ ืกื™ื•ื ื”ืงืจื™ืื” - ืื—ืจื™ ืฉืžืกื•ืžืŸ ื™ื“ื ื™ืช ื›ื ืงืจื - ืžื™ืงื•ื ื”ื”ื•ืจื“ื” - ื”ืฆื’ ืชืžื™ื“ ืžืขื‘ืจื™ ืคืจืงื™ื - ืืœืคื - ื›ื—ื•ืœ - ื™ืจื•ืง - ืื“ื•ื - ืžืื•ื–ืŸ ื ืขื•ืœ - ืžืื•ื ืš ื ืขื•ืœ - ื—ื•ืคืฉื™ - ื‘ืจื™ืจืช ื”ืžื—ื“ืœ ืฉืœ ืกื•ื’ ื”ืกื™ื‘ื•ื‘ - ืžื”ื™ืจื” - ืจื’ื™ืœื” - ื‘ืœื™ ืื ื™ืžืฆื™ื” - ืžืจื›ื– - ื™ืžื™ืŸ - ืฉืžืืœ - ืื•ื˜ื•ืžื˜ื™ - ืžื™ืงื•ื ื”ืชื—ืœื” ืฉืœ ื”ื”ื’ื“ืœื” - ื”ืชืืžื” ื—ื›ืžื” - ื’ื•ื“ืœ ืžืงื•ืจื™ - ื”ืชืื ืœื’ื•ื‘ื” - ื”ืชืื ืœืจื•ื—ื‘ - ืœืžืชื•ื— - ื”ืชืื ืœื’ื•ื“ืœ ืžืกืš - ืกื•ื’ ืงื ื” ืžื™ื“ื” - Webtoon - ืื ื›ื™ - ื™ืžื™ืŸ ืœืฉืžืืœ - ืฉืžืืœ ืœื™ืžื™ืŸ - ื‘ืจื™ืจืช ื”ืžื—ื“ืœ ืฉืœ ืžืฆื‘ ื”ืงืจื™ืื” - ืฉื—ื•ืจ - ืœื‘ืŸ - ืฆื‘ืข ืจืงืข - ืžืงืฉื™ ืขื•ืฆืžืช ืฉืžืข ื”ืคื•ื›ื™ื - ืžืงืฉื™ ืขื•ืฆืžืช ืฉืžืข - ื ื™ื•ื•ื˜ - ื“ืœื’ ืขืœ ืคืจืงื™ื ืžืกื•ื ื ื™ื - ื“ืœื’ ืขืœ ืคืจืงื™ื ื”ืžืกื•ืžื ื™ื ื›ื ืงืจืื• - ื”ืฉืืจ ืžืกืš ื“ืœื•ืง - ืฉืจื•ืฃ / ืžื•ื—ืฉืš - ืžืกืš - ืžืกื ืŸ ืฆื‘ืข ืžื•ืชืื ืื™ืฉื™ืช - ื‘ื”ื™ืจื•ืช ืžื•ืชืืžืช ืื™ืฉื™ืช - ื—ืชื•ืš ื’ื‘ื•ืœื•ืช - ืฆื‘ืข 32-ืกื™ื‘ื™ื•ืช - ื”ืฆื’ ืžืกืคืจ ืขืžื•ื“ - ืžื”ื™ืจื•ืช ื”ื ืคืฉื” ื‘ื”ืงืฉื” ื›ืคื•ืœื” - ื”ื ืคืฉืช ืžืขื‘ืจื™ ื“ืคื™ื - ื”ืฆื’ ืชื•ื›ืŸ ื‘ืื–ื•ืจ ื”ื—ืชื•ืš - ื‘ื“ื•ืง ืื ืงื™ื™ืžื™ื ืขื“ื›ื•ื ื™ ื”ืจื—ื‘ื•ืช - ืชื•ืกืฃ ืœื ืžืื•ืžืช - ืชื•ืกืฃ ื–ื” ืื™ื ื• ื–ืžื™ืŸ ืขื•ื“. - ืžืกืš ืžืœื - ื”ื•ืกืฃ - ืขืจื•ืš - ืขื“ื›ืŸ ืกืคืจื™ื™ื” - ืขื“ื›ื•ืŸ - ืžื—ืง - ื‘ื˜ืœ ืกื™ืžื•ืŸ ืคืจืง - ืกืžืŸ ืคืจืง - ื”ื•ืจื“ - ืกืžืŸ ืืช ื”ืงื•ื“ืžื™ื ื›ื ืงืจืื• - ืกืžืŸ ื›ืœื ื ืงืจื - ืกืžืŸ ื›ื ืงืจื - ืกืžืŸ ื”ื›ืœ - ื—ื™ืคื•ืฉ ื›ืœืœื™ - ื—ื™ืคื•ืฉ - ื”ืคืจืง ื”ืื—ืจื•ืŸ - ื ืงืจื ืœืื—ืจื•ื ื” - ื›ืœ ื”ืคืจืงื™ื - ืืœืคื‘ื™ืชื™ืช - ื”ืกืจ ืืช ื”ืกื ืŸ - ืœื ื ืงืจื - ืกื ืŸ - ืชืคืจื™ื˜ - ื”ื’ื“ืจื•ืช - ืขื–ืจื” - ืคืจื˜ื™ ื”ืจื—ื‘ื” - ื”ืจื—ื‘ื•ืช - ื”ืขื‘ืจื” - ื’ื™ื‘ื•ื™ ื•ืฉื—ื–ื•ืจ - ืžืงื•ืจื•ืช - ื”ื™ืกื˜ื•ืจื™ื” - ืขื“ื›ื•ื ื™ื - ืกืคืจื™ื™ื” - ืชื•ืจ ื”ื”ื•ืจื“ื•ืช - ื”ื’ื“ืจื•ืช - ื”ื™ืกื˜ื•ืจื™ื” - ืคืจืงื™ื - ืžื ื’ื” - ืงื˜ื’ื•ืจื™ื•ืช - ืฉื - ืœื—ืฅ ืื—ื•ืจื” ืฉื•ื‘ ื›ื“ื™ ืœืกื’ื•ืจ - ืฉื—ืจืจ ืืช Aniyomi - ืฉื•ื ื“ื‘ืจ ื ืงืจื ืœืื—ืจื•ื ื” - ืื™ืŸ ืขื™ื“ื›ื•ื ื™ื ืื—ืจื•ื ื™ื - ืกื•ืžืŸ - ืœืื—ืจื•ื ื” - ืงืฆืจ (ื”ื™ื•ื, ืืชืžื•ืœ) - ืจืขื ื•ืŸ ืขื•ืงื‘ื™ื ืื•ื˜ื•ืžื˜ื™ืช - ื‘ืจื™ืจืช ื”ืžื—ื“ืœ - ืขืจื›ืช ื”ื ื•ืฉื ืฉืœ ื”ืืคืœื™ืงืฆื™ื” - ื”ืชื—ืœ ืœื”ื•ืจื™ื“ ืขื›ืฉื™ื• - ื—ืœืง ืขืœื™ื•ืŸ - ื—ืœืง ืืžืฆืขื™ - ื—ืœืง ืชื—ืชื•ืŸ - ื ืขืฅ - ืžืกืš ืžืื•ื‘ื˜ื— - ืคืจื™ื˜ื™ื ืœืฉื•ืจื” - ืขืงื•ื‘ ืื—ืจ ื”ืžืขืจื›ืช - ื‘ื“ื•ืง ืื ื™ืฉ ื›ืจื™ื›ื” ื•ืคืจื˜ื™ื ื—ื“ืฉื™ื ื‘ืขืช ืขื“ื›ื•ืŸ ื”ืกืคืจื™ื™ื” - ืกื”\"ื› ืžื ื’ื” - ื”ื’ื“ืจื•ืช ื—ื™ืคื•ืฉ - ื‘ื—ืจ ืืช ื”ื”ืคืš - ืฉืคื” - ื”ืฆื’ ืงื˜ื’ื•ืจื™ืช ื›ืจื˜ื™ืกื™ื•ืช - ื”ืฆื’ ืžืกืคืจ ืฉืœ ืคืจื™ื˜ื™ื - ื”ืฉื‘ืช - ื”ืกืจ ื ืขื™ืฆื” - ื‘ื˜ืœ ื”ื›ืœ ืœืกื“ืจื” ื–ื• - ืœืคื™ ืชืืจื™ืš ื”ื”ืขืœืื” - ืœืคื™ ืžืกืคืจ ื”ืคืจืง - ืขื•ืœื” - ื™ื•ืจื“ - ืื–ื”ืจื” - ืืžืช ืœืืฉืจ ืืช ื”ืฉื™ื ื•ื™ - ื”ื•ืจื“ ืคืจืงื™ื ืฉืœื ื ืงืจืื• - ืžืฆื‘ ืงืจื™ืื” - ืžืขืงื‘ - ื ื•ืฉื - ืืจื•ืš (ืงืฆืจ+, ืœืคื ื™ n ื™ืžื™ื) - ืืฉืจ ื™ืฆื™ืื” - ืžืงื•ืจื•ืช NSFW (18+) - ื”ืฆื’ ื‘ืจืฉื™ืžื•ืช ืžืงื•ืจื•ืช ื•ืชื•ืกืคื™ื - ื–ื” ืœื ืžื•ื ืข ืชื•ืกืคื™ื ืœื ืจืฉืžื™ื™ื ืื• ืชื•ืกืคื™ื ืฉืขืœื•ืœื™ื ืœืกืžืŸ ื‘ืื•ืคืŸ ืฉื’ื•ื™ ืžืœื”ืฆื™ื’ ืชื•ื›ืŸ NSFW (18+) ื‘ืชื•ืš ื”ืืคืœื™ืงืฆื™ื”. - ื”ื™ื•ื - ืชืฆื•ื’ื” - ืขื“ื›ืŸ ืขื•ืงื‘ื™ื ื‘ืขืช ืขื“ื›ื•ืŸ ื”ืกืคืจื™ื™ื” - ืืคืฉืจ ื”ื›ืœ - ื”ืฉื‘ืช ื”ื›ืœ - ื”ืชื—ืœ - ื”ืขื‘ืจื” - ืจืฉืช ื ื•ื—ื” - ืคืจืงื™ื ืฉืœื ื ืงืจืื• - ืžื ื’ื” ืžืงื•ืžื™ืช - ื˜ื•ืจืงื™ื– ืฆื”ื‘ื”ื‘ - ืžืฆื‘ ืฉื—ื•ืจ ื›ื”ื” ื˜ื”ื•ืจ - ื™ืจื•ืง ืชืคื•ื— - ืชื•ืช ื“ืื™ืงื™ืจื™ - ื™ื™ืŸ ื•ื™ืื ื’ - ืืจื‘ืขื” ืขืœื™ื - ื ื™ื•ื•ื˜ - ืœื—ืฅ ื›ื“ื™ ืœืจืื•ืช ืคืจื˜ื™ื - ืžืจืื” - ื“ืžื“ื•ืžื™ ื—ืฆื•ืช - ื™ื™ืฉื•ืจ ืกืžืœื™ ื ื™ื•ื•ื˜ ื‘ืฆื“ - ื˜ืืงื• - ืจืขื ืŸ ื ืชื•ื ื™ื ื‘ืื•ืคืŸ ืื•ื˜ื•ืžื˜ื™ - ื‘ืžืขืงื‘ - ืชืืจื™ืš ื”ื•ืกืคื” - ืชืืจื™ืš ื”ืื—ื–ื•ืจ ืฉืœ ื”ืคืจืงื™ื - ื“ื™ื ืžื™ - ืžื ื’ื” ื”ื ืžืฆืืช ื‘ืงื˜ื’ื•ืจื™ื™ืช ืžื ื•ืขื™ ื”ืขื“ื›ื•ื ื™ื ืœื ืชืขื•ื“ื›ืŸ ื’ื ืื ื”ื™ื ื ื›ืœืœืช ื‘ืงื˜ื’ื•ืจื™ื™ื” ืื—ืจืช ืฉื›ืŸ ืžืชืขื“ื›ื ืช. - ื”ืจืื” ืžื ื’ื” - ื”ืชื—ื™ืœ - ืขื ืคืจืง(ื™ื) ืฉืœื ื ืงืจื(ื•) - ื”ืขื‘ืจ ืกื“ืจื” ืœืจืืฉ - ื—ื•ืชืžืช ื–ืžืŸ - ื—ื•ืชืžื•ืช ื–ืžืŸ ืงืฉื•ืจื•ืช - ืžื™ื•ืฉืŸ - ื–ื” ืขื•ื“ ืœื ื”ืชื—ื™ืœ - ืขื“ื›ืŸ ื”ื›ืœ - ื”ืืžืŸ - ื”ื”ืจื—ื‘ื” ื”ื–ืืช ืœื ื ืžืฆืืช ื‘ืจืฉื™ืžืช ื”ื”ืจื—ื‘ื•ืช ื”ืจืฉืžื™ื•ืช ืฉืœ Tachiyomi. - ืขืœื•ืœ ืœื”ื›ื™ืœ ืชื•ื›ืŸ ืฉืœื ืžืชืื™ื ืœืžืงื•ื ื”ืขื‘ื•ื“ื” (18+) - ืžืชืงื™ืŸ ื”ืจื—ื‘ื”โ€ฆ - - ืงื˜ื’ื•ืจื™ื™ื” ืื—ืช - ืฉืชื™ ืงื˜ื’ื•ืจื™ื•ืช - %d ืงื˜ื’ื•ืจื™ื•ืช - %d ืงื˜ื’ื•ืจื™ื•ืช - - ื”ืจืื” ืžืกืคื•ืจ ืฉืœ ืคืจืงื™ื ืฉืœื ื ืงืจืื• ื‘ืื™ื™ืงื•ืŸ ื”ืขื“ื›ื•ื ื™ื - ื›ืœื•ืœ: %s - ื”ืจืื” ืœื–ืžืŸ ืงืฆืจ ื›ืฉืคื•ืชื—ื™ื ืืช ืžืฆื‘ ื”ืงืจื™ืื” - ื”ืจืื” ืžืฆื‘ ืงืจื™ืื” - ื‘ืœืชื™ ืจืฉืžื™ - ืคืจื˜ื™ ื”ืืคืœื™ืงืฆื™ื™ื” - ื“ืœื’ ืขืœ ืขื“ื›ื•ื ื™ ื›ื•ืชืจื•ืช - ื ื›ืฉืœ ื‘ืงื‘ืœืช ืจืฉื™ืžืช ื”ื”ืจื—ื‘ื•ืช - ืฉื•\"ืช ื•ืžื“ืจื™ื›ื™ื - - ืืชืžื•ืœ - ืœืคื ื™ ื™ื•ืžื™ื™ื - ืœืคื ื™ %1$d ื™ืžื™ื - ืœืคื ื™ %1$d ื™ืžื™ื - - 18+ - ืื™ืŸ - ื›ืœ 3 ื™ืžื™ื - ืจืง ื‘ Wi-Fi - ื”ื’ื‘ืœื•ืช: %s - ื”ืจืื” ืœื–ืžืŸ ืงืฆืจ ืืช ืกื’ื ื•ืŸ ื”ืงืจื™ืื” ื›ืฉืคื•ืชื—ื™ื ืืช ืžืฆื‘ ื”ืงืจื™ืื” - ืœื—ื™ืฆื” ื”ื•ืคื›ื™ืช - ื›ื‘ื•ื™ - ืคืขื™ืœ - ื˜ื•ื•ื— ื’ื•ื•ื ื™ ื”ืืคื•ืจ - ื”ื•ืคื›ื™ - ืื•ืคืงื™ - ื™ืžื™ืŸ ื•ืฉืžืืœ - ื”ื‘ื - ื™ืžื™ืŸ - ืกื•ื’ ื”ืกื™ื‘ื•ื‘ - ืžืื•ื ืš - ืžืื•ื–ืŸ - 5% - 10% - 15% - ืจื’ื™ืฉื•ืช ื”ื—ื‘ืืช ื”ืชืคืจื™ื˜ ื‘ื’ืœื™ืœื” - ื”ื›ื™ ื’ื‘ื•ื” - ื’ื‘ื•ื” - ื ืžื•ืš - ื”ื›ื™ ื ืžื•ืš - ืžื—ืง ืคืจืงื™ื - ืงื˜ื’ื•ืจื™ื•ืช ืžื•ื—ืจื’ื•ืช - ืžื ื’ื” ื”ื ืžืฆืืช ื‘ืงื˜ื’ื•ืจื™ื™ืช ืžื ื•ืขื™ ื”ื”ื•ืจื“ื•ืช ืœื ืชืขื•ื“ื›ืŸ ื’ื ืื ื”ื™ื ื ื›ืœืœืช ื‘ืงื˜ื’ื•ืจื™ื” ืื—ืจืช ืฉื›ืŸ ื ื›ืœืœืช (ื‘ื”ื•ืจื“ื•ืช). - ืฉืžื•ืจ ื›ืืจื›ื™ื•ืŸ CBZ - ืฉื™ืจื•ืชื™ื ืžืฉื•ืคืจื™ื - ื’ื‘ื•ืœ - ืขืฉื•ื™ ื‘ืฆื•ืจืช L - ืฉืžืืœ - ื”ืงื•ื“ื - ืงืจื™ืื” - 20% - 25% - ืžืฆื‘ ืงืจื™ืื” - ืœืœื - ื”ื•ืจื“ื” ืื•ื˜ื•ืžื˜ื™ืช - ืืคื•ืจ - ืื ื›ื™ ืžืชืžืฉืš - ืขืžื•ื“ื™ื - ืžืื•ื ืš ื”ืคื•ืš - ืฉืžื•ืจ ื“ืคื™ื ื‘ืชื™ืงื™ื•ืช ื ืคืจื“ื•ืช - ืื ื›ื™ - ืฉื ื™ื”ื - ืคืขื•ืœื•ืช - ื”ืจืื” ื‘ืœื—ื™ืฆื” ืืจื•ื›ื” - ืื•ื˜ื•ืžื˜ื™ - ืืคืฉืจ ืžื—ื™ืงืช ืคืจืงื™ื ืฉืกื•ืžื ื• - ืžื ื•ืข - ืฆื•ืจ ืชื™ืงื™ื•ืช ื‘ื”ืชืื ืœื›ื•ืชืจืช ื”ืžื ื’ื” - ืฉื™ืจื•ืชื™ื ื”ืžืกืคืงื™ื ืฉื™ืจื•ืชื™ื ืžืฉื•ืคืจื™ื ืœืžืงื•ืจื•ืช ืกืคืฆื™ืคื™ื™ื. ืžื ื’ื•ืช ื™ื”ื™ื• ื‘ืžืขืงื‘ ืื•ื˜ื•ืžื˜ื™ ืื—ืจื™ ื”ื•ืกืคื” ืœืกืคืจื™ื™ื” ืฉืœืš. - ืžืงื•ืจื•ืช ื—ืกืจื™ื: - ืงื•ื‘ืฅ ื’ื™ื‘ื•ื™ ืœื ืชืงื™ืŸ - ื”ื’ื™ื‘ื•ื™ ืœื ืžื›ื™ืœ ืฉื•ื ืžื ื’ื”. - ื›ืœื•ืœ ืจืง ืžืงื•ืจื•ืช ื ืขื•ืฆื™ื - ืœื ืžื—ื•ื‘ืจ ืœ: %1$s - ืฉื’ื™ืื”: URI ืจื™ืง - ืžืขืงื‘ - ื ืงื” ืืช ื–ื™ื›ืจื•ืŸ ื”ืžื˜ืžื•ืŸ ืฉืœ ื”ืคืจืงื™ื ื›ืฉื”ืืคืœื™ืงืฆื™ื” ื ืกื’ืจืช - ื™ืฉ %1$d ืžื ื’ื” ืฉื ืžืฆืื•ืช ื‘ืžืกื“ ื”ื ืชื•ื ื™ื ืื‘ืœ ืœื ื‘ืกืคืจื™ื™ื” - ืชื’ื™ื - ืœืฉื•ื ื™ื•ืช - ื—ืคืฉ ืืช \"%1$s\" ื’ืœื•ื‘ืœื™ืช - ืžืฆื‘ ืœื ื™ื“ื•ืข - ืžื•ืจืฉื”, ื‘ืขืœ ืจื™ืฉื™ื•ืŸ - ื”ืคืจืกื•ื ื”ืกืชื™ื™ื - - ืคืจืง ืื—ื“ - ืฉื ื™ ืคืจืงื™ื - %1$s ืคืจืงื™ื - %1$s ืคืจืงื™ื - - ื ื›ืฉืœื” ื”ื”ืขืชืงื” ืœืœื•ื— - ืœืคื™ ืชืืจื™ืš ื”ืขืœืื” - ื”ื›ืจื™ื›ื” ื ืฉืžืจื” - ืฉื’ื™ืื” ื‘ืฉืžื™ืจืช ื”ื›ืจื™ื›ื” - ืฉื’ื™ืื” ื‘ืฉื™ืชื•ืฃ ื”ื›ืจื™ื›ื” - ืงื‘ืข ื›ื‘ืจื™ืจืช ื”ืžื—ื“ืœ - ืžืฆื‘ ืงืจื™ืื” - ืžื ื’ื” ืžืชื•ืš ื”ืกืคืจื™ื™ื” - ืคืจืงื™ื ืฉื™ืจื“ื• - ื›ืจื™ื›ื” - ื™ื•ืชืจ - ืคื—ื•ืช - ื›ื‘ืจ ืžืชืจื—ืฉ ื’ื™ื‘ื•ื™ - ืžื“ืจื™ืš ืœืžื ื’ื” ืžืงื•ืžื™ืช - ืื™ืŸ ืžืงื•ืจื•ืช ื ืขื•ืฆื™ื - ืคืจืง ืœื ื ืžืฆื - ืœืกื“ืจื” ื”ื–ืืช - ืžื™ื“ืข ืžืงื•ื‘ืฅ ื”ื’ื™ื‘ื•ื™ ื™ืฉื•ื—ื–ืจ. -\n -\nืœืื—ืจ ืžื›ืŸ ืฆืจื™ืš ืœื”ืชืงื™ืŸ ื”ืจื—ื‘ื•ืช ื—ืกืจื•ืช ื•ืœื”ืชื—ื‘ืจ ืœืฉื™ืจื•ืชื™ ื”ืžืขืงื‘ ื›ื“ื™ ืœื”ืฉืชืžืฉ ื‘ื”ื. - ื™ื›ื•ืœ ืœื”ื™ื•ืช ืฉื”ื’ื™ื‘ื•ื™/ื”ืฉื—ื–ื•ืจ ืœื ื™ืขื‘ื“ื• ื›ืžื• ืฉืฆืจื™ืš ืื ืื•ืคื˜ืžื™ื–ืฆื™ื™ืช MIUI ืžื ื•ืขื”. - ื”ื’ื™ื‘ื•ื™ ื ื›ืฉืœ - ืฉื—ื–ื•ืจ ื”ื’ื™ื‘ื•ื™ ื ื›ืฉืœ - ืฆืจื™ืš ืœืืชื—ืœ ืืช ื”ืืคืœื™ืงืฆื™ื” ื›ื“ื™ ืฉื”ืฉื™ื ื•ื™ื™ื ื™ื™ื›ื ืกื• ืœืชื•ืงืฃ - ืžื“ื™ื ื™ื•ืช ืคืจื˜ื™ื•ืช - ื”ื•ืกืฃ ืœืกืคืจื™ื™ื” - ื”ื•ืขืชืง ืœืœื•ื—: -\n%1$s - ืœื ื ืžืฆืื• ืคืจืงื™ื - ืชืืจื™ืš ื”ืชื—ืœื” - ืชืืจื™ืš ืกื™ื•ื - ืžื” ื—ื“ืฉ - ืขื–ื•ืจ ืœืชืจื’ื - ืจืง ืžื” ืฉื™ืจื“ - ื“ืคื“ืคืŸ - ืœื ื ืงืจื - ืงืจื™ืื” - - 1 ื ืฉืืจ - 2 ื ืฉืืจื• - %1$s ื ืฉืืจื• - %1$s ื ืฉืืจื• - - ื”ืชื—ื‘ืจื•ืช - ื ืขื•ืฅ - %02d ื“ืงื•ืช, %02d ืฉื ื™ื•ืช - ื”ืฉื—ื–ื•ืจ ื‘ื•ื˜ืœ - ืขื“ื›ื•ื ื™ื ืื•ื˜ื•ืžื˜ื™ื™ื ืžืื•ื“ ืžื•ืžืœืฆื™ื. ืจืฆื•ื™ ืœืฉืžื•ืจ ืขื•ืชืงื™ื ื ื•ืกืคื™ื ื‘ืžืงื•ืžื•ืช ืื—ืจื™ื. - ืžืกื“ ื”ื ืชื•ื ื™ื ื ืงื™ - ื‘ื“ื•ืง ืขื“ื›ื•ื ื™ื - ืกื“ืจ ืข\"ื™ - ืชืืจื™ืš - ืžื—ื‘ืจ ืœื ื™ื“ื•ืข - ืžืฆื‘ ืคืจื˜ื™ - ืกื™ื ื•ืŸ ื›ืœ ื”ืžื ื’ื” ื‘ืกืคืจื™ื™ื” ืฉืœืš - ืืชืจ - ืžื™ื“ืข - ืฉื’ื™ืื” ื‘ืฉืžื™ืจืช ื”ืชืžื•ื ื” - %1$s: %2$s ืขืžื•ื“ %3$d - ืขื•ืฆืจ ืืช ืงืจื™ืืช ื”ื”ื™ืกื˜ื•ืจื™ื” - ื‘ื™ื˜ื•ืœ ืžืฆื‘ ืคืจื˜ื™ - ื”ื•ืจื“ื” - - ื”ื•ืฉืœื ื‘ %1$s ืขื ืฉื’ื™ืื” ืื—ืช - ื”ื•ืฉืœื ื‘ %1$s ืขื ืฉืชื™ ืฉื’ื™ืื•ืช - ื”ื•ืฉืœื ื‘ %1$s ืขื %2$s ืฉื’ื™ืื•ืช - ื”ื•ืฉืœื ื‘ %1$s ืขื %2$s ืฉื’ื™ืื•ืช - - ืจืฉืช - ื‘ื•ื˜ืœ/ื” - ื‘ืชื•ืš ื”ืกืคืจื™ื™ื” - ื”ื’ื“ืจื•ืช ืคืจืง - ื”ืื ืืชื” ื‘ื˜ื•ื— ืฉืืชื” ืจื•ืฆื” ืœืฉืžื•ืจ ืืช ื”ื”ื’ื“ืจื•ืช ื”ืืœื” ื›ื‘ืจื™ืจืช ื”ืžื—ื“ืœ\? - ื›ื‘ืจ ืžืชืจื—ืฉ ืฉื—ื–ื•ืจ - ืœื ื ืžืฆืื• ืขืžื•ื“ื™ื - ืžืงื•ืจ ืœื ื ืžืฆื - ื”ื”ื™ืกื˜ื•ืจื™ื” ื ืžื—ืงื” - ืžืชืงื™ืŸ - ื”ืชืงื“ืžื•ืช - ื”ื•ืฉืœื - ื”ื“ืฃ ื”ืงื•ื“ื - ื”ื“ืฃ ื”ื‘ื - ืื•ืงื™ื™, ื–ื” ืžื‘ื™ืš - ื›ืœ ื”ื’ื“ืจื•ืช ืžืฆื‘ ื”ืงืจื™ืื” ืื•ืคืกื• - ื”ื—ืจื’ื”: %s - ืขื“ื›ื•ื ื™ ืืคืœื™ืงืฆื™ื” - ืœื ื ืžืฆืื• ืžืงื•ืจื•ืช ืžื•ืชืงื ื™ื - ื”ื•ืจื“ื•ืช ื’ื“ื•ืœื•ืช ืคื•ื’ืขื•ืช ื‘ืžืงื•ืจื•ืช ื•ื™ื›ื•ืœื•ืช ืœื’ืจื•ื ืœืขื“ื›ื•ื ื™ื ืื™ื˜ื™ื™ื ื™ื•ืชืจ ื•ื’ื ืœืฉื™ืžื•ืฉ ืžื•ื’ื‘ืจ ื‘ืกื•ืœืœื”. ืœื—ืฅ ื›ื“ื™ ืœืœืžื•ื“ ืขื•ื“. - ื’ืจืกื” ื—ื“ืฉื” ื–ืžื™ื ื” ืžื”ื”ื•ืฆืื” ื”ืจืฉืžื™ืช. ืœื—ืฅ ื›ื“ื™ ืœืœืžื•ื“ ืื™ืš ืœื”ืขื‘ื™ืจ ืžื”ื•ืฆืื•ืช ืœื ืจืฉืžื™ื•ืช ืฉืœ F-Droid. - ืขื“ื™ื™ืŸ ืื™ืŸ ืงื˜ื’ื•ืจื™ื•ืช. - ืœื—ืฅ ื›ื“ื™ ืœืœืžื•ื“ ืขื•ื“ - ื ืงื” ื”ื™ืกื˜ื•ืจื™ื” - ื ื›ืฉืœ ืื™ืคื•ืก ื”ื’ื“ืจื•ืช ืžืฆื‘ ื”ืงืจื™ืื” - ืขื“ื›ื•ืŸ ื”ืžื ื’ื” ื”ืื—ืจื•ืŸ - ืกืคื™ืจื” ืฉืœ ืœื ื ืงืจืื• - ืื™ืŸ ืงืฆืช ืกื•ืœืœื” - ื”ื’ื“ืจื•ืช ืžื™ื•ืŸ ื•ืชืฆื•ื’ื” ืœื›ืœ ืงื˜ื’ื•ืจื™ื” ื‘ื ืคืจื“ - ืžืงื•ืจ ืœื ื ืžืฆื - %1$d ืขื“ื›ื•ื ื™ื ื“ื•ืœื’ื• - ื’ืจืกืช ื”ืื ื“ืจื•ืื™ื“ ื”ื–ืืช ื›ื‘ืจ ืœื ื ืชืžื›ืช - WebView ื ืฆืจืš ืœ-Tachiyomi - ื”ื”ื•ืจื“ื” ื”ื•ืฉืœืžื” - ืจืฉืช ืจืง ืฉืœ ื”ื›ืจื™ื›ื•ืช - ืคืชื— ืืช ื™ื•ืžืŸ ื”ืื™ืจื•ืขื™ื - %1$d ืขื“ื›ื•ื ื™ื ื ื›ืฉืœื• - ื”ื’ื“ืœ ืชืžื•ื ื” ืื•ืคืงื™ืช - ืžืฉืคืจ ืืช ื‘ื™ืฆื•ืขื™ ืžืฆื‘ ื”ืงืจื™ืื” ืข\"ื™ ื—ื™ืชื•ืš ืชืžื•ื ื•ืช ื’ื‘ื•ื”ื•ืช ืฉื™ืจื“ื•. - ื“ื•ืœื’ ื‘ื’ืœืœ ืฉื”ืกื“ืจื” ื ื’ืžืจื” - ื“ื•ืœื’ ื‘ื’ืœืœ ืฉื™ืฉ ืคืจืงื™ื ืฉืœื ื ืงืจืื• - ื“ื•ืœื’ ื‘ื’ืœืœ ืฉืื™ืŸ ืคืจืงื™ื ืฉื ืงืจืื• - ืกื’ื•ืจ - ื“ื•ืœื’ื• - - ื“ื•ืœื’ ืคืจืง ืื—ื“, ื”ืžืงื•ืจ ื—ืกืจ ืื• ืฉื”ื•ื ืกื•ื ืŸ ื”ื—ื•ืฆื” - ื“ื•ืœื’ื• ืฉื ื™ ืคืจืงื™ื, ื”ืžืงื•ืจ ื—ืกืจ ืื• ืฉื”ื ืกื•ื ื ื• ื”ื—ื•ืฆื” - ื“ื•ืœื’ื• %d ืคืจืงื™ื, ื”ืžืงื•ืจ ื—ืกืจ ืื• ืฉื”ื ืกื•ื ื ื• ื”ื—ื•ืฆื” - ื“ื•ืœื’ื• %d ืคืจืงื™ื, ื”ืžืงื•ืจ ื—ืกืจ ืื• ืฉื”ื ืกื•ื ื ื• ื”ื—ื•ืฆื” - - ืžืคื—ื™ืช ืคืกื™ื, ืืš ืขืฉื•ื™ ืœื”ืฉืคื™ืข ืขืœ ื”ื‘ื™ืฆื•ืขื™ื - ืฉื’ื™ืื•ืช - ืืคืก ืืช ื”ื’ื“ืจื•ืช ืžืฆื‘ ื”ืงืจื™ืื” ืฉืœ ื›ืœ ืกื“ืจื” ื‘ื ืคืจื“ - Shizuku ืœื ืคื•ืขืœ - ื”ืชืงืŸ ื•ื”ืคืขืœ ืืช Shizuku ื›ื“ื™ ืœื”ืฉืชืžืฉ ื‘Shizuku ื›ืžืชืงื™ืŸ ื”ื”ืจื—ื‘ื•ืช. - ื”ืจืื” ืืช ืคืจื™ืฉืช ืžื™ืงื•ืžื™ ื”ืœื—ื™ืฆื” - ืืชื” ื‘ื˜ื•ื—\? ื›ืœ ื”ื”ื™ืกื˜ื•ืจื™ื” ืชืžื—ืง. - ืžื“ืจื™ืš ื ื“ื™ื“ืช ืžืงื•ืจื•ืช - ืœื ื ื™ืชืŸ ืœื”ื•ืจื™ื“ ืคืจืงื™ื ื›ื™ ืื™ืŸ ืžืกืคื™ืง ืžืงื•ื ืื—ืกื•ืŸ - ืžืขื“ื›ืŸ ืกืคืจื™ื™ื”... (%1$d/%2$d) - ืื–ื”ืจื”: ื”ื•ืจื“ื•ืช ื’ื“ื•ืœื•ืช ืขืœื•ืœื•ืช ืœื’ืจื•ื ืœื”ืื˜ื” ื‘ืžืงื•ืจื•ืช ื•/ืื• ืœื—ืกื™ืžืช Tachiyomi. ืœื—ืฅ ื›ื“ื™ ืœืœืžื•ื“ ืขื•ื“. - ืคื•ืจืžื˜ ืคืจืง ืœื ืชืงื™ืŸ - ื‘ื”ืคืกืงื” - \ No newline at end of file diff --git a/i18n/src/main/res/values-bg/strings.xml b/i18n/src/main/res/values-bg/strings.xml index 845b54707..52397458c 100644 --- a/i18n/src/main/res/values-bg/strings.xml +++ b/i18n/src/main/res/values-bg/strings.xml @@ -269,9 +269,6 @@ ะ”ะฐ ัะต ะธะทั‚ั€ะธัั‚ ะปะธ ะธะทั‚ะตะณะปะตะฝะธั‚ะต ะณะปะฐะฒะธ? ะะฐ ะฟะฐัƒะทะฐ ะ˜ะทั‚ะตะณะปัะฝะตั‚ะพ ัะฟั€ัะฝะพ - ะ—ะฐ ะฒัŠะทัั‚ะฐะฝะพะฒัะฒะฐะฝะตั‚ะพ ัะต ะธะทั‚ะตะณะปัั‚ ะดะฐะฝะฝะธ ะพั‚ ะธะทั‚ะพั‡ะฝะธะบะฐ, ะทะฐ ะบะพะธั‚ะพ ะผะพะถะต ะดะฐ ะฑัŠะดะต ะพั‚ั‡ะตั‚ะตะฝ ั€ะฐะทั…ะพะด ะฝะฐ ะดะฐะฝะฝะธ. -\n -\nะกัŠั‰ะพ ะฟั€ะพะฒะตั€ะตั‚ะต ะดะฐะปะธ ัั‚ะต ะธะทั‚ะตะณะปะธะปะธ ะฒัะธั‡ะบะธ ะฝะตะพะฑั…ะพะดะธะผะธ ั€ะฐะทัˆะธั€ะตะฝะธั ะธ ัั‚ะต ะฒะปะตะทะปะธ ะบะพั€ะตะบั‚ะฝะพ ะฒ ะธะทั‚ะพั‡ะฝะธั†ะธั‚ะต ะธ ัƒัะปัƒะณะธั‚ะต ะทะฐ ะฟั€ะพัะปะตะดัะฒะฐะฝะต ะฟั€ะตะดะธ ะฒัŠะทัั‚ะฐะฝะพะฒัะฒะฐะฝะตั‚ะพ. ะะฐ ะฟะฐัƒะทะฐ ะ˜ะทั‚ะตะณะปัะฝะตั‚ะพ ัะฟั€ัะฝะพ ะ“ะปะพะฑะฐะปะฝะพ ั‚ัŠั€ัะตะฝะต diff --git a/i18n/src/main/res/values-kk/strings.xml b/i18n/src/main/res/values-kk/strings.xml index 35e18dc96..17b3ca183 100644 --- a/i18n/src/main/res/values-kk/strings.xml +++ b/i18n/src/main/res/values-kk/strings.xml @@ -683,7 +683,7 @@ ะ‘ะตะปะณั–ัั–ะท า›ะฐั‚ะตะปั–ะบั‚ั–าฃ ะบะตัั–ั€ั–ะฝะตะฝ ั‚ะฐั€ะฐัƒ ะถาฏะบั‚ะตะฟ ะฐะปั‹ะฝะฑะฐะดั‹ ะ‘ะตั‚ ั„ะฐะนะปั‹ะฝั‹าฃ ะถะพะปั‹ %d ั‚ะฐะฑั‹ะปะผะฐะดั‹ ำจั‚ะบั–ะทัƒะฟ ะถั–ะฑะตั€ั–ะปะดั– - ะขะฐั€ะฐัƒ ะถะฐาฃะฐั€ั‚ัƒะปะฐั€ั‹ + ะขะฐั€ะฐัƒ ะถะฐาฃะฐั€ั‚ัƒะปะฐั€ั‹ าšะพะปะดะฐะฝะฑะฐ ะถะฐาฃะฐั€ั‚ัƒะปะฐั€ั‹ ะšะตาฃะตะนั‚ัƒ ะถะฐาฃะฐั€ั‚ัƒะปะฐั€ั‹ ะะปะดั‹าฃา“ั‹ ะฑะตั‚ diff --git a/i18n/src/main/res/values-lt/strings.xml b/i18n/src/main/res/values-lt/strings.xml index 142783fe7..72ddeaa8a 100644 --- a/i18n/src/main/res/values-lt/strings.xml +++ b/i18n/src/main/res/values-lt/strings.xml @@ -574,7 +574,7 @@ Baigtas Praleistas Atsisiuntimas pristabdytas - Skyriaus atnaujinimai + Skyriaus atnaujinimai Kitas puslapis Iลกvalyti slapukus Iลกvalyti skyriaus talpyklฤ… uลพdarius programฤ… diff --git a/i18n/src/main/res/values-sk/strings.xml b/i18n/src/main/res/values-sk/strings.xml index 7b9bf6420..3adf71c16 100644 --- a/i18n/src/main/res/values-sk/strings.xml +++ b/i18n/src/main/res/values-sk/strings.xml @@ -2,7 +2,7 @@ Nรกzov Kategรณrie - Manga + Zรกznamy v kniลพnici Kapitoly Sledovanie Histรณria @@ -354,7 +354,7 @@ ลคuknutรญm zobrazรญte podrobnosti Podฤพa systรฉmu Tako - Zobraziลฅ mangu + Zobraziลฅ zรกznam Yin a Yang Vzhฤพad Migrovaลฅ @@ -364,9 +364,8 @@ Podฤพa dรกtumu nahratia Zaฤaลฅ Mrieลพka len s obalmi - Lokรกlna manga + Lokรกlny zdroj Jazyk - Nepreฤรญtanรฉ kapitoly Pripnรบลฅ Zruลกiลฅ vลกetko pre tรบto sรฉriu Podฤพa ฤรญsla kapitoly @@ -409,7 +408,7 @@ Uzamknรบลฅ pri neฤinnosti Vลพdy Nikdy - Preskoฤiลฅ aktualizรกciu titulov + Preskoฤiลฅ aktualizรกciu zรกznamov Zobraziลฅ v zoznamoch zdrojov a rozลกรญrenรญ NSFW (18+) zdroje Nedรกvno @@ -485,7 +484,7 @@ Populรกrne Zoznam ฤรญtania Dรกtum ukonฤenia - Poslednรก aktualizรกcia kniลพnice: %1$s + Poslednรก aktualizรกcia kniลพnice: %s Pozrite si svoju nedรกvno aktualizovanรบ mangu Citlivosลฅ pre skrytie ponuky pri posรบvanรญ Nenaลกli sa ลพiadne strรกnky @@ -518,15 +517,12 @@ Teraz ste odhlรกsenรญ Nรกvod pre lokรกlne zdroje Neznรกmy autor - V kniลพnici mรกte poloลพku s rovnakรฝm nรกzvom, ale z inรฉho zdroja (%1$s). -\n -\nChcete eลกte pokraฤovaลฅ\? Nepodarilo sa skopรญrovaลฅ do schrรกnky Nepodarilo sa nรกjsลฅ cestu k sรบboru strรกnky %d Chyby Vyp. Nepodarilo sa stiahnuลฅ kapitoly z dรดvodu nedostatku รบloลพnรฉho priestoru - Celkom mangy + Celkovรฝ poฤet Neoficiรกlne Pรดvodnรฝ Automatickรฝ @@ -571,7 +567,7 @@ ฤŽalลกรญch %d nepreฤรญtanรฝch kapitol Vynechanรฉ - Aktualizรกcie kapitol + Aktualizรกcie kapitol Kontrola aktualizรกciรญ rozลกรญrenia V databรกze je manga %1$d, ktorรก sa nenachรกdza v kniลพnici Aktualizรกcia kniลพnice @@ -628,7 +624,7 @@ Aktualizovanรฉ na v%1$s Iba stiahnutรฉ Zoznam prianรญ - Chystรกte sa odstrรกniลฅ tรบto mangu zo svojej kniลพnice + Chystรกte sa odstrรกniลฅ \"%s\" zo svojej kniลพnice Filtruje vลกetku mangu vo vaลกej kniลพnici Zdroj nie je podporovanรฝ Nenainลกtalovanรฉ @@ -642,7 +638,7 @@ Typ rotรกcie 25% Zatvoriลฅ - Mangy vo vylรบฤenรฝch kategรณriรกch nebudรบ aktualizovanรฉ, aj keฤ sรบ tieลพ v zahrnutรฝch kategรณriรกch. + Zรกznamy vo vylรบฤenรฝch kategรณriรกch nebudรบ aktualizovanรฉ, aj keฤ sรบ tieลพ v zahrnutรฝch kategรณriรกch. ฤŒakajรบce aktualizรกcie ลฝiadne ฤŽalลกia strana @@ -655,7 +651,7 @@ 5% Sluลพby, ktorรฉ poskytujรบ rozลกรญrenรฉ funkcie pre konkrรฉtne zdroje. Mangy sรบ automaticky sledovanรฉ po pridanรญ do vaลกej kniลพnice. Rozdelenie na dve strany - Poslednรก aktualizรกcia mangy + Poslednรก aktualizรกcia Odstrรกniลฅ vลกetko InternalError: Skontrolujte zรกznmy pre ฤalลกie informรกcie 18+ @@ -750,4 +746,10 @@ Preskoฤenรฉ, pretoลพe obsahuje nepreฤรญtanรฉ kapitoly Preskoฤenรฉ, pretoลพe neboli preฤรญtanรฉ ลพiadne kapitoly Formรกt RARv5 nie je podporovanรฝ + Lokรกlna + Stiahnutรฉ + ล tatistiky + Zaฤatรฉ + Hฤพadaลฅโ€ฆ + Otvoriลฅ nรกhodnรฝ zรกznam \ No newline at end of file diff --git a/i18n/src/main/res/values-sq/strings.xml b/i18n/src/main/res/values-sq/strings.xml index 4e6038e09..926e2c41e 100644 --- a/i18n/src/main/res/values-sq/strings.xml +++ b/i18n/src/main/res/values-sq/strings.xml @@ -106,7 +106,7 @@ Faqja e meparshme Pรซrditรซsimet shtesรซ Pรซrditรซsimet e aplikacioneve - Pรซrditรซsimet e kapitullit + Pรซrditรซsimet e kapitullit U anashkalua Shkarkimet e indeksimit Imazhi i shkarkuar nuk mund tรซ ndahej diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index d1310f871..b6cafc953 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -25,11 +25,13 @@ More Settings - Manga Download queue + Download queue + Manga Download queue Anime Download queue Library Manga Anime + Anime Manga Updates History @@ -37,7 +39,7 @@ Manga Sources Anime Sources Backup and restore - Migrate Manga + Migrate Manga Migrate Anime Statistics Extensions @@ -577,7 +579,8 @@ Check for extension updates - Only include pinned sources + Only include pinned manga sources + Only include pinned anime sources Create backup @@ -815,6 +818,8 @@ Invalid download location Chapter settings Episode settings + Error + Paused Are you sure you want to save these settings as default? Also apply to all manga in my library Also apply to all anime in my library @@ -968,10 +973,12 @@ Overview Completed entries Read duration + Watched duration Entries In global update Total Read + Watched Trackers Tracked entries Mean score diff --git a/source-api/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSourceFetcher.kt b/source-api/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSourceFetcher.kt index 0537e4fc5..fb10ae4a3 100644 --- a/source-api/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSourceFetcher.kt +++ b/source-api/src/main/java/eu/kanade/tachiyomi/animesource/online/AnimeHttpSourceFetcher.kt @@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.animesource.model.Video import rx.Observable fun AnimeHttpSource.getVideoUrl(video: Video): Observable