From 88cff99c8b00bfc5348a26c1d2806a6ac4606151 Mon Sep 17 00:00:00 2001 From: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun, 23 Jul 2023 15:50:00 +0200 Subject: [PATCH] chore(merge): merge upstream till v0.14.6 --- .github/workflows/build_pull_request.yml | 4 +- .github/workflows/build_push.yml | 4 +- .github/workflows/issue_moderator.yml | 5 +- app/build.gradle.kts | 8 +- app/proguard-android-optimize.txt | 1 + app/proguard-rules.pro | 3 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 +- app/src/main/AndroidManifest.xml | 4 - .../eu/kanade/core/util/CollectionUtils.kt | 6 +- .../eu/kanade/domain/base/BasePreferences.kt | 7 + .../base/ExtensionInstallerPreference.kt | 2 +- .../entries/anime/interactor/UpdateAnime.kt | 2 +- .../domain/entries/anime/model/Anime.kt | 3 - .../entries/manga/interactor/UpdateManga.kt | 2 +- .../domain/entries/manga/model/Manga.kt | 3 - .../interactor/SyncChaptersWithSource.kt | 2 +- .../items/chapter/model/ChapterFilter.kt | 2 +- .../interactor/SyncEpisodesWithSource.kt | 2 +- .../items/episode/model/EpisodeFilter.kt | 2 +- .../service/DelayedAnimeTrackingUpdateJob.kt | 5 +- .../service/DelayedMangaTrackingUpdateJob.kt | 5 +- .../browse/anime/AnimeSourcesScreen.kt | 3 +- .../browse/anime/BrowseAnimeSourceScreen.kt | 5 +- .../browse/anime/GlobalAnimeSearchScreen.kt | 6 +- .../browse/anime/MigrateAnimeSearchScreen.kt | 6 +- .../browse/manga/BrowseMangaSourceScreen.kt | 5 +- .../browse/manga/GlobalMangaSearchScreen.kt | 6 +- .../browse/manga/MangaSourcesScreen.kt | 3 +- .../browse/manga/MigrateMangaSearchScreen.kt | 6 +- .../presentation/components/AdaptiveSheet.kt | 28 +- .../kanade/presentation/components/AppBar.kt | 29 +- .../kanade/presentation/entries/ItemHeader.kt | 29 +- .../presentation/entries/anime/AnimeScreen.kt | 3 + .../presentation/entries/manga/MangaScreen.kt | 3 + .../library/LibraryColumnsDialog.kt | 126 ++++++++ .../anime/AnimeLibrarySettingsDialog.kt | 31 +- .../manga/MangaLibrarySettingsDialog.kt | 31 +- .../more/settings/screen/AboutScreen.kt | 2 +- .../settings/screen/SettingsAdvancedScreen.kt | 15 +- .../settings/screen/SettingsBackupScreen.kt | 30 +- .../settings/screen/SettingsDownloadScreen.kt | 5 + .../settings/screen/SettingsLibraryScreen.kt | 177 ------------ .../settings/screen/SettingsPlayerScreen.kt | 2 +- .../settings/screen/SettingsReaderScreen.kt | 45 ++- .../settings/screen/debug/DebugInfoScreen.kt | 132 +++++++++ .../screen/{ => debug}/WorkerInfoScreen.kt | 38 +-- .../track/TrackInfoDialogSelector.kt | 11 +- .../eu/kanade/presentation/util/Navigator.kt | 20 ++ .../eu/kanade/presentation/util/Resources.kt | 6 +- app/src/main/java/eu/kanade/tachiyomi/App.kt | 12 +- .../java/eu/kanade/tachiyomi/Migrations.kt | 32 ++- ...BackupCreatorJob.kt => BackupCreateJob.kt} | 30 +- .../tachiyomi/data/backup/BackupManager.kt | 4 +- .../tachiyomi/data/backup/BackupNotifier.kt | 21 +- .../tachiyomi/data/backup/BackupRestoreJob.kt | 85 ++++++ .../data/backup/BackupRestoreService.kt | 141 --------- .../tachiyomi/data/backup/BackupRestorer.kt | 60 ++-- .../download/anime/AnimeDownloadManager.kt | 2 +- .../download/anime/AnimeDownloadNotifier.kt | 7 +- .../download/anime/AnimeDownloadProvider.kt | 16 +- .../download/anime/AnimeDownloadService.kt | 6 +- .../data/download/anime/AnimeDownloader.kt | 19 +- .../download/manga/MangaDownloadManager.kt | 2 +- .../download/manga/MangaDownloadNotifier.kt | 7 +- .../download/manga/MangaDownloadProvider.kt | 16 +- .../download/manga/MangaDownloadService.kt | 6 +- .../data/download/manga/MangaDownloader.kt | 21 +- .../library/anime/AnimeLibraryUpdateJob.kt | 31 +- .../anime/AnimeLibraryUpdateNotifier.kt | 123 ++++---- .../library/manga/MangaLibraryUpdateJob.kt | 31 +- .../manga/MangaLibraryUpdateNotifier.kt | 121 ++++---- .../data/notification/NotificationReceiver.kt | 48 +--- .../data/notification/Notifications.kt | 18 +- .../data/preference/PreferenceValues.kt | 37 --- .../data/track/shikimori/ShikimoriApi.kt | 2 +- .../data/updater/AppUpdateNotifier.kt | 4 +- .../extension/ExtensionUpdateNotifier.kt | 37 ++- .../util/AnimeExtensionInstallService.kt | 10 +- .../anime/util/AnimeExtensionInstaller.kt | 3 +- .../util/MangaExtensionInstallService.kt | 10 +- .../manga/util/MangaExtensionInstaller.kt | 3 +- .../source/anime/AnimeSourceExtensions.kt | 4 +- .../source/manga/MangaSourceExtensions.kt | 4 +- .../base/delegate/SecureActivityDelegate.kt | 2 +- .../ui/base/delegate/ThemingDelegate.kt | 2 +- .../extension/AnimeExtensionFilterScreen.kt | 2 +- .../extension/AnimeExtensionsScreenModel.kt | 2 +- .../details/AnimeExtensionDetailsScreen.kt | 6 +- .../search/AnimeSourceSearchScreen.kt | 2 +- .../migration/search/MigrateAnimeDialog.kt | 7 +- .../search/MigrateAnimeSearchScreen.kt | 4 +- .../source/browse/BrowseAnimeSourceScreen.kt | 14 +- .../browse/BrowseAnimeSourceScreenModel.kt | 54 +--- .../globalsearch/AnimeSearchScreenModel.kt | 49 +--- .../globalsearch/GlobalAnimeSearchScreen.kt | 63 ++-- .../extension/MangaExtensionFilterScreen.kt | 2 +- .../extension/MangaExtensionsScreenModel.kt | 2 +- .../details/MangaExtensionDetailsScreen.kt | 6 +- .../search/MangaSourceSearchScreen.kt | 2 +- .../migration/search/MigrateMangaDialog.kt | 7 +- .../search/MigrateMangaSearchScreen.kt | 4 +- .../source/browse/BrowseMangaSourceScreen.kt | 14 +- .../browse/BrowseMangaSourceScreenModel.kt | 54 +--- .../globalsearch/GlobalMangaSearchScreen.kt | 63 ++-- .../globalsearch/MangaSearchScreenModel.kt | 49 +--- .../ui/entries/anime/AnimeScreenModel.kt | 17 +- .../ui/entries/manga/MangaScreenModel.kt | 17 +- .../library/anime/AnimeLibraryScreenModel.kt | 3 +- .../ui/library/anime/AnimeLibraryTab.kt | 8 +- .../library/manga/MangaLibraryScreenModel.kt | 3 +- .../ui/library/manga/MangaLibraryTab.kt | 8 +- .../tachiyomi/ui/player/PlayerViewModel.kt | 2 +- .../settings/dialogs/SkipIntroLengthDialog.kt | 2 +- .../tachiyomi/ui/reader/ChapterTransition.kt | 170 +++++++++++ .../tachiyomi/ui/reader/ReaderActivity.kt | 24 +- .../tachiyomi/ui/reader/ReaderViewModel.kt | 47 +-- .../tachiyomi/ui/reader/SaveImageNotifier.kt | 7 +- .../setting/ReaderColorFilterSettings.kt | 8 +- .../reader/setting/ReaderGeneralSettings.kt | 6 +- .../ui/reader/setting/ReaderPreferences.kt | 26 +- .../setting/ReaderReadingModeSettings.kt | 46 ++- .../ui/reader/setting/ReaderSettingsSheet.kt | 55 ++-- .../ui/reader/viewer/MissingChapters.kt | 46 +-- .../ui/reader/viewer/ReaderPageImageView.kt | 4 +- .../ui/reader/viewer/ReaderTransitionView.kt | 161 +---------- .../ui/reader/viewer/ViewerConfig.kt | 9 +- .../ui/reader/viewer/ViewerNavigation.kt | 8 +- .../ui/reader/viewer/pager/PagerConfig.kt | 12 + .../ui/reader/viewer/pager/PagerPageHolder.kt | 14 + .../viewer/pager/PagerTransitionHolder.kt | 2 +- .../ui/reader/viewer/pager/PagerViewer.kt | 12 +- .../reader/viewer/pager/PagerViewerAdapter.kt | 6 +- .../reader/viewer/webtoon/WebtoonAdapter.kt | 6 +- .../ui/stats/anime/AnimeStatsScreenModel.kt | 2 +- .../ui/stats/manga/MangaStatsScreenModel.kt | 2 +- .../kanade/tachiyomi/util/AnimeExtensions.kt | 2 +- .../eu/kanade/tachiyomi/util/CrashLogUtil.kt | 36 +-- .../kanade/tachiyomi/util/MangaExtensions.kt | 2 +- .../util/chapter/ChapterRemoveDuplicates.kt | 15 + .../tachiyomi/util/lang/RectFExtensions.kt | 4 +- .../util/preference/PreferenceExtensions.kt | 8 - .../util/system/ActivityExtensions.kt | 14 - .../util/system/AnimationExtensions.kt | 8 + .../util/system/ContextExtensions.kt | 139 +-------- .../util/system/DisplayExtensions.kt | 106 +++++++ .../util/system/NetworkExtensions.kt | 43 +++ .../util/system/NotificationExtensions.kt | 79 ++++-- .../util/system/WorkManagerExtensions.kt | 13 + .../tachiyomi/util/view/ViewExtensions.kt | 18 +- .../widget/TachiyomiTextInputEditText.kt | 6 +- .../widget/sheet/TabbedBottomSheetDialog.kt | 43 --- .../main/res/layout/compose_controller.xml | 4 - .../main/res/layout/reader_pager_settings.xml | 22 +- .../res/layout/reader_transition_view.xml | 50 ---- .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 +- .../main/res/mipmap-hdpi/ic_local_source.webp | Bin 1050 -> 882 bytes .../main/res/mipmap-mdpi/ic_local_source.webp | Bin 610 -> 674 bytes .../res/mipmap-xhdpi/ic_local_source.webp | Bin 1180 -> 1046 bytes .../res/mipmap-xxhdpi/ic_local_source.webp | Bin 2098 -> 1476 bytes .../res/mipmap-xxxhdpi/ic_local_source.webp | Bin 2546 -> 1774 bytes build.gradle.kts | 6 +- .../src/main/kotlin/LocalesConfigPlugin.kt | 15 +- .../UncaughtExceptionInterceptor.kt | 6 +- .../tachiyomi/util/system/DeviceUtil.kt | 20 +- .../tachiyomi/core/util/system/ImageUtil.kt | 18 ++ domain/build.gradle.kts | 2 +- .../domain/backup/service/PreferenceValues.kt | 3 + .../download/service/DownloadPreferences.kt | 2 + .../domain/items/service/MissingItems.kt | 61 ++++ .../library/service/LibraryPreferences.kt | 2 +- .../chapter/service/ChapterRecognitionTest.kt | 5 +- .../domain/items/service/MissingItemsTest.kt | 84 ++++++ .../domain/library/model/LibraryFlagsTest.kt | 43 +-- gradle.properties | 8 +- gradle/androidx.versions.toml | 12 +- gradle/compose.versions.toml | 8 +- gradle/kotlinx.versions.toml | 5 +- gradle/libs.versions.toml | 16 +- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 6 +- i18n/src/main/res/values-am/strings.xml | 1 - .../main/res/values-ar/strings-aniyomi.xml | 6 +- i18n/src/main/res/values-ar/strings.xml | 231 +++++++-------- i18n/src/main/res/values-b+es+419/strings.xml | 6 - i18n/src/main/res/values-be/strings.xml | 1 - i18n/src/main/res/values-bg/strings.xml | 6 - i18n/src/main/res/values-bn/strings.xml | 6 - .../main/res/values-ca/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-ca/strings.xml | 7 +- i18n/src/main/res/values-ceb/strings.xml | 5 +- .../main/res/values-cs/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-cs/strings.xml | 30 +- .../main/res/values-cv/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-cv/strings.xml | 126 ++++---- i18n/src/main/res/values-da/strings.xml | 1 - .../main/res/values-de/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-de/strings.xml | 21 +- i18n/src/main/res/values-el/strings.xml | 19 +- i18n/src/main/res/values-eo/strings.xml | 2 - i18n/src/main/res/values-es/strings.xml | 26 +- i18n/src/main/res/values-eu/strings.xml | 4 - i18n/src/main/res/values-fa/strings.xml | 5 +- i18n/src/main/res/values-fi/strings.xml | 6 - i18n/src/main/res/values-fil/strings.xml | 45 +-- .../main/res/values-fr/strings-aniyomi.xml | 12 +- i18n/src/main/res/values-fr/strings.xml | 54 ++-- i18n/src/main/res/values-gl/strings.xml | 35 ++- i18n/src/main/res/values-he/strings.xml | 3 - .../main/res/values-hi/strings-aniyomi.xml | 6 +- i18n/src/main/res/values-hi/strings.xml | 64 ++--- i18n/src/main/res/values-hr/strings.xml | 28 +- i18n/src/main/res/values-hu/strings.xml | 6 - i18n/src/main/res/values-in/strings.xml | 22 +- i18n/src/main/res/values-it/strings.xml | 20 +- i18n/src/main/res/values-ja/strings.xml | 18 +- i18n/src/main/res/values-jv/strings.xml | 1 - .../res/values-ka-rGE/strings-aniyomi.xml | 4 +- i18n/src/main/res/values-ka-rGE/strings.xml | 43 ++- i18n/src/main/res/values-kk/strings.xml | 6 - i18n/src/main/res/values-km/strings.xml | 1 - i18n/src/main/res/values-kn/strings.xml | 4 - i18n/src/main/res/values-ko/strings.xml | 20 +- i18n/src/main/res/values-lt/strings.xml | 6 - .../main/res/values-lv/strings-aniyomi.xml | 15 +- i18n/src/main/res/values-lv/strings.xml | 165 ++++++++--- i18n/src/main/res/values-mr/strings.xml | 1 - i18n/src/main/res/values-ms/strings.xml | 18 +- i18n/src/main/res/values-nb-rNO/strings.xml | 21 +- .../main/res/values-ne/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-ne/strings.xml | 58 ++-- i18n/src/main/res/values-nl/strings.xml | 7 - i18n/src/main/res/values-nn/strings.xml | 1 - i18n/src/main/res/values-pl/strings.xml | 6 - i18n/src/main/res/values-pt-rBR/strings.xml | 20 +- i18n/src/main/res/values-pt/strings.xml | 17 +- i18n/src/main/res/values-ro/strings.xml | 6 - i18n/src/main/res/values-ru/strings.xml | 21 +- i18n/src/main/res/values-sa/strings.xml | 4 - i18n/src/main/res/values-sah/strings.xml | 2 - i18n/src/main/res/values-sc/strings.xml | 19 +- i18n/src/main/res/values-sdh/strings.xml | 1 - i18n/src/main/res/values-sk/strings.xml | 6 - i18n/src/main/res/values-sq/strings.xml | 7 - i18n/src/main/res/values-sr/strings.xml | 7 - .../main/res/values-sv/strings-aniyomi.xml | 12 +- i18n/src/main/res/values-sv/strings.xml | 69 +++-- i18n/src/main/res/values-te/strings.xml | 1 - i18n/src/main/res/values-th/strings.xml | 20 +- i18n/src/main/res/values-tr/strings.xml | 27 +- i18n/src/main/res/values-uk/strings.xml | 23 +- i18n/src/main/res/values-uz/strings.xml | 1 - .../main/res/values-vi/strings-aniyomi.xml | 2 +- i18n/src/main/res/values-vi/strings.xml | 49 +++- i18n/src/main/res/values-zh-rCN/strings.xml | 24 +- i18n/src/main/res/values-zh-rTW/strings.xml | 28 +- i18n/src/main/res/values/strings.xml | 32 ++- .../core/components/AdaptiveSheet.kt | 35 +-- .../core/components/SettingsItemsPaddings.kt | 12 + .../core/components/VerticalFastScroller.kt | 33 ++- .../core/components/WheelPicker.kt | 268 +++++++++--------- .../presentation/core/screens/EmptyScreen.kt | 16 +- .../presentation/core/util/Modifier.kt | 64 +++++ .../presentation/core/util/Scrollbar.kt | 11 +- .../tachiyomi/animesource/AnimeSource.kt | 11 +- .../eu/kanade/tachiyomi/source/MangaSource.kt | 11 +- .../local/entries/anime/LocalAnimeSource.kt | 6 + .../local/entries/manga/LocalMangaSource.kt | 6 + 269 files changed, 3208 insertions(+), 2823 deletions(-) create mode 100644 app/src/main/java/eu/kanade/presentation/library/LibraryColumnsDialog.kt create mode 100644 app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt rename app/src/main/java/eu/kanade/presentation/more/settings/screen/{ => debug}/WorkerInfoScreen.kt (78%) rename app/src/main/java/eu/kanade/tachiyomi/data/backup/{BackupCreatorJob.kt => BackupCreateJob.kt} (77%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRemoveDuplicates.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/WorkManagerExtensions.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt delete mode 100644 app/src/main/res/layout/compose_controller.xml delete mode 100644 app/src/main/res/layout/reader_transition_view.xml create mode 100644 domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt create mode 100644 domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index b58c2fb3fb..077eb0251e 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -28,10 +28,10 @@ jobs: - name: Dependency Review uses: actions/dependency-review-action@v3 - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 distribution: adopt - name: Build app and run unit tests diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index ef72155589..2a0ca39096 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -23,10 +23,10 @@ jobs: - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - - name: Set up JDK 11 + - name: Set up JDK uses: actions/setup-java@v3 with: - java-version: 11 + java-version: 17 distribution: adopt - name: Build app and run unit tests diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml index 592e23b457..1bfe6239a2 100644 --- a/.github/workflows/issue_moderator.yml +++ b/.github/workflows/issue_moderator.yml @@ -11,9 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Moderate issues - uses: tachiyomiorg/issue-moderator-action@v1 + uses: tachiyomiorg/issue-moderator-action@v2 with: repo-token: ${{ secrets.GITHUB_TOKEN }} + duplicate-label: Duplicate + auto-close-rules: | [ { @@ -27,3 +29,4 @@ jobs: "message": "Requested information in the template was not filled out." } ] + auto-close-ignore-label: do-not-autoclose diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a64dfcb678..5bbf3fa2f6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,8 +23,8 @@ android { defaultConfig { applicationId = "xyz.jmir.tachiyomi.mi" - versionCode = 99 - versionName = "0.14.5" + versionCode = 102 + versionName = "0.14.6" buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") @@ -102,7 +102,7 @@ android { } } - packagingOptions { + packaging { resources.excludes.addAll(listOf( "META-INF/DEPENDENCIES", "LICENSE.txt", @@ -258,7 +258,7 @@ dependencies { implementation(libs.bundles.shizuku) // Tests - testImplementation(libs.junit) + testImplementation(libs.bundles.test) // For detecting memory leaks; see https://square.github.io/leakcanary/ // debugImplementation(libs.leakcanary.android) diff --git a/app/proguard-android-optimize.txt b/app/proguard-android-optimize.txt index 1a85da105a..9f53403165 100644 --- a/app/proguard-android-optimize.txt +++ b/app/proguard-android-optimize.txt @@ -1,4 +1,5 @@ -dontusemixedcaseclassnames +-ignorewarnings -verbose -keepattributes *Annotation* diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 123a76360f..343be8d652 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,8 @@ -dontobfuscate +-keep,allowoptimization class eu.kanade.** +-keep,allowoptimization class tachiyomi.** + # Keep common dependencies used in extensions -keep,allowoptimization class androidx.preference.** { public protected *; } -keep,allowoptimization class android.content.** { *; } diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml index 57a9a3396f..dcbbdf1218 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml index 57a9a3396f..dcbbdf1218 100644 --- a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ca6c4e6cc0..56a084673a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -281,10 +281,6 @@ android:name=".data.updater.AppUpdateService" android:exported="false" /> - - diff --git a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt index 07d5933ad0..5017631501 100644 --- a/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt +++ b/app/src/main/java/eu/kanade/core/util/CollectionUtils.kt @@ -12,10 +12,10 @@ fun List.insertSeparators( val newList = mutableListOf() for (i in -1..lastIndex) { val before = getOrNull(i) - before?.let { newList.add(it) } + before?.let(newList::add) val after = getOrNull(i + 1) val separator = generator.invoke(before, after) - separator?.let { newList.add(it) } + separator?.let(newList::add) } return newList } @@ -80,7 +80,7 @@ inline fun List.fastMapNotNull(transform: (T) -> R?): List { contract { callsInPlace(transform) } val destination = ArrayList() fastForEach { element -> - transform(element)?.let { destination.add(it) } + transform(element)?.let(destination::add) } return destination } diff --git a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt index 83f1eb358f..3cea7a5b97 100644 --- a/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/base/BasePreferences.kt @@ -3,6 +3,7 @@ package eu.kanade.domain.base import android.content.Context import android.content.pm.PackageManager import android.os.Build +import eu.kanade.tachiyomi.R import tachiyomi.core.preference.PreferenceStore class BasePreferences( @@ -22,4 +23,10 @@ class BasePreferences( fun acraEnabled() = preferenceStore.getBoolean("acra.enable", false) fun deviceHasPip() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + + enum class ExtensionInstaller(val titleResId: Int) { + LEGACY(R.string.ext_installer_legacy), + PACKAGEINSTALLER(R.string.ext_installer_packageinstaller), + SHIZUKU(R.string.ext_installer_shizuku), + } } diff --git a/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt index a981bd0ff9..03c63dd2bb 100644 --- a/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt +++ b/app/src/main/java/eu/kanade/domain/base/ExtensionInstallerPreference.kt @@ -1,7 +1,7 @@ package eu.kanade.domain.base import android.content.Context -import eu.kanade.tachiyomi.data.preference.PreferenceValues.ExtensionInstaller +import eu.kanade.domain.base.BasePreferences.ExtensionInstaller import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller import eu.kanade.tachiyomi.util.system.isShizukuInstalled import kotlinx.coroutines.CoroutineScope diff --git a/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt b/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt index f5e84a5ece..4b9ebdd354 100644 --- a/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt +++ b/app/src/main/java/eu/kanade/domain/entries/anime/interactor/UpdateAnime.kt @@ -1,12 +1,12 @@ package eu.kanade.domain.entries.anime.interactor import eu.kanade.domain.entries.anime.model.hasCustomCover -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.anime.model.AnimeUpdate import tachiyomi.domain.entries.anime.repository.AnimeRepository +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date diff --git a/app/src/main/java/eu/kanade/domain/entries/anime/model/Anime.kt b/app/src/main/java/eu/kanade/domain/entries/anime/model/Anime.kt index 8df0789f84..52f73c2699 100644 --- a/app/src/main/java/eu/kanade/domain/entries/anime/model/Anime.kt +++ b/app/src/main/java/eu/kanade/domain/entries/anime/model/Anime.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import tachiyomi.domain.entries.TriStateFilter import tachiyomi.domain.entries.anime.model.Anime -import tachiyomi.source.local.entries.anime.LocalAnimeSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -77,8 +76,6 @@ fun SAnime.toDomainAnime(sourceId: Long): Anime { ) } -fun Anime.isLocal(): Boolean = source == LocalAnimeSource.ID - fun Anime.hasCustomCover(coverCache: AnimeCoverCache = Injekt.get()): Boolean { return coverCache.getCustomCoverFile(id).exists() } diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt index d9b27ed74b..8d159cfe01 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/interactor/UpdateManga.kt @@ -1,12 +1,12 @@ package eu.kanade.domain.entries.manga.interactor import eu.kanade.domain.entries.manga.model.hasCustomCover -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.source.model.SManga import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.MangaUpdate import tachiyomi.domain.entries.manga.repository.MangaRepository +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date diff --git a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt index df638d71eb..92ace31224 100644 --- a/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/entries/manga/model/Manga.kt @@ -10,7 +10,6 @@ import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus import tachiyomi.domain.entries.TriStateFilter import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter -import tachiyomi.source.local.entries.manga.LocalMangaSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -89,8 +88,6 @@ fun SManga.toDomainManga(sourceId: Long): Manga { ) } -fun Manga.isLocal(): Boolean = source == LocalMangaSource.ID - fun Manga.hasCustomCover(coverCache: MangaCoverCache = Injekt.get()): Boolean { return coverCache.getCustomCoverFile(id).exists() } diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt index b7cdef7a79..42802ae709 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/interactor/SyncChaptersWithSource.kt @@ -7,7 +7,6 @@ import eu.kanade.domain.items.chapter.model.toSChapter import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.download.manga.MangaDownloadProvider import eu.kanade.tachiyomi.source.MangaSource -import eu.kanade.tachiyomi.source.manga.isLocal import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.online.HttpSource import tachiyomi.data.items.chapter.ChapterSanitizer @@ -20,6 +19,7 @@ import tachiyomi.domain.items.chapter.model.NoChaptersException import tachiyomi.domain.items.chapter.model.toChapterUpdate import tachiyomi.domain.items.chapter.repository.ChapterRepository import tachiyomi.domain.items.chapter.service.ChapterRecognition +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.lang.Long.max diff --git a/app/src/main/java/eu/kanade/domain/items/chapter/model/ChapterFilter.kt b/app/src/main/java/eu/kanade/domain/items/chapter/model/ChapterFilter.kt index 315fa24185..06b3b3422a 100644 --- a/app/src/main/java/eu/kanade/domain/items/chapter/model/ChapterFilter.kt +++ b/app/src/main/java/eu/kanade/domain/items/chapter/model/ChapterFilter.kt @@ -1,13 +1,13 @@ package eu.kanade.domain.items.chapter.model import eu.kanade.domain.entries.manga.model.downloadedFilter -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem import tachiyomi.domain.entries.applyFilter import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter import tachiyomi.domain.items.chapter.service.getChapterSort +import tachiyomi.source.local.entries.manga.isLocal /** * Applies the view filters to the list of chapters obtained from the database. diff --git a/app/src/main/java/eu/kanade/domain/items/episode/interactor/SyncEpisodesWithSource.kt b/app/src/main/java/eu/kanade/domain/items/episode/interactor/SyncEpisodesWithSource.kt index e415c7937f..9132080cf6 100644 --- a/app/src/main/java/eu/kanade/domain/items/episode/interactor/SyncEpisodesWithSource.kt +++ b/app/src/main/java/eu/kanade/domain/items/episode/interactor/SyncEpisodesWithSource.kt @@ -9,7 +9,6 @@ import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider -import eu.kanade.tachiyomi.source.anime.isLocal import tachiyomi.data.items.episode.EpisodeSanitizer import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId @@ -20,6 +19,7 @@ import tachiyomi.domain.items.episode.model.NoEpisodesException import tachiyomi.domain.items.episode.model.toEpisodeUpdate import tachiyomi.domain.items.episode.repository.EpisodeRepository import tachiyomi.domain.items.episode.service.EpisodeRecognition +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.lang.Long.max diff --git a/app/src/main/java/eu/kanade/domain/items/episode/model/EpisodeFilter.kt b/app/src/main/java/eu/kanade/domain/items/episode/model/EpisodeFilter.kt index 54ef7179c2..11d47e02fb 100644 --- a/app/src/main/java/eu/kanade/domain/items/episode/model/EpisodeFilter.kt +++ b/app/src/main/java/eu/kanade/domain/items/episode/model/EpisodeFilter.kt @@ -1,13 +1,13 @@ package eu.kanade.domain.items.episode.model import eu.kanade.domain.entries.anime.model.downloadedFilter -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.applyFilter import tachiyomi.domain.items.episode.model.Episode import tachiyomi.domain.items.episode.service.getEpisodeSort +import tachiyomi.source.local.entries.anime.isLocal /** * Applies the view filters to the list of episodes obtained from the database. diff --git a/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt index 4d977fcfef..3343643dc6 100644 --- a/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/anime/service/DelayedAnimeTrackingUpdateJob.kt @@ -7,11 +7,11 @@ import androidx.work.CoroutineWorker import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager import androidx.work.WorkerParameters import eu.kanade.domain.track.anime.model.toDbTrack import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.util.system.workManager import logcat.LogPriority import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat @@ -74,8 +74,7 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame .addTag(TAG) .build() - WorkManager.getInstance(context) - .enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request) + context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request) } } } diff --git a/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt b/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt index 242bd5b2c3..32fdcb8acf 100644 --- a/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt +++ b/app/src/main/java/eu/kanade/domain/track/manga/service/DelayedMangaTrackingUpdateJob.kt @@ -7,11 +7,11 @@ import androidx.work.CoroutineWorker import androidx.work.ExistingWorkPolicy import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.WorkManager import androidx.work.WorkerParameters import eu.kanade.domain.track.manga.model.toDbTrack import eu.kanade.domain.track.manga.store.DelayedMangaTrackingStore import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.util.system.workManager import logcat.LogPriority import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.system.logcat @@ -74,8 +74,7 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame .addTag(TAG) .build() - WorkManager.getInstance(context) - .enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request) + context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request) } } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt index 4d6283c729..fc5ebc82d5 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper import tachiyomi.domain.source.anime.model.AnimeSource import tachiyomi.domain.source.anime.model.Pin import tachiyomi.presentation.core.components.ScrollbarLazyColumn +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.screens.EmptyScreen @@ -143,7 +144,7 @@ private fun AnimeSourcePinButton( onClick: () -> Unit, ) { val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin - val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground + val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground.copy(alpha = SecondaryItemAlpha) val description = if (isPinned) R.string.action_unpin else R.string.action_pin IconButton(onClick = onClick) { Icon( diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt index 6bd23cda92..31dc7f7484 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/BrowseAnimeSourceScreen.kt @@ -78,6 +78,7 @@ fun BrowseAnimeSourceContent( if (animeList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) { EmptyScreen( + modifier = Modifier.padding(contentPadding), message = getErrorMessage(errorState), actions = if (source is LocalAnimeSource) { listOf( @@ -112,7 +113,9 @@ fun BrowseAnimeSourceContent( } if (animeList.itemCount == 0 && animeList.loadState.refresh is LoadState.Loading) { - LoadingScreen() + LoadingScreen( + modifier = Modifier.padding(contentPadding), + ) return } diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt index 426f0e909c..5b18bcd10b 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt @@ -29,7 +29,7 @@ fun GlobalAnimeSearchScreen( navigateUp: () -> Unit, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, - getAnime: @Composable (AnimeCatalogueSource, Anime) -> State, + getAnime: @Composable (Anime) -> State, onClickSource: (AnimeCatalogueSource) -> Unit, onClickItem: (Anime) -> Unit, onLongClickItem: (Anime) -> Unit, @@ -62,7 +62,7 @@ fun GlobalAnimeSearchScreen( private fun GlobalAnimeSearchContent( items: Map, contentPadding: PaddingValues, - getAnime: @Composable (AnimeCatalogueSource, Anime) -> State, + getAnime: @Composable (Anime) -> State, onClickSource: (AnimeCatalogueSource) -> Unit, onClickItem: (Anime) -> Unit, onLongClickItem: (Anime) -> Unit, @@ -96,7 +96,7 @@ private fun GlobalAnimeSearchContent( GlobalAnimeSearchCardRow( titles = result.result, - getAnime = { getAnime(source, it) }, + getAnime = getAnime, onClick = onClickItem, onLongClick = onLongClickItem, ) diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSearchScreen.kt index a6bcebc050..9829db2731 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSearchScreen.kt @@ -21,7 +21,7 @@ import tachiyomi.presentation.core.components.material.Scaffold fun MigrateAnimeSearchScreen( navigateUp: () -> Unit, state: MigrateAnimeSearchState, - getAnime: @Composable (AnimeCatalogueSource, Anime) -> State, + getAnime: @Composable (Anime) -> State, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, onClickSource: (AnimeCatalogueSource) -> Unit, @@ -58,7 +58,7 @@ fun MigrateAnimeSearchContent( sourceId: Long, items: Map, contentPadding: PaddingValues, - getAnime: @Composable (AnimeCatalogueSource, Anime) -> State, + getAnime: @Composable (Anime) -> State, onClickSource: (AnimeCatalogueSource) -> Unit, onClickItem: (Anime) -> Unit, onLongClickItem: (Anime) -> Unit, @@ -85,7 +85,7 @@ fun MigrateAnimeSearchContent( GlobalAnimeSearchCardRow( titles = result.result, - getAnime = { getAnime(source, it) }, + getAnime = getAnime, onClick = onClickItem, onLongClick = onLongClickItem, ) diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt index f9fd9b4672..f591ba98d4 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/BrowseMangaSourceScreen.kt @@ -78,6 +78,7 @@ fun BrowseSourceContent( if (mangaList.itemCount <= 0 && errorState != null && errorState is LoadState.Error) { EmptyScreen( + modifier = Modifier.padding(contentPadding), message = getErrorMessage(errorState), actions = if (source is LocalMangaSource) { listOf( @@ -112,7 +113,9 @@ fun BrowseSourceContent( } if (mangaList.itemCount == 0 && mangaList.loadState.refresh is LoadState.Loading) { - LoadingScreen() + LoadingScreen( + modifier = Modifier.padding(contentPadding), + ) return } diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt index 30308f6cea..701ff60886 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt @@ -29,7 +29,7 @@ fun GlobalMangaSearchScreen( navigateUp: () -> Unit, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, - getManga: @Composable (CatalogueSource, Manga) -> State, + getManga: @Composable (Manga) -> State, onClickSource: (CatalogueSource) -> Unit, onClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit, @@ -62,7 +62,7 @@ fun GlobalMangaSearchScreen( private fun GlobalSearchContent( items: Map, contentPadding: PaddingValues, - getManga: @Composable (CatalogueSource, Manga) -> State, + getManga: @Composable (Manga) -> State, onClickSource: (CatalogueSource) -> Unit, onClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit, @@ -96,7 +96,7 @@ private fun GlobalSearchContent( GlobalMangaSearchCardRow( titles = result.result, - getManga = { getManga(source, it) }, + getManga = getManga, onClick = onClickItem, onLongClick = onLongClickItem, ) diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt index 8c4b5276e2..8d97d67559 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.util.system.LocaleHelper import tachiyomi.domain.source.manga.model.Pin import tachiyomi.domain.source.manga.model.Source import tachiyomi.presentation.core.components.ScrollbarLazyColumn +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.topSmallPaddingValues import tachiyomi.presentation.core.screens.EmptyScreen @@ -143,7 +144,7 @@ private fun SourcePinButton( onClick: () -> Unit, ) { val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin - val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground + val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground.copy(alpha = SecondaryItemAlpha) val description = if (isPinned) R.string.action_unpin else R.string.action_pin IconButton(onClick = onClick) { Icon( diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSearchScreen.kt index 0aa7d3c2bd..12db7ad92e 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSearchScreen.kt @@ -21,7 +21,7 @@ import tachiyomi.presentation.core.components.material.Scaffold fun MigrateMangaSearchScreen( navigateUp: () -> Unit, state: MigrateMangaSearchState, - getManga: @Composable (CatalogueSource, Manga) -> State, + getManga: @Composable (Manga) -> State, onChangeSearchQuery: (String?) -> Unit, onSearch: (String) -> Unit, onClickSource: (CatalogueSource) -> Unit, @@ -58,7 +58,7 @@ fun MigrateMangaSearchContent( sourceId: Long, items: Map, contentPadding: PaddingValues, - getManga: @Composable (CatalogueSource, Manga) -> State, + getManga: @Composable (Manga) -> State, onClickSource: (CatalogueSource) -> Unit, onClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit, @@ -85,7 +85,7 @@ fun MigrateMangaSearchContent( GlobalMangaSearchCardRow( titles = result.result, - getManga = { getManga(source, it) }, + getManga = getManga, onClick = onClickItem, onLongClick = onLongClickItem, ) diff --git a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt index ff2a8dbbd5..0746f62c28 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt @@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.only import androidx.compose.runtime.Composable import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import cafe.adriel.voyager.core.lifecycle.DisposableEffectIgnoringConfiguration import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.ScreenTransition @@ -78,17 +80,25 @@ fun AdaptiveSheet( content: @Composable (PaddingValues) -> Unit, ) { val isTabletUi = isTabletUi() - AdaptiveSheetImpl( - isTabletUi = isTabletUi, - tonalElevation = tonalElevation, - enableSwipeDismiss = enableSwipeDismiss, + Dialog( onDismissRequest = onDismissRequest, + properties = DialogProperties( + usePlatformDefaultWidth = false, + decorFitsSystemWindows = false, + ), ) { - val contentPadding = if (isTabletUi) { - PaddingValues() - } else { - WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues() + AdaptiveSheetImpl( + isTabletUi = isTabletUi, + tonalElevation = tonalElevation, + enableSwipeDismiss = enableSwipeDismiss, + onDismissRequest = onDismissRequest, + ) { + val contentPadding = if (isTabletUi) { + PaddingValues() + } else { + WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues() + } + content(contentPadding) } - content(contentPadding) } } 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 7c8342559f..2aefa606a9 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key @@ -50,8 +49,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.Pill +import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide import tachiyomi.presentation.core.util.runOnEnterKeyPressed import tachiyomi.presentation.core.util.secondaryItemAlpha +import tachiyomi.presentation.core.util.showSoftKeyboard const val SEARCH_DEBOUNCE_MILLIS = 250L @@ -259,7 +260,6 @@ fun SearchToolbar( actionMode: Boolean = false, ) { val focusRequester = remember { FocusRequester() } - var searchClickCount by remember { mutableStateOf(0) } AppBar( titleContent = { @@ -281,7 +281,9 @@ fun SearchToolbar( modifier = Modifier .fillMaxWidth() .focusRequester(focusRequester) - .runOnEnterKeyPressed(action = searchAndClearFocus), + .runOnEnterKeyPressed(action = searchAndClearFocus) + .showSoftKeyboard(remember { searchQuery.isEmpty() }) + .clearFocusOnSoftKeyboardHide(), textStyle = MaterialTheme.typography.titleMedium.copy( color = MaterialTheme.colorScheme.onBackground, fontWeight = FontWeight.Normal, @@ -320,10 +322,7 @@ fun SearchToolbar( navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch, actions = { key("search") { - val onClick = { - searchClickCount++ - onChangeSearchQuery("") - } + val onClick = { onChangeSearchQuery("") } if (!searchEnabled) { // Don't show search action @@ -332,7 +331,12 @@ fun SearchToolbar( Icon(Icons.Outlined.Search, contentDescription = stringResource(R.string.action_search)) } } else if (searchQuery.isNotEmpty()) { - IconButton(onClick) { + IconButton( + onClick = { + onClick() + focusRequester.requestFocus() + }, + ) { Icon(Icons.Outlined.Close, contentDescription = stringResource(R.string.action_reset)) } } @@ -344,15 +348,6 @@ fun SearchToolbar( scrollBehavior = scrollBehavior, onCancelActionMode = cancelAction, ) - LaunchedEffect(searchClickCount) { - if (searchQuery == null) return@LaunchedEffect - if (searchClickCount == 0 && searchQuery.isNotEmpty()) return@LaunchedEffect - try { - focusRequester.requestFocus() - } catch (_: Throwable) { - // TextField is gone - } - } } sealed interface AppBar { diff --git a/app/src/main/java/eu/kanade/presentation/entries/ItemHeader.kt b/app/src/main/java/eu/kanade/presentation/entries/ItemHeader.kt index 6e03286240..ebbaecec36 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/ItemHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/ItemHeader.kt @@ -1,27 +1,30 @@ package eu.kanade.presentation.entries import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha @Composable fun ItemHeader( enabled: Boolean, itemCount: Int?, + missingItemsCount: Int, onClick: () -> Unit, isManga: Boolean, ) { - Row( + Column( modifier = Modifier .fillMaxWidth() .clickable( @@ -29,7 +32,7 @@ fun ItemHeader( onClick = onClick, ) .padding(horizontal = 16.dp, vertical = 4.dp), - verticalAlignment = Alignment.CenterVertically, + verticalArrangement = Arrangement.spacedBy(4.dp), ) { Text( text = if (itemCount == null) { @@ -40,8 +43,24 @@ fun ItemHeader( pluralStringResource(id = pluralCount, count = itemCount, itemCount) }, style = MaterialTheme.typography.titleMedium, - modifier = Modifier.weight(1f), color = MaterialTheme.colorScheme.onBackground, ) + + MissingItemsWarning(missingItemsCount) + } +} + +@Composable +private fun MissingItemsWarning(count: Int) { + if (count == 0) { + return } + + Text( + text = pluralStringResource(id = R.plurals.missing_items, count = count, count), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha), + ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index bba77206fd..a79bfa7069 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -73,6 +73,7 @@ import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.coroutines.delay import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode +import tachiyomi.domain.items.service.missingItemsCount import tachiyomi.domain.source.anime.model.StubAnimeSource import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.TwoPanelBox @@ -415,6 +416,7 @@ private fun AnimeScreenSmallImpl( ItemHeader( enabled = episodes.fastAll { !it.selected }, itemCount = episodes.size, + missingItemsCount = episodes.map { it.episode.episodeNumber }.missingItemsCount(), onClick = onFilterClicked, isManga = false, ) @@ -658,6 +660,7 @@ fun AnimeScreenLargeImpl( ItemHeader( enabled = episodes.fastAll { !it.selected }, itemCount = episodes.size, + missingItemsCount = episodes.map { it.episode.episodeNumber }.missingItemsCount(), onClick = onFilterButtonClicked, isManga = false, ) diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt index 3c57de0128..fa4e9d73b4 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt @@ -68,6 +68,7 @@ import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.system.copyToClipboard import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.service.missingItemsCount import tachiyomi.domain.source.manga.model.StubMangaSource import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.TwoPanelBox @@ -397,6 +398,7 @@ private fun MangaScreenSmallImpl( ItemHeader( enabled = chapters.fastAll { !it.selected }, itemCount = chapters.size, + missingItemsCount = chapters.map { it.chapter.chapterNumber }.missingItemsCount(), onClick = onFilterClicked, isManga = true, ) @@ -611,6 +613,7 @@ fun MangaScreenLargeImpl( ItemHeader( enabled = chapters.fastAll { !it.selected }, itemCount = chapters.size, + missingItemsCount = chapters.map { it.chapter.chapterNumber }.missingItemsCount(), onClick = onFilterButtonClicked, isManga = true, ) diff --git a/app/src/main/java/eu/kanade/presentation/library/LibraryColumnsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibraryColumnsDialog.kt new file mode 100644 index 0000000000..f3ad0827ef --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/library/LibraryColumnsDialog.kt @@ -0,0 +1,126 @@ +package eu.kanade.presentation.library + +import androidx.compose.foundation.layout.BoxWithConstraints +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.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R +import tachiyomi.presentation.core.components.WheelPickerDefaults +import tachiyomi.presentation.core.components.WheelTextPicker + +@Composable +fun LibraryColumnsDialog( + initialPortrait: Int, + initialLandscape: Int, + onDismissRequest: () -> Unit, + onValueChanged: (portrait: Int, landscape: Int) -> Unit, +) { + var portraitValue by rememberSaveable { mutableStateOf(initialPortrait) } + var landscapeValue by rememberSaveable { mutableStateOf(initialLandscape) } + + AlertDialog( + onDismissRequest = onDismissRequest, + title = { Text(text = stringResource(R.string.pref_library_columns)) }, + text = { + Column { + Row { + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.portrait), + textAlign = TextAlign.Center, + maxLines = 1, + style = MaterialTheme.typography.labelMedium, + ) + Text( + modifier = Modifier.weight(1f), + text = stringResource(R.string.landscape), + textAlign = TextAlign.Center, + maxLines = 1, + style = MaterialTheme.typography.labelMedium, + ) + } + LibraryColumnsPicker( + modifier = Modifier.fillMaxWidth(), + portraitValue = portraitValue, + onPortraitChange = { portraitValue = it }, + landscapeValue = landscapeValue, + onLandscapeChange = { landscapeValue = it }, + ) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(R.string.action_cancel)) + } + }, + confirmButton = { + TextButton( + enabled = portraitValue != initialPortrait || landscapeValue != initialLandscape, + onClick = { onValueChanged(portraitValue, landscapeValue) }, + ) { + Text(text = stringResource(android.R.string.ok)) + } + }, + ) +} + +@Composable +private fun LibraryColumnsPicker( + modifier: Modifier = Modifier, + portraitValue: Int, + onPortraitChange: (Int) -> Unit, + landscapeValue: Int, + onLandscapeChange: (Int) -> Unit, +) { + BoxWithConstraints( + modifier = modifier, + contentAlignment = Alignment.Center, + ) { + WheelPickerDefaults.Background(size = DpSize(maxWidth, 128.dp)) + + val size = DpSize(width = maxWidth / 2, height = 128.dp) + Row { + val columns = (0..10).map { getColumnValue(value = it) } + WheelTextPicker( + startIndex = portraitValue, + items = columns, + size = size, + onSelectionChanged = onPortraitChange, + backgroundContent = null, + ) + WheelTextPicker( + startIndex = landscapeValue, + items = columns, + size = size, + onSelectionChanged = onLandscapeChange, + backgroundContent = null, + ) + } + } +} + +@Composable +@ReadOnlyComposable +private fun getColumnValue(value: Int): String { + return if (value == 0) { + stringResource(R.string.label_default) + } else { + value.toString() + } +} diff --git a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt index 4c075dcffe..91f19d3691 100644 --- a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt @@ -7,11 +7,15 @@ 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 +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings import eu.kanade.presentation.components.TriStateItem +import eu.kanade.presentation.library.LibraryColumnsDialog import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.library.anime.AnimeLibrarySettingsScreenModel @@ -22,6 +26,7 @@ import tachiyomi.domain.library.anime.model.sort import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.display import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.presentation.core.components.BasicItem import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.RadioItem @@ -171,6 +176,23 @@ private fun ColumnScope.DisplayPage( category: Category, screenModel: AnimeLibrarySettingsScreenModel, ) { + val portraitColumns by screenModel.libraryPreferences.animePortraitColumns().collectAsState() + val landscapeColumns by screenModel.libraryPreferences.animeLandscapeColumns().collectAsState() + + var showColumnsDialog by rememberSaveable { mutableStateOf(false) } + if (showColumnsDialog) { + LibraryColumnsDialog( + initialPortrait = portraitColumns, + initialLandscape = landscapeColumns, + onDismissRequest = { showColumnsDialog = false }, + onValueChanged = { portrait, landscape -> + screenModel.libraryPreferences.animePortraitColumns().set(portrait) + screenModel.libraryPreferences.animeLandscapeColumns().set(landscape) + showColumnsDialog = false + }, + ) + } + HeadingItem(R.string.action_display_mode) listOf( R.string.action_display_grid to LibraryDisplayMode.CompactGrid, @@ -185,7 +207,14 @@ private fun ColumnScope.DisplayPage( ) } - HeadingItem(R.string.complications_header) + if (category.display != LibraryDisplayMode.List) { + BasicItem( + label = stringResource(R.string.pref_library_columns), + onClick = { showColumnsDialog = true }, + ) + } + + HeadingItem(R.string.overlay_header) val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState() CheckboxItem( label = stringResource(R.string.action_display_download_badge_anime), diff --git a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt index ba911a5e5b..9b9843c6e0 100644 --- a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt @@ -7,11 +7,15 @@ 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 +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import eu.kanade.presentation.components.TabbedDialog import eu.kanade.presentation.components.TabbedDialogPaddings import eu.kanade.presentation.components.TriStateItem +import eu.kanade.presentation.library.LibraryColumnsDialog import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.library.manga.MangaLibrarySettingsScreenModel @@ -22,6 +26,7 @@ import tachiyomi.domain.library.manga.model.sort import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.display import tachiyomi.domain.library.service.LibraryPreferences +import tachiyomi.presentation.core.components.BasicItem import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.HeadingItem import tachiyomi.presentation.core.components.RadioItem @@ -170,6 +175,23 @@ private fun ColumnScope.DisplayPage( category: Category, screenModel: MangaLibrarySettingsScreenModel, ) { + val portraitColumns by screenModel.libraryPreferences.mangaPortraitColumns().collectAsState() + val landscapeColumns by screenModel.libraryPreferences.mangaLandscapeColumns().collectAsState() + + var showColumnsDialog by rememberSaveable { mutableStateOf(false) } + if (showColumnsDialog) { + LibraryColumnsDialog( + initialPortrait = portraitColumns, + initialLandscape = landscapeColumns, + onDismissRequest = { showColumnsDialog = false }, + onValueChanged = { portrait, landscape -> + screenModel.libraryPreferences.mangaPortraitColumns().set(portrait) + screenModel.libraryPreferences.mangaLandscapeColumns().set(landscape) + showColumnsDialog = false + }, + ) + } + HeadingItem(R.string.action_display_mode) listOf( R.string.action_display_grid to LibraryDisplayMode.CompactGrid, @@ -184,7 +206,14 @@ private fun ColumnScope.DisplayPage( ) } - HeadingItem(R.string.complications_header) + if (category.display != LibraryDisplayMode.List) { + BasicItem( + label = stringResource(R.string.pref_library_columns), + onClick = { showColumnsDialog = true }, + ) + } + + HeadingItem(R.string.overlay_header) val downloadBadge by screenModel.libraryPreferences.downloadBadge().collectAsState() CheckboxItem( label = stringResource(R.string.action_display_download_badge), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt index ae1f6f5a81..f6f62eca9d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/AboutScreen.kt @@ -227,7 +227,7 @@ object AboutScreen : Screen() { } } - private fun getFormattedBuildTime(): String { + internal fun getFormattedBuildTime(): String { return try { val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US) inputDf.timeZone = TimeZone.getTimeZone("UTC") 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 4c4bc44fb6..12109eef70 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 @@ -26,6 +26,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.base.BasePreferences import eu.kanade.presentation.more.settings.Preference +import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -33,7 +34,6 @@ import eu.kanade.tachiyomi.data.cache.EpisodeCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences @@ -76,6 +76,8 @@ object SettingsAdvancedScreen : SearchableSettings { override fun getPreferences(): List { val scope = rememberCoroutineScope() val context = LocalContext.current + val navigator = LocalNavigator.currentOrThrow + val basePreferences = remember { Injekt.get() } val networkPreferences = remember { Injekt.get() } @@ -104,6 +106,10 @@ object SettingsAdvancedScreen : SearchableSettings { true }, ), + Preference.PreferenceItem.TextPreference( + title = stringResource(R.string.pref_debug_info), + onClick = { navigator.push(DebugInfoScreen) }, + ), getBackgroundActivityGroup(), getDataGroup(), getNetworkGroup(networkPreferences = networkPreferences), @@ -116,7 +122,6 @@ object SettingsAdvancedScreen : SearchableSettings { private fun getBackgroundActivityGroup(): Preference.PreferenceGroup { val context = LocalContext.current val uriHandler = LocalUriHandler.current - val navigator = LocalNavigator.currentOrThrow return Preference.PreferenceGroup( title = stringResource(R.string.label_background_activity), @@ -148,10 +153,6 @@ object SettingsAdvancedScreen : SearchableSettings { subtitle = stringResource(R.string.about_dont_kill_my_app), onClick = { uriHandler.openUri("https://dontkillmyapp.com/") }, ), - Preference.PreferenceItem.TextPreference( - title = stringResource(R.string.pref_worker_info), - onClick = { navigator.push(WorkerInfoScreen) }, - ), ), ) } @@ -386,7 +387,7 @@ object SettingsAdvancedScreen : SearchableSettings { entries = extensionInstallerPref.entries .associateWith { stringResource(it.titleResId) }, onValueChanged = { - if (it == PreferenceValues.ExtensionInstaller.SHIZUKU && + if (it == BasePreferences.ExtensionInstaller.SHIZUKU && !context.isShizukuInstalled ) { shizukuMissing = true diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt index 0e5dc4d0f5..0e2f941461 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt @@ -41,23 +41,23 @@ import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.backup.BackupConst -import eu.kanade.tachiyomi.data.backup.BackupCreatorJob +import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.backup.BackupFileValidator -import eu.kanade.tachiyomi.data.backup.BackupRestoreService +import eu.kanade.tachiyomi.data.backup.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.preference.FLAG_CATEGORIES -import eu.kanade.tachiyomi.data.preference.FLAG_CHAPTERS -import eu.kanade.tachiyomi.data.preference.FLAG_EXTENSIONS -import eu.kanade.tachiyomi.data.preference.FLAG_EXT_SETTINGS -import eu.kanade.tachiyomi.data.preference.FLAG_HISTORY -import eu.kanade.tachiyomi.data.preference.FLAG_SETTINGS -import eu.kanade.tachiyomi.data.preference.FLAG_TRACK import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.launch import tachiyomi.domain.backup.service.BackupPreferences +import tachiyomi.domain.backup.service.FLAG_CATEGORIES +import tachiyomi.domain.backup.service.FLAG_CHAPTERS +import tachiyomi.domain.backup.service.FLAG_EXTENSIONS +import tachiyomi.domain.backup.service.FLAG_EXT_SETTINGS +import tachiyomi.domain.backup.service.FLAG_HISTORY +import tachiyomi.domain.backup.service.FLAG_SETTINGS +import tachiyomi.domain.backup.service.FLAG_TRACK import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.util.isScrolledToEnd @@ -100,7 +100,7 @@ object SettingsBackupScreen : SearchableSettings { Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION, ) - BackupCreatorJob.startNow(context, it, flag) + BackupCreateJob.startNow(context, it, flag) } flag = 0 } @@ -126,7 +126,7 @@ object SettingsBackupScreen : SearchableSettings { subtitle = stringResource(R.string.pref_create_backup_summ), onClick = { scope.launch { - if (!BackupCreatorJob.isManualJobRunning(context)) { + if (!BackupCreateJob.isManualJobRunning(context)) { if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG) } @@ -288,7 +288,7 @@ object SettingsBackupScreen : SearchableSettings { confirmButton = { TextButton( onClick = { - BackupRestoreService.start(context, err.uri) + BackupRestoreJob.start(context, err.uri) onDismissRequest() }, ) { @@ -318,7 +318,7 @@ object SettingsBackupScreen : SearchableSettings { } if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) { - BackupRestoreService.start(context, it) + BackupRestoreJob.start(context, it) return@rememberLauncherForActivityResult } @@ -330,7 +330,7 @@ object SettingsBackupScreen : SearchableSettings { title = stringResource(R.string.pref_restore_backup), subtitle = stringResource(R.string.pref_restore_backup_summ), onClick = { - if (!BackupRestoreService.isRunning(context)) { + if (!BackupRestoreJob.isRunning(context)) { if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) { context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG) } @@ -381,7 +381,7 @@ object SettingsBackupScreen : SearchableSettings { 168 to stringResource(R.string.update_weekly), ), onValueChanged = { - BackupCreatorJob.setupTask(context, it) + BackupCreateJob.setupTask(context, it) true }, ), 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 bb19ebb98f..4f905a5506 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 @@ -61,6 +61,11 @@ object SettingsDownloadScreen : SearchableSettings { pref = downloadPreferences.saveChaptersAsCBZ(), title = stringResource(R.string.save_chapter_as_cbz), ), + Preference.PreferenceItem.SwitchPreference( + pref = downloadPreferences.splitTallImages(), + title = stringResource(R.string.split_tall_images), + subtitle = stringResource(R.string.split_tall_images_summary), + ), Preference.PreferenceItem.ListPreference( pref = downloadPreferences.numberOfDownloads(), title = stringResource(R.string.pref_download_slots), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index c79487d6cf..1638793a44 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 @@ -1,14 +1,6 @@ package eu.kanade.presentation.more.settings.screen import androidx.annotation.StringRes -import androidx.compose.foundation.layout.BoxWithConstraints -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.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.collectAsState @@ -18,14 +10,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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.platform.LocalContext import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastMap import androidx.core.content.ContextCompat import cafe.adriel.voyager.navigator.LocalNavigator @@ -53,8 +40,6 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_HAS_UNVIEWED import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED -import tachiyomi.presentation.core.components.WheelPicker -import tachiyomi.presentation.core.components.WheelPickerDefaults import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -74,69 +59,11 @@ object SettingsLibraryScreen : SearchableSettings { val libraryPreferences = remember { Injekt.get() } return mutableListOf( - getDisplayGroup(libraryPreferences), getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, allAnimeCategories, libraryPreferences), getGlobalUpdateGroup(allCategories, allAnimeCategories, libraryPreferences), ) } - @Composable - private fun getDisplayGroup(libraryPreferences: LibraryPreferences): Preference.PreferenceGroup { - val scope = rememberCoroutineScope() - - val animePortraitColumns by libraryPreferences.animePortraitColumns().stateIn(scope).collectAsState() - val mangaPortraitColumns by libraryPreferences.mangaPortraitColumns().stateIn(scope).collectAsState() - val animeLandscapeColumns by libraryPreferences.animeLandscapeColumns().stateIn(scope).collectAsState() - val mangaLandscapeColumns by libraryPreferences.mangaLandscapeColumns().stateIn(scope).collectAsState() - - var showAnimeDialog by rememberSaveable { mutableStateOf(false) } - var showDialog by rememberSaveable { mutableStateOf(false) } - - if (showAnimeDialog) { - LibraryColumnsDialog( - initialPortrait = animePortraitColumns, - initialLandscape = animeLandscapeColumns, - onDismissRequest = { showAnimeDialog = false }, - onValueChanged = { portrait, landscape -> - libraryPreferences.animePortraitColumns().set(portrait) - libraryPreferences.animeLandscapeColumns().set(landscape) - showAnimeDialog = false - }, - ) - } - - if (showDialog) { - LibraryColumnsDialog( - initialPortrait = mangaPortraitColumns, - initialLandscape = mangaLandscapeColumns, - onDismissRequest = { showDialog = false }, - onValueChanged = { portrait, landscape -> - libraryPreferences.mangaPortraitColumns().set(portrait) - libraryPreferences.mangaLandscapeColumns().set(landscape) - showDialog = false - }, - ) - } - - return Preference.PreferenceGroup( - title = stringResource(R.string.pref_category_display), - preferenceItems = listOf( - Preference.PreferenceItem.TextPreference( - title = stringResource(R.string.pref_library_anime_columns), - subtitle = "${stringResource(R.string.portrait)}: ${getColumnValue(animePortraitColumns)}, " + - "${stringResource(R.string.landscape)}: ${getColumnValue(animeLandscapeColumns)}", - onClick = { showAnimeDialog = true }, - ), - Preference.PreferenceItem.TextPreference( - title = stringResource(R.string.pref_library_manga_columns), - subtitle = "${stringResource(R.string.portrait)}: ${getColumnValue(mangaPortraitColumns)}, " + - "${stringResource(R.string.landscape)}: ${getColumnValue(mangaLandscapeColumns)}", - onClick = { showDialog = true }, - ), - ), - ) - } - @Composable private fun getCategoriesGroup( navigator: Navigator, @@ -349,108 +276,4 @@ object SettingsLibraryScreen : SearchableSettings { ), ) } - - @Composable - private fun LibraryColumnsDialog( - initialPortrait: Int, - initialLandscape: Int, - onDismissRequest: () -> Unit, - onValueChanged: (portrait: Int, landscape: Int) -> Unit, - ) { - var portraitValue by rememberSaveable { mutableStateOf(initialPortrait) } - var landscapeValue by rememberSaveable { mutableStateOf(initialLandscape) } - - AlertDialog( - onDismissRequest = onDismissRequest, - title = { Text(text = stringResource(R.string.pref_library_columns)) }, - text = { - Column { - Row { - Text( - modifier = Modifier.weight(1f), - text = stringResource(R.string.portrait), - textAlign = TextAlign.Center, - maxLines = 1, - style = MaterialTheme.typography.labelMedium, - ) - Text( - modifier = Modifier.weight(1f), - text = stringResource(R.string.landscape), - textAlign = TextAlign.Center, - maxLines = 1, - style = MaterialTheme.typography.labelMedium, - ) - } - LibraryColumnsPicker( - modifier = Modifier.fillMaxWidth(), - portraitValue = portraitValue, - onPortraitChange = { portraitValue = it }, - landscapeValue = landscapeValue, - onLandscapeChange = { landscapeValue = it }, - ) - } - }, - dismissButton = { - TextButton(onClick = onDismissRequest) { - Text(text = stringResource(R.string.action_cancel)) - } - }, - confirmButton = { - TextButton( - enabled = portraitValue != initialPortrait || landscapeValue != initialLandscape, - onClick = { onValueChanged(portraitValue, landscapeValue) }, - ) { - Text(text = stringResource(android.R.string.ok)) - } - }, - ) - } - - @Composable - private fun LibraryColumnsPicker( - modifier: Modifier = Modifier, - portraitValue: Int, - onPortraitChange: (Int) -> Unit, - landscapeValue: Int, - onLandscapeChange: (Int) -> Unit, - ) { - BoxWithConstraints( - modifier = modifier, - contentAlignment = Alignment.Center, - ) { - WheelPickerDefaults.Background(size = DpSize(maxWidth, maxHeight)) - - val size = DpSize(width = maxWidth / 2, height = 128.dp) - Row { - WheelPicker( - size = size, - count = 11, - startIndex = portraitValue, - onSelectionChanged = onPortraitChange, - backgroundContent = null, - ) { index -> - WheelPickerDefaults.Item(text = getColumnValue(value = index)) - } - WheelPicker( - size = size, - count = 11, - startIndex = landscapeValue, - onSelectionChanged = onLandscapeChange, - backgroundContent = null, - ) { index -> - WheelPickerDefaults.Item(text = getColumnValue(value = index)) - } - } - } - } - - @Composable - @ReadOnlyComposable - private fun getColumnValue(value: Int): String { - return if (value == 0) { - stringResource(R.string.label_default) - } else { - value.toString() - } - } } 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 cb5409c658..daa7400001 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 @@ -357,7 +357,7 @@ object SettingsPlayerScreen : SearchableSettings { content = { WheelTextPicker( modifier = Modifier.align(Alignment.Center), - texts = remember { 1..255 }.map { + items = remember { 1..255 }.map { stringResource( R.string.seconds_short, it, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 033de10587..b49f0dcd7e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -12,8 +12,6 @@ import androidx.compose.ui.res.stringResource import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.util.collectAsState import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceValues.ReaderHideThreshold -import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType @@ -156,10 +154,12 @@ object SettingsReaderScreen : SearchableSettings { val navModePref = readerPreferences.navigationModePager() val imageScaleTypePref = readerPreferences.imageScaleType() val dualPageSplitPref = readerPreferences.dualPageSplitPaged() + val rotateToFitPref = readerPreferences.dualPageRotateToFit() val navMode by navModePref.collectAsState() val imageScaleType by imageScaleTypePref.collectAsState() val dualPageSplit by dualPageSplitPref.collectAsState() + val rotateToFit by rotateToFitPref.collectAsState() return Preference.PreferenceGroup( title = stringResource(R.string.pager_viewer), @@ -175,10 +175,10 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPreferences.pagerNavInverted(), title = stringResource(R.string.pref_read_with_tapping_inverted), entries = mapOf( - TappingInvertMode.NONE to stringResource(R.string.none), - TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal), - TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical), - TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both), + ReaderPreferences.TappingInvertMode.NONE to stringResource(R.string.none), + ReaderPreferences.TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal), + ReaderPreferences.TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical), + ReaderPreferences.TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both), ), enabled = navMode != 5, ), @@ -222,6 +222,10 @@ object SettingsReaderScreen : SearchableSettings { Preference.PreferenceItem.SwitchPreference( pref = dualPageSplitPref, title = stringResource(R.string.pref_dual_page_split), + onValueChanged = { + rotateToFitPref.set(false) + true + }, ), Preference.PreferenceItem.SwitchPreference( pref = readerPreferences.dualPageInvertPaged(), @@ -229,6 +233,19 @@ object SettingsReaderScreen : SearchableSettings { subtitle = stringResource(R.string.pref_dual_page_invert_summary), enabled = dualPageSplit, ), + Preference.PreferenceItem.SwitchPreference( + pref = rotateToFitPref, + title = stringResource(R.string.pref_page_rotate), + onValueChanged = { + dualPageSplitPref.set(false) + true + }, + ), + Preference.PreferenceItem.SwitchPreference( + pref = readerPreferences.dualPageRotateToFitInvert(), + title = stringResource(R.string.pref_page_rotate_invert), + enabled = rotateToFit, + ), ), ) } @@ -255,10 +272,10 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPreferences.webtoonNavInverted(), title = stringResource(R.string.pref_read_with_tapping_inverted), entries = mapOf( - TappingInvertMode.NONE to stringResource(R.string.none), - TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal), - TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical), - TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both), + ReaderPreferences.TappingInvertMode.NONE to stringResource(R.string.none), + ReaderPreferences.TappingInvertMode.HORIZONTAL to stringResource(R.string.tapping_inverted_horizontal), + ReaderPreferences.TappingInvertMode.VERTICAL to stringResource(R.string.tapping_inverted_vertical), + ReaderPreferences.TappingInvertMode.BOTH to stringResource(R.string.tapping_inverted_both), ), enabled = navMode != 5, ), @@ -278,10 +295,10 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPreferences.readerHideThreshold(), title = stringResource(R.string.pref_hide_threshold), entries = mapOf( - ReaderHideThreshold.HIGHEST to stringResource(R.string.pref_highest), - ReaderHideThreshold.HIGH to stringResource(R.string.pref_high), - ReaderHideThreshold.LOW to stringResource(R.string.pref_low), - ReaderHideThreshold.LOWEST to stringResource(R.string.pref_lowest), + ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(R.string.pref_highest), + ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(R.string.pref_high), + ReaderPreferences.ReaderHideThreshold.LOW to stringResource(R.string.pref_low), + ReaderPreferences.ReaderHideThreshold.LOWEST to stringResource(R.string.pref_lowest), ), ), Preference.PreferenceItem.SwitchPreference( diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt new file mode 100644 index 0000000000..5128cf5e24 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt @@ -0,0 +1,132 @@ +package eu.kanade.presentation.more.settings.screen.debug + +import android.os.Build +import android.webkit.WebView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.platform.LocalContext +import androidx.profileinstaller.ProfileVerifier +import cafe.adriel.voyager.navigator.LocalNavigator +import cafe.adriel.voyager.navigator.currentOrThrow +import eu.kanade.presentation.more.settings.Preference +import eu.kanade.presentation.more.settings.PreferenceScaffold +import eu.kanade.presentation.more.settings.screen.AboutScreen +import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.system.DeviceUtil +import kotlinx.coroutines.guava.await + +object DebugInfoScreen : Screen() { + @Composable + override fun Content() { + val navigator = LocalNavigator.currentOrThrow + PreferenceScaffold( + titleRes = R.string.pref_debug_info, + onBackPressed = navigator::pop, + itemsProvider = { + listOf( + Preference.PreferenceItem.TextPreference( + title = WorkerInfoScreen.title, + onClick = { navigator.push(WorkerInfoScreen) }, + ), + getAppInfoGroup(), + getDeviceInfoGroup(), + ) + }, + ) + } + + @Composable + private fun getAppInfoGroup(): Preference.PreferenceGroup { + return Preference.PreferenceGroup( + title = "App info", + preferenceItems = listOf( + Preference.PreferenceItem.TextPreference( + title = "Version", + subtitle = AboutScreen.getVersionName(false), + ), + Preference.PreferenceItem.TextPreference( + title = "Build time", + subtitle = AboutScreen.getFormattedBuildTime(), + ), + getProfileVerifierPreference(), + Preference.PreferenceItem.TextPreference( + title = "WebView version", + subtitle = getWebViewVersion(), + ), + ), + ) + } + + @Composable + @ReadOnlyComposable + private fun getWebViewVersion(): String { + val webView = WebView.getCurrentWebViewPackage() ?: return "how did you get here?" + val pm = LocalContext.current.packageManager + val label = webView.applicationInfo.loadLabel(pm) + val version = webView.versionName + return "$label $version" + } + + @Composable + private fun getProfileVerifierPreference(): Preference.PreferenceItem.TextPreference { + val status by produceState(initialValue = "-") { + val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode + value = when (result) { + ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed" + ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled" + ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> "Compiled non-matching" + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ, + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE, + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST, + -> "Error $result" + ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported" + ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation" + else -> "Unknown code $result" + } + } + return Preference.PreferenceItem.TextPreference( + title = "Profile compilation status", + subtitle = status, + ) + } + + private fun getDeviceInfoGroup(): Preference.PreferenceGroup { + val items = mutableListOf( + Preference.PreferenceItem.TextPreference( + title = "Model", + subtitle = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})", + ), + ) + if (DeviceUtil.oneUiVersion != null) { + items += Preference.PreferenceItem.TextPreference( + title = "OneUI version", + subtitle = "${DeviceUtil.oneUiVersion}", + ) + } else if (DeviceUtil.miuiMajorVersion != null) { + items += Preference.PreferenceItem.TextPreference( + title = "MIUI version", + subtitle = "${DeviceUtil.miuiMajorVersion}", + ) + } + + val androidVersion = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Build.VERSION.RELEASE_OR_PREVIEW_DISPLAY + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Build.VERSION.RELEASE_OR_CODENAME + } else { + Build.VERSION.RELEASE + } + items += Preference.PreferenceItem.TextPreference( + title = "Android version", + subtitle = "$androidVersion (${Build.DISPLAY})", + ) + + return Preference.PreferenceGroup( + title = "Device info", + preferenceItems = items, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt similarity index 78% rename from app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt rename to app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt index b09ee8d072..4decc0e1dc 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/WorkerInfoScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/WorkerInfoScreen.kt @@ -1,4 +1,4 @@ -package eu.kanade.presentation.more.settings.screen +package eu.kanade.presentation.more.settings.screen.debug import android.content.Context import androidx.compose.foundation.horizontalScroll @@ -11,43 +11,39 @@ import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import androidx.lifecycle.asFlow import androidx.work.WorkInfo -import androidx.work.WorkManager import androidx.work.WorkQuery import cafe.adriel.voyager.core.model.ScreenModel -import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.util.Screen -import eu.kanade.tachiyomi.R +import eu.kanade.presentation.util.ioCoroutineScope +import eu.kanade.tachiyomi.util.system.copyToClipboard +import eu.kanade.tachiyomi.util.system.workManager import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import tachiyomi.presentation.core.components.LazyColumn import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.util.plus object WorkerInfoScreen : Screen() { + const val title = "Worker info" + @Composable override fun Content() { val context = LocalContext.current @@ -59,24 +55,19 @@ object WorkerInfoScreen : Screen() { val finished by screenModel.finished.collectAsState() val running by screenModel.running.collectAsState() - val snackbarHostState = remember { SnackbarHostState() } - val scope = rememberCoroutineScope() - Scaffold( topBar = { TopAppBar( - title = { Text(text = stringResource(R.string.pref_worker_info)) }, + title = { Text(text = title) }, navigationIcon = { IconButton(onClick = navigator::pop) { Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) } }, actions = { - val copiedString = stringResource(R.string.copied_to_clipboard_plain) IconButton( onClick = { - clipboardManager.setText(AnnotatedString(enqueued + finished + running)) - scope.launch { snackbarHostState.showSnackbar(copiedString) } + context.copyToClipboard(title, enqueued + finished + running) }, ) { Icon(imageVector = Icons.Default.ContentCopy, contentDescription = null) @@ -85,7 +76,6 @@ object WorkerInfoScreen : Screen() { scrollBehavior = it, ) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { contentPadding -> LazyColumn( contentPadding = contentPadding + PaddingValues(horizontal = 16.dp), @@ -122,31 +112,31 @@ object WorkerInfoScreen : Screen() { } private class Model(context: Context) : ScreenModel { - private val workManager = WorkManager.getInstance(context) + private val workManager = context.workManager val finished = workManager .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED)) .asFlow() .map(::constructString) - .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + .stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "") val running = workManager .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING)) .asFlow() .map(::constructString) - .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + .stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "") val enqueued = workManager .getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.ENQUEUED)) .asFlow() .map(::constructString) - .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), "") + .stateIn(ioCoroutineScope, SharingStarted.WhileSubscribed(), "") private fun constructString(list: List) = buildString { if (list.isEmpty()) { appendLine("-") } else { - list.forEach { workInfo -> + list.fastForEach { workInfo -> appendLine("Id: ${workInfo.id}") appendLine("Tags:") workInfo.tags.forEach { diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index b3e6c79b11..3855b395fb 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import eu.kanade.tachiyomi.R import tachiyomi.presentation.core.components.ScrollbarLazyColumn +import tachiyomi.presentation.core.components.WheelNumberPicker import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.material.AlertDialogContent import tachiyomi.presentation.core.components.material.Divider @@ -100,10 +101,10 @@ fun TrackItemSelector( BaseSelector( title = stringResource(titleText), content = { - WheelTextPicker( + WheelNumberPicker( modifier = Modifier.align(Alignment.Center), startIndex = selection, - texts = range.map { "$it" }, + items = range.toList(), onSelectionChanged = { onSelectionChange(it) }, ) }, @@ -126,7 +127,7 @@ fun TrackScoreSelector( WheelTextPicker( modifier = Modifier.align(Alignment.Center), startIndex = selections.indexOf(selection).coerceAtLeast(0), - texts = selections, + items = selections, onSelectionChanged = { onSelectionChange(selections[it]) }, ) }, @@ -149,12 +150,14 @@ fun TrackDateSelector( ) AlertDialogContent( modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars), + title = { Text(text = title) }, content = { Column { DatePicker( state = pickerState, - title = { Text(text = title) }, dateValidator = dateValidator, + title = null, + headline = null, showModeToggle = false, ) Row( diff --git a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt index fe89516d64..f9eaa93df6 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Navigator.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Navigator.kt @@ -3,12 +3,20 @@ package eu.kanade.presentation.util import androidx.compose.runtime.Composable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.staticCompositionLocalOf +import cafe.adriel.voyager.core.model.ScreenModel +import cafe.adriel.voyager.core.model.ScreenModelStore import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.ScreenKey import cafe.adriel.voyager.core.screen.uniqueScreenKey import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.ScreenTransition +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.plus import soup.compose.material.motion.animation.materialSharedAxisX import soup.compose.material.motion.animation.rememberSlideDistance @@ -28,6 +36,18 @@ abstract class Screen : Screen { override val key: ScreenKey = uniqueScreenKey } +/** + * A variant of ScreenModel.coroutineScope except with the IO dispatcher instead of the + * main dispatcher. + */ +val ScreenModel.ioCoroutineScope: CoroutineScope + get() = ScreenModelStore.getOrPutDependency( + screenModel = this, + name = "ScreenModelIoCoroutineScope", + factory = { key -> CoroutineScope(Dispatchers.IO + SupervisorJob()) + CoroutineName(key) }, + onDispose = { scope -> scope.cancel() }, + ) + interface AssistContentScreen { fun onProvideAssistUrl(): String? } diff --git a/app/src/main/java/eu/kanade/presentation/util/Resources.kt b/app/src/main/java/eu/kanade/presentation/util/Resources.kt index 44d9968b94..5b7bc9948d 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Resources.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Resources.kt @@ -11,11 +11,11 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap /** - * Create a BitmapPainter from an drawable resource. - * - * > Only use this if [androidx.compose.ui.res.painterResource] doesn't work. + * Create a BitmapPainter from a drawable resource. + * Use this only if [androidx.compose.ui.res.painterResource] doesn't work. * * @param id the resource identifier + * * @return the bitmap associated with the resource */ @Composable diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index c34fa42d31..5b754c0573 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -41,8 +41,8 @@ import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.animatorDurationScale -import eu.kanade.tachiyomi.util.system.notification -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.notify import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -96,7 +96,10 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { .onEach { enabled -> if (enabled) { disableIncognitoReceiver.register() - val notification = notification(Notifications.CHANNEL_INCOGNITO_MODE) { + notify( + Notifications.ID_INCOGNITO_MODE, + Notifications.CHANNEL_INCOGNITO_MODE, + ) { setContentTitle(getString(R.string.pref_incognito_mode)) setContentText(getString(R.string.notification_incognito_text)) setSmallIcon(R.drawable.ic_glasses_24dp) @@ -110,10 +113,9 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory { ) setContentIntent(pendingIntent) } - notificationManager.notify(Notifications.ID_INCOGNITO_MODE, notification) } else { disableIncognitoReceiver.unregister() - notificationManager.cancel(Notifications.ID_INCOGNITO_MODE) + cancelNotification(Notifications.ID_INCOGNITO_MODE) } } .launchIn(ProcessLifecycleOwner.get().lifecycleScope) diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 70b61ae093..f612c661ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -3,15 +3,13 @@ package eu.kanade.tachiyomi import android.content.Context import androidx.core.content.edit import androidx.preference.PreferenceManager -import androidx.work.WorkManager import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.ui.UiPreferences import eu.kanade.tachiyomi.core.security.SecurityPreferences -import eu.kanade.tachiyomi.data.backup.BackupCreatorJob +import eu.kanade.tachiyomi.data.backup.BackupCreateJob import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE @@ -21,7 +19,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.preference.minusAssign import eu.kanade.tachiyomi.util.preference.plusAssign import eu.kanade.tachiyomi.util.system.DeviceUtil +import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.system.workManager import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.getEnum import tachiyomi.domain.backup.service.BackupPreferences @@ -61,7 +61,7 @@ object Migrations { // Always set up background tasks to ensure they're running MangaLibraryUpdateJob.setupTask(context) AnimeLibraryUpdateJob.setupTask(context) - BackupCreatorJob.setupTask(context) + BackupCreateJob.setupTask(context) // Fresh install if (oldVersion == 0) { @@ -103,7 +103,7 @@ object Migrations { if (oldVersion < 43) { // Restore jobs after migrating from Evernote's job scheduler to WorkManager. MangaLibraryUpdateJob.setupTask(context) - BackupCreatorJob.setupTask(context) + BackupCreateJob.setupTask(context) } if (oldVersion < 44) { // Reset sorting preference if using removed sort by source @@ -282,12 +282,12 @@ object Migrations { if (oldSecureScreen) { securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS) } - if (DeviceUtil.isMiui && basePreferences.extensionInstaller().get() == PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER) { - basePreferences.extensionInstaller().set(PreferenceValues.ExtensionInstaller.LEGACY) + if (DeviceUtil.isMiui && basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) { + basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY) } } if (oldVersion < 76) { - BackupCreatorJob.setupTask(context) + BackupCreateJob.setupTask(context) } if (oldVersion < 77) { val oldReaderTap = prefs.getBoolean("reader_tap", false) @@ -331,7 +331,7 @@ object Migrations { } if (backupPreferences.backupInterval().get() == 0) { backupPreferences.backupInterval().set(12) - BackupCreatorJob.setupTask(context) + BackupCreateJob.setupTask(context) } } if (oldVersion < 85) { @@ -416,8 +416,8 @@ object Migrations { } if (oldVersion < 97) { // Removed background jobs - WorkManager.getInstance(context).cancelAllWorkByTag("UpdateChecker") - WorkManager.getInstance(context).cancelAllWorkByTag("ExtensionUpdate") + context.workManager.cancelAllWorkByTag("UpdateChecker") + context.workManager.cancelAllWorkByTag("ExtensionUpdate") prefs.edit { remove("automatic_ext_updates") } @@ -447,6 +447,16 @@ object Migrations { } } } + if (oldVersion < 100) { + BackupCreateJob.setupTask(context) + } + if (oldVersion < 102) { + // This was accidentally visible from the reader settings sheet, but should always + // be disabled in release builds. + if (isReleaseBuildType) { + readerPreferences.longStripSplitWebtoon().set(false) + } + } return true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt similarity index 77% rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt index 13d63cb9fa..752e0384bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt @@ -9,13 +9,13 @@ import androidx.work.ExistingWorkPolicy import androidx.work.ForegroundInfo import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder -import androidx.work.WorkInfo -import androidx.work.WorkManager import androidx.work.WorkerParameters import androidx.work.workDataOf import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.isRunning +import eu.kanade.tachiyomi.util.system.workManager import logcat.LogPriority import tachiyomi.core.util.system.logcat import tachiyomi.domain.backup.service.BackupPreferences @@ -23,7 +23,7 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.TimeUnit -class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : +class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { private val notifier = BackupNotifier(context) @@ -41,7 +41,6 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" } } - context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) return try { val location = BackupManager(context).createBackup(uri, flags, isAutoBackup) if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())) @@ -51,18 +50,20 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet if (!isAutoBackup) notifier.showBackupError(e.message) Result.failure() } finally { - context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) + context.cancelNotification(Notifications.ID_BACKUP_PROGRESS) } } override suspend fun getForegroundInfo(): ForegroundInfo { - return ForegroundInfo(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) + return ForegroundInfo( + Notifications.ID_BACKUP_PROGRESS, + notifier.showBackupProgress().build(), + ) } companion object { fun isManualJobRunning(context: Context): Boolean { - val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG_MANUAL).get() - return list.find { it.state == WorkInfo.State.RUNNING } != null + return context.workManager.isRunning(TAG_MANUAL) } fun setupTask(context: Context, prefInterval: Int? = null, prefFlags: Int? = null) { @@ -71,9 +72,8 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet val flags = prefFlags ?: backupPreferences.backupFlags().get().sumOf { s -> s.toInt(16) } - val workManager = WorkManager.getInstance(context) if (interval > 0) { - val request = PeriodicWorkRequestBuilder( + val request = PeriodicWorkRequestBuilder( interval.toLong(), TimeUnit.HOURS, 10, @@ -88,9 +88,9 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet ) .build() - workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) + context.workManager.enqueueUniquePeriodicWork(TAG_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) } else { - workManager.cancelUniqueWork(TAG_AUTO) + context.workManager.cancelUniqueWork(TAG_AUTO) } } @@ -100,11 +100,11 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet LOCATION_URI_KEY to uri.toString(), BACKUP_FLAGS_KEY to flags, ) - val request = OneTimeWorkRequestBuilder() + val request = OneTimeWorkRequestBuilder() .addTag(TAG_MANUAL) .setInputData(inputData) .build() - WorkManager.getInstance(context).enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request) + context.workManager.enqueueUniqueWork(TAG_MANUAL, ExistingWorkPolicy.KEEP, request) } } } 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 dc172cae7c..fb4a548888 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 @@ -193,7 +193,7 @@ class BackupManager( .map(Manga::source) .distinct() .map(mangaSourceManager::getOrStub) - .map { BackupSource.copyFrom(it) } + .map(BackupSource::copyFrom) .toList() } @@ -203,7 +203,7 @@ class BackupManager( .map(Anime::source) .distinct() .map(animeSourceManager::getOrStub) - .map { BackupAnimeSource.copyFrom(it) } + .map(BackupAnimeSource::copyFrom) .toList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt index f7ef75d9d6..092e88bc7e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt @@ -9,8 +9,9 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.storage.getUriCompat +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify import uy.kohesive.injekt.injectLazy import java.io.File import java.util.concurrent.TimeUnit @@ -34,7 +35,7 @@ class BackupNotifier(private val context: Context) { } private fun NotificationCompat.Builder.show(id: Int) { - context.notificationManager.notify(id, build()) + context.notify(id, build()) } fun showBackupProgress(): NotificationCompat.Builder { @@ -50,7 +51,7 @@ class BackupNotifier(private val context: Context) { } fun showBackupError(error: String?) { - context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) + context.cancelNotification(Notifications.ID_BACKUP_PROGRESS) with(completeNotificationBuilder) { setContentTitle(context.getString(R.string.creating_backup_error)) @@ -61,15 +62,13 @@ class BackupNotifier(private val context: Context) { } fun showBackupComplete(unifile: UniFile) { - context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) + context.cancelNotification(Notifications.ID_BACKUP_PROGRESS) with(completeNotificationBuilder) { setContentTitle(context.getString(R.string.backup_created)) setContentText(unifile.filePath ?: unifile.name) - // Clear old actions if they exist clearActions() - addAction( R.drawable.ic_share_24dp, context.getString(R.string.action_share), @@ -91,12 +90,10 @@ class BackupNotifier(private val context: Context) { setProgress(maxAmount, progress, false) setOnlyAlertOnce(true) - // Clear old actions if they exist clearActions() - addAction( R.drawable.ic_close_24dp, - context.getString(R.string.action_stop), + context.getString(R.string.action_cancel), NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS), ) } @@ -107,7 +104,7 @@ class BackupNotifier(private val context: Context) { } fun showRestoreError(error: String?) { - context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS) + context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) with(completeNotificationBuilder) { setContentTitle(context.getString(R.string.restoring_backup_error)) @@ -118,7 +115,7 @@ class BackupNotifier(private val context: Context) { } fun showRestoreComplete(time: Long, errorCount: Int, path: String?, file: String?) { - context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS) + context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) val timeString = context.getString( R.string.restore_duration, @@ -132,9 +129,7 @@ class BackupNotifier(private val context: Context) { setContentTitle(context.getString(R.string.restore_completed)) setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount)) - // Clear old actions if they exist clearActions() - if (errorCount > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) { val destFile = File(path, file) val uri = destFile.getUriCompat(context) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt new file mode 100644 index 0000000000..72d23e48a3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt @@ -0,0 +1,85 @@ +package eu.kanade.tachiyomi.data.backup + +import android.content.Context +import android.net.Uri +import androidx.core.net.toUri +import androidx.work.CoroutineWorker +import androidx.work.ExistingWorkPolicy +import androidx.work.ForegroundInfo +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.system.cancelNotification +import eu.kanade.tachiyomi.util.system.isRunning +import eu.kanade.tachiyomi.util.system.workManager +import kotlinx.coroutines.CancellationException +import logcat.LogPriority +import tachiyomi.core.util.system.logcat + +class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) : + CoroutineWorker(context, workerParams) { + + private val notifier = BackupNotifier(context) + + override suspend fun doWork(): Result { + val uri = inputData.getString(LOCATION_URI_KEY)?.toUri() + ?: return Result.failure() + + try { + setForeground(getForegroundInfo()) + } catch (e: IllegalStateException) { + logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" } + } + + return try { + val restorer = BackupRestorer(context, notifier) + restorer.restoreBackup(uri) + Result.success() + } catch (e: Exception) { + if (e is CancellationException) { + notifier.showRestoreError(context.getString(R.string.restoring_backup_canceled)) + Result.success() + } else { + logcat(LogPriority.ERROR, e) + notifier.showRestoreError(e.message) + Result.failure() + } + } finally { + context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) + } + } + + override suspend fun getForegroundInfo(): ForegroundInfo { + return ForegroundInfo( + Notifications.ID_RESTORE_PROGRESS, + notifier.showRestoreProgress().build(), + ) + } + + companion object { + fun isRunning(context: Context): Boolean { + return context.workManager.isRunning(TAG) + } + + fun start(context: Context, uri: Uri) { + val inputData = workDataOf( + LOCATION_URI_KEY to uri.toString(), + ) + val request = OneTimeWorkRequestBuilder() + .addTag(TAG) + .setInputData(inputData) + .build() + context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request) + } + + fun stop(context: Context) { + context.workManager.cancelUniqueWork(TAG) + } + } +} + +private const val TAG = "BackupRestore" + +private const val LOCATION_URI_KEY = "location_uri" // String diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt deleted file mode 100644 index 28b796586a..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ /dev/null @@ -1,141 +0,0 @@ -package eu.kanade.tachiyomi.data.backup - -import android.app.Service -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.IBinder -import android.os.PowerManager -import androidx.core.content.ContextCompat -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.util.system.acquireWakeLock -import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat -import eu.kanade.tachiyomi.util.system.isServiceRunning -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import logcat.LogPriority -import tachiyomi.core.util.system.logcat - -/** - * Restores backup. - */ -class BackupRestoreService : Service() { - - companion object { - - /** - * Returns the status of the service. - * - * @param context the application context. - * @return true if the service is running, false otherwise. - */ - fun isRunning(context: Context): Boolean = - context.isServiceRunning(BackupRestoreService::class.java) - - /** - * Starts a service to restore a backup from Json - * - * @param context context of application - * @param uri path of Uri - */ - fun start(context: Context, uri: Uri) { - if (isRunning(context)) return - - val intent = Intent(context, BackupRestoreService::class.java).apply { - putExtra(BackupConst.EXTRA_URI, uri) - } - ContextCompat.startForegroundService(context, intent) - } - - /** - * Stops the service. - * - * @param context the application context. - */ - fun stop(context: Context) { - context.stopService(Intent(context, BackupRestoreService::class.java)) - - BackupNotifier(context).showRestoreError(context.getString(R.string.restoring_backup_canceled)) - } - } - - /** - * Wake lock that will be held until the service is destroyed. - */ - private lateinit var wakeLock: PowerManager.WakeLock - - private lateinit var scope: CoroutineScope - private var restorer: BackupRestorer? = null - private lateinit var notifier: BackupNotifier - - override fun onCreate() { - scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - notifier = BackupNotifier(this) - wakeLock = acquireWakeLock(javaClass.name) - - startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build()) - } - - override fun stopService(name: Intent?): Boolean { - destroyJob() - return super.stopService(name) - } - - override fun onDestroy() { - destroyJob() - } - - private fun destroyJob() { - restorer?.job?.cancel() - scope.cancel() - if (wakeLock.isHeld) { - wakeLock.release() - } - } - - /** - * This method needs to be implemented, but it's not used/needed. - */ - override fun onBind(intent: Intent): IBinder? = null - - /** - * Method called when the service receives an intent. - * - * @param intent the start intent from. - * @param flags the flags of the command. - * @param startId the start id of this command. - * @return the start value of the command. - */ - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val uri = intent?.getParcelableExtraCompat(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY - - // Cancel any previous job if needed. - restorer?.job?.cancel() - - restorer = BackupRestorer(this, notifier) - - val handler = CoroutineExceptionHandler { _, exception -> - logcat(LogPriority.ERROR, exception) - restorer?.writeErrorLog() - - notifier.showRestoreError(exception.message) - stopSelf(startId) - } - val job = scope.launch(handler) { - if (restorer?.restoreBackup(uri) == false) { - notifier.showRestoreError(getString(R.string.restoring_backup_canceled)) - } - } - job.invokeOnCompletion { - stopSelf(startId) - } - restorer?.job = job - - return START_NOT_STICKY - } -} 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 e5e3efe2a2..20d88d4196 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 @@ -25,7 +25,8 @@ import eu.kanade.tachiyomi.data.backup.models.StringSetPreferenceValue import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.createFileInCacheDir -import kotlinx.coroutines.Job +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.isActive import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.entries.manga.model.Manga @@ -43,8 +44,6 @@ class BackupRestorer( private val notifier: BackupNotifier, ) { - var job: Job? = null - private var backupManager = BackupManager(context) private var restoreAmount = 0 @@ -76,7 +75,7 @@ class BackupRestorer( return true } - fun writeErrorLog(): File { + private fun writeErrorLog(): File { try { if (errors.isNotEmpty()) { val file = context.createFileInCacheDir("aniyomi_restore.txt") @@ -117,41 +116,42 @@ class BackupRestorer( val backupAnimeMaps = backup.backupBrokenAnimeSources.map { BackupAnimeSource(it.name, it.sourceId) } + backup.backupAnimeSources animeSourceMapping = backupAnimeMaps.associate { it.sourceId to it.name } - // Restore individual manga - backup.backupManga.forEach { - if (job?.isActive != true) { - return false + return coroutineScope { + // Restore individual manga + backup.backupManga.forEach { + if (!isActive) { + return@coroutineScope false + } + + restoreManga(it, backup.backupCategories) } - restoreManga(it, backup.backupCategories) - } + backup.backupAnime.forEach { + if (!isActive) { + return@coroutineScope false + } - backup.backupAnime.forEach { - if (job?.isActive != true) { - return false + restoreAnime(it, backup.backupAnimeCategories) } - restoreAnime(it, backup.backupAnimeCategories) - } - - // TODO: optionally trigger online library + tracker update + if (backup.backupPreferences.isNotEmpty()) { + restorePreferences( + backup.backupPreferences, + PreferenceManager.getDefaultSharedPreferences(context), + ) + } - if (backup.backupPreferences.isNotEmpty()) { - restorePreferences( - backup.backupPreferences, - PreferenceManager.getDefaultSharedPreferences(context), - ) - } + if (backup.backupExtensionPreferences.isNotEmpty()) { + restoreExtensionPreferences(backup.backupExtensionPreferences) + } - if (backup.backupExtensionPreferences.isNotEmpty()) { - restoreExtensionPreferences(backup.backupExtensionPreferences) - } + if (backup.backupExtensions.isNotEmpty()) { + restoreExtensions(backup.backupExtensions) + } - if (backup.backupExtensions.isNotEmpty()) { - restoreExtensions(backup.backupExtensions) + // TODO: optionally trigger online library + tracker update + true } - - return true } private suspend fun restoreCategories(backupCategories: List) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt index 0453480306..59e8099cd5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadManager.kt @@ -90,7 +90,7 @@ class AnimeDownloadManager( * @param episodeId the episode to check. */ fun getQueuedDownloadOrNull(episodeId: Long): AnimeDownload? { - return queueState.value.find { it: AnimeDownload -> it.episode.id == episodeId } + return queueState.value.find { it.episode.id == episodeId } } fun startDownloadNow(episodeId: Long?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt index 6bc773a6f3..f4ed0c4dad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt @@ -12,8 +12,9 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.lang.chop +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify import uy.kohesive.injekt.injectLazy import java.util.regex.Pattern @@ -61,7 +62,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { * @param id the id of the notification. */ private fun NotificationCompat.Builder.show(id: Int) { - context.notificationManager.notify(id, build()) + context.notify(id, build()) } /** @@ -70,7 +71,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { */ fun dismissProgress(download: AnimeDownload) { val notificationId = notificationIdMap[download.episode.id] ?: return - context.notificationManager.cancel(notificationId) + context.cancelNotification(notificationId) notificationIdMap.remove(download.episode.id) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt index fa3f97ecad..2ef9324d35 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt @@ -138,14 +138,26 @@ class AnimeDownloadProvider( * @param episodeScanlator scanlator of the episode to query */ fun getEpisodeDirName(episodeName: String, episodeScanlator: String?): String { + val newEpisodeName = sanitizeEpisodeName(episodeName) return DiskUtil.buildValidFilename( when { - episodeScanlator.isNullOrBlank().not() -> "${episodeScanlator}_$episodeName" - else -> episodeName + episodeScanlator.isNullOrBlank().not() -> "${episodeScanlator}_$newEpisodeName" + else -> newEpisodeName }, ) } + /** + * Return the new name for the episode (in case it's empty or blank) + * + * @param episodeName the name of the episode + */ + private fun sanitizeEpisodeName(episodeName: String): String { + return episodeName.ifBlank { + "Episode" + } + } + /** * Returns the episode directory name for an episode. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt index a5ff3e863d..d78f3e6244 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt @@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isServiceRunning -import eu.kanade.tachiyomi.util.system.notification +import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -152,8 +152,8 @@ class AnimeDownloadService : Service() { } private fun getPlaceholderNotification(): Notification { - return notification(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { + return notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { setContentTitle(getString(R.string.download_notifier_downloader_title)) - } + }.build() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt index 708bd17d9f..1e20f55dda 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt @@ -62,11 +62,6 @@ import java.util.concurrent.TimeUnit * * The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected * behavior, but it's safe to read it from multiple threads. - * - * @param context the application context. - * @param provider the downloads directory provider. - * @param cache the downloads cache, used to add the downloads to the cache after their completion. - * @param sourceManager the source manager. */ class AnimeDownloader( private val context: Context, @@ -85,7 +80,7 @@ class AnimeDownloader( /** * Queue where active downloads are kept. */ - val _queueState = MutableStateFlow>(emptyList()) + private val _queueState = MutableStateFlow>(emptyList()) val queueState = _queueState.asStateFlow() /** @@ -146,7 +141,7 @@ class AnimeDownloader( initializeSubscription() - val pending = queueState.value.filter { it: AnimeDownload -> it.status != AnimeDownload.State.DOWNLOADED } + val pending = queueState.value.filter { it.status != AnimeDownload.State.DOWNLOADED } pending.forEach { if (it.status != AnimeDownload.State.QUEUE) it.status = AnimeDownload.State.QUEUE } isPaused = false @@ -290,7 +285,7 @@ class AnimeDownloader( // Runs in main thread (synchronization needed). val episodesToQueue = episodesWithoutDir.await() // Filter out those already enqueued. - .filter { episode -> queueState.value.none { it: AnimeDownload -> it.episode.id == episode.id } } + .filter { episode -> queueState.value.none { it.episode.id == episode.id } } // Create a download for each one. .map { AnimeDownload(source, anime, it, changeDownloader, video) } @@ -765,7 +760,7 @@ class AnimeDownloader( * Returns true if all the queued downloads are in DOWNLOADED or ERROR state. */ private fun areAllAnimeDownloadsFinished(): Boolean { - return queueState.value.none { it: AnimeDownload -> it.status.value <= AnimeDownload.State.DOWNLOADING.value } + return queueState.value.none { it.status.value <= AnimeDownload.State.DOWNLOADING.value } } private val progressSubject = PublishSubject.create() @@ -780,7 +775,7 @@ class AnimeDownloader( video?.progressSubject = subject } - fun addAllToQueue(downloads: List) { + private fun addAllToQueue(downloads: List) { _queueState.update { downloads.forEach { download -> download.progressSubject = progressSubject @@ -792,7 +787,7 @@ class AnimeDownloader( } } - fun removeFromQueue(download: AnimeDownload) { + private fun removeFromQueue(download: AnimeDownload) { _queueState.update { store.remove(download) download.progressSubject = null @@ -814,7 +809,7 @@ class AnimeDownloader( queueState.value.filter { it.anime.id == anime.id }.forEach { removeFromQueue(it) } } - fun _clearQueue() { + private fun _clearQueue() { _queueState.update { it.forEach { download -> download.progressSubject = null diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt index 1ec8689ebb..4d04be8c22 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt @@ -89,7 +89,7 @@ class MangaDownloadManager( * @param chapterId the chapter to check. */ fun getQueuedDownloadOrNull(chapterId: Long): MangaDownload? { - return queueState.value.find { it: MangaDownload -> it.chapter.id == chapterId } + return queueState.value.find { it.chapter.id == chapterId } } fun startDownloadNow(chapterId: Long?) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadNotifier.kt index 08b286ebc0..22befe3003 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadNotifier.kt @@ -11,8 +11,9 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.lang.chop +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify import uy.kohesive.injekt.injectLazy import java.util.regex.Pattern @@ -50,7 +51,7 @@ internal class MangaDownloadNotifier(private val context: Context) { * @param id the id of the notification. */ private fun NotificationCompat.Builder.show(id: Int) { - context.notificationManager.notify(id, build()) + context.notify(id, build()) } /** @@ -58,7 +59,7 @@ internal class MangaDownloadNotifier(private val context: Context) { * those can only be dismissed by the user. */ fun dismissProgress() { - context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS) + context.cancelNotification(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt index 19f90a45a1..f9b12f82ca 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt @@ -138,14 +138,26 @@ class MangaDownloadProvider( * @param chapterScanlator scanlator of the chapter to query */ fun getChapterDirName(chapterName: String, chapterScanlator: String?): String { + val newChapterName = sanitizeChapterName(chapterName) return DiskUtil.buildValidFilename( when { - chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$chapterName" - else -> chapterName + chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$newChapterName" + else -> newChapterName }, ) } + /** + * Return the new name for the chapter (in case it's empty or blank) + * + * @param chapterName the name of the chapter + */ + private fun sanitizeChapterName(chapterName: String): String { + return chapterName.ifBlank { + "Chapter" + } + } + fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean { return oldChapter.name != newChapter.name || oldChapter.scanlator?.takeIf { it.isNotBlank() } != newChapter.scanlator?.takeIf { it.isNotBlank() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt index 36a43679ff..58042c1c0c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadService.kt @@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isServiceRunning -import eu.kanade.tachiyomi.util.system.notification +import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -143,8 +143,8 @@ class MangaDownloadService : Service() { } private fun getPlaceholderNotification(): Notification { - return notification(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { + return notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { setContentTitle(getString(R.string.download_notifier_downloader_title)) - } + }.build() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 2f3a69d5c1..9baa850bd0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -66,11 +66,6 @@ import java.util.zip.ZipOutputStream * * The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected * behavior, but it's safe to read it from multiple threads. - * - * @param context the application context. - * @param provider the downloads directory provider. - * @param cache the downloads cache, used to add the downloads to the cache after their completion. - * @param sourceManager the source manager. */ class MangaDownloader( private val context: Context, @@ -90,7 +85,7 @@ class MangaDownloader( /** * Queue where active downloads are kept. */ - val _queueState = MutableStateFlow>(emptyList()) + private val _queueState = MutableStateFlow>(emptyList()) val queueState = _queueState.asStateFlow() /** @@ -140,7 +135,7 @@ class MangaDownloader( initializeSubscription() - val pending = queueState.value.filter { it: MangaDownload -> it.status != MangaDownload.State.DOWNLOADED } + val pending = queueState.value.filter { it.status != MangaDownload.State.DOWNLOADED } pending.forEach { if (it.status != MangaDownload.State.QUEUE) it.status = MangaDownload.State.QUEUE } isPaused = false @@ -266,7 +261,7 @@ class MangaDownloader( // Runs in main thread (synchronization needed). val chaptersToQueue = chaptersWithoutDir.await() // Filter out those already enqueued. - .filter { chapter -> queueState.value.none { it: MangaDownload -> it.chapter.id == chapter.id } } + .filter { chapter -> queueState.value.none { it.chapter.id == chapter.id } } // Create a download for each one. .map { MangaDownload(source, manga, it) } @@ -499,6 +494,8 @@ class MangaDownloader( } private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { + if (!downloadPreferences.splitTallImages().get()) return + try { val filenamePrefix = String.format("%03d", page.number) val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) } @@ -638,10 +635,10 @@ class MangaDownloader( * Returns true if all the queued downloads are in DOWNLOADED or ERROR state. */ private fun areAllDownloadsFinished(): Boolean { - return queueState.value.none { it: MangaDownload -> it.status.value <= MangaDownload.State.DOWNLOADING.value } + return queueState.value.none { it.status.value <= MangaDownload.State.DOWNLOADING.value } } - fun addAllToQueue(downloads: List) { + private fun addAllToQueue(downloads: List) { _queueState.update { downloads.forEach { download -> download.status = MangaDownload.State.QUEUE @@ -651,7 +648,7 @@ class MangaDownloader( } } - fun removeFromQueue(download: MangaDownload) { + private fun removeFromQueue(download: MangaDownload) { _queueState.update { store.remove(download) if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { @@ -671,7 +668,7 @@ class MangaDownloader( queueState.value.filter { it.manga.id == manga.id }.forEach { removeFromQueue(it) } } - fun _clearQueue() { + private fun _clearQueue() { _queueState.update { it.forEach { download -> if (download.status == MangaDownload.State.DOWNLOADING || download.status == MangaDownload.State.QUEUE) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt index f0e4258339..1eed2ad892 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateJob.kt @@ -11,7 +11,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkInfo -import androidx.work.WorkManager import androidx.work.WorkQuery import androidx.work.WorkerParameters import androidx.work.workDataOf @@ -37,8 +36,9 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewEpisodes import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.isConnectedToWifi +import eu.kanade.tachiyomi.util.system.isRunning +import eu.kanade.tachiyomi.util.system.workManager import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -47,7 +47,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit -import kotlinx.coroutines.withContext import logcat.LogPriority import tachiyomi.core.preference.getAndSet import tachiyomi.core.util.lang.withIOContext @@ -116,13 +115,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa } // Find a running manual worker. If exists, try again later - val otherRunningWorker = withContext(Dispatchers.IO) { - WorkManager.getInstance(context) - .getWorkInfosByTag(WORK_NAME_MANUAL) - .get() - .find { it.state == WorkInfo.State.RUNNING } - } - if (otherRunningWorker != null) { + if (context.workManager.isRunning(WORK_NAME_MANUAL)) { return Result.retry() } } @@ -167,7 +160,10 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa override suspend fun getForegroundInfo(): ForegroundInfo { val notifier = AnimeLibraryUpdateNotifier(context) - return ForegroundInfo(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build()) + return ForegroundInfo( + Notifications.ID_LIBRARY_PROGRESS, + notifier.progressNotificationBuilder.build(), + ) } /** @@ -539,7 +535,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa private const val KEY_TARGET = "target" fun cancelAllWorks(context: Context) { - WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + context.workManager.cancelAllWorkByTag(TAG) } fun setupTask( @@ -568,9 +564,9 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES) .build() - WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) + context.workManager.enqueueUniquePeriodicWork(WORK_NAME_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) } else { - WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME_AUTO) + context.workManager.cancelUniqueWork(WORK_NAME_AUTO) } } fun startNow( @@ -578,9 +574,8 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa category: Category? = null, target: Target = Target.EPISODES, ): Boolean { - val wm = WorkManager.getInstance(context) - val infos = wm.getWorkInfosByTag(TAG).get() - if (infos.find { it.state == WorkInfo.State.RUNNING } != null) { + val wm = context.workManager + if (wm.isRunning(TAG)) { // Already running either as a scheduled or manual job return false } @@ -600,7 +595,7 @@ class AnimeLibraryUpdateJob(private val context: Context, workerParams: WorkerPa } fun stop(context: Context) { - val wm = WorkManager.getInstance(context) + val wm = context.workManager val workQuery = WorkQuery.Builder.fromTags(listOf(TAG)) .addStates(listOf(WorkInfo.State.RUNNING)) .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt index 53680d2fc0..00d04dd3dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt @@ -9,6 +9,7 @@ import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import coil.imageLoader import coil.request.ImageRequest import coil.transform.CircleCropTransformation @@ -21,9 +22,9 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.chop -import eu.kanade.tachiyomi.util.system.notification +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify import tachiyomi.core.util.lang.launchUI import tachiyomi.domain.entries.anime.model.Anime import tachiyomi.domain.items.episode.model.Episode @@ -82,7 +83,7 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { .setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder .setProgress(total, current, false) @@ -91,18 +92,16 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { } fun showQueueSizeWarningNotification() { - val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) { + context.notify( + Notifications.ID_LIBRARY_SIZE_WARNING, + Notifications.CHANNEL_LIBRARY_PROGRESS, + ) { setContentTitle(context.getString(R.string.label_warning)) setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(R.string.notification_size_warning))) setSmallIcon(R.drawable.ic_warning_white_24dp) setTimeoutAfter(AnimeDownloader.WARNING_NOTIF_TIMEOUT_MS) setContentIntent(NotificationHandler.openUrl(context, HELP_WARNING_URL)) } - - context.notificationManager.notify( - Notifications.ID_LIBRARY_SIZE_WARNING, - notificationBuilder.build(), - ) } /** @@ -116,17 +115,16 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { return } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_ERROR, - context.notificationBuilder(Notifications.CHANNEL_LIBRARY_ERROR) { - setContentTitle(context.resources.getString(R.string.notification_update_error, failed)) - setContentText(context.getString(R.string.action_show_errors)) - setSmallIcon(R.drawable.ic_ani) + Notifications.CHANNEL_LIBRARY_ERROR, + ) { + setContentTitle(context.resources.getString(R.string.notification_update_error, failed)) + setContentText(context.getString(R.string.action_show_errors)) + setSmallIcon(R.drawable.ic_ani) - setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri)) - } - .build(), - ) + setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri)) + } } /** @@ -139,16 +137,15 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { return } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_SKIPPED, - context.notificationBuilder(Notifications.CHANNEL_LIBRARY_SKIPPED) { - setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped)) - setContentText(context.getString(R.string.learn_more)) - setSmallIcon(R.drawable.ic_ani) - setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_ANIME_URL)) - } - .build(), - ) + Notifications.CHANNEL_LIBRARY_SKIPPED, + ) { + setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped)) + setContentText(context.getString(R.string.learn_more)) + setSmallIcon(R.drawable.ic_ani) + setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_ANIME_URL)) + } } /** @@ -158,58 +155,54 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { */ fun showUpdateNotifications(updates: List>>) { // Parent group notification - context.notificationManager.notify( - Notifications.ID_NEW_CHAPTERS, - context.notification(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { - setContentTitle(context.getString(R.string.notification_new_chapters)) - if (updates.size == 1 && !preferences.hideNotificationContent().get()) { - setContentText(updates.first().first.title.chop(NOTIF_ANIME_TITLE_MAX_LEN)) - } else { - setContentText( - context.resources.getQuantityString( - R.plurals.notification_new_chapters_summary, - updates.size, - updates.size, + context.notify( + Notifications.ID_NEW_EPISODES, + Notifications.CHANNEL_NEW_CHAPTERS_EPISODES, + ) { + setContentTitle(context.getString(R.string.notification_new_episodes)) + if (updates.size == 1 && !preferences.hideNotificationContent().get()) { + setContentText(updates.first().first.title.chop(NOTIF_ANIME_TITLE_MAX_LEN)) + } else { + setContentText(context.resources.getQuantityString(R.plurals.notification_new_episodes_summary, updates.size, updates.size)) + + if (!preferences.hideNotificationContent().get()) { + setStyle( + NotificationCompat.BigTextStyle().bigText( + updates.joinToString("\n") { + it.first.title.chop(NOTIF_ANIME_TITLE_MAX_LEN) + }, ), ) - - if (!preferences.hideNotificationContent().get()) { - setStyle( - NotificationCompat.BigTextStyle().bigText( - updates.joinToString("\n") { - it.first.title.chop(NOTIF_ANIME_TITLE_MAX_LEN) - }, - ), - ) - } } + } - setSmallIcon(R.drawable.ic_ani) - setLargeIcon(notificationBitmap) + setSmallIcon(R.drawable.ic_ani) + setLargeIcon(notificationBitmap) - setGroup(Notifications.GROUP_NEW_EPISODES) - setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - setGroupSummary(true) - priority = NotificationCompat.PRIORITY_HIGH + setGroup(Notifications.GROUP_NEW_EPISODES) + setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + setGroupSummary(true) + priority = NotificationCompat.PRIORITY_HIGH - setContentIntent(getNotificationIntent()) - setAutoCancel(true) - }, - ) + setContentIntent(getNotificationIntent()) + setAutoCancel(true) + } // Per-anime notification if (!preferences.hideNotificationContent().get()) { launchUI { - updates.forEach { (anime, episodes) -> - context.notificationManager.notify(anime.id.hashCode(), createNewEpisodesNotification(anime, episodes)) - } + context.notify( + updates.map { (anime, episodes) -> + NotificationManagerCompat.NotificationWithIdAndTag(anime.id.hashCode(), createNewEpisodesNotification(anime, episodes)) + }, + ) } } } private suspend fun createNewEpisodesNotification(anime: Anime, episodes: Array): Notification { val icon = getAnimeIcon(anime) - return context.notification(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { + return context.notificationBuilder(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { setContentTitle(anime.title) val description = getNewEpisodesDescription(episodes) @@ -265,14 +258,14 @@ class AnimeLibraryUpdateNotifier(private val context: Context) { ), ) } - } + }.build() } /** * Cancels the progress notification. */ fun cancelProgressNotification() { - context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS) + context.cancelNotification(Notifications.ID_LIBRARY_PROGRESS) } private suspend fun getAnimeIcon(anime: Anime): Bitmap? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt index f473b40186..e297854d93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateJob.kt @@ -11,7 +11,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkInfo -import androidx.work.WorkManager import androidx.work.WorkQuery import androidx.work.WorkerParameters import androidx.work.workDataOf @@ -37,8 +36,9 @@ import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.isConnectedToWifi +import eu.kanade.tachiyomi.util.system.isRunning +import eu.kanade.tachiyomi.util.system.workManager import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope @@ -47,7 +47,6 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit -import kotlinx.coroutines.withContext import logcat.LogPriority import tachiyomi.core.preference.getAndSet import tachiyomi.core.util.lang.withIOContext @@ -116,13 +115,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa } // Find a running manual worker. If exists, try again later - val otherRunningWorker = withContext(Dispatchers.IO) { - WorkManager.getInstance(context) - .getWorkInfosByTag(WORK_NAME_MANUAL) - .get() - .find { it.state == WorkInfo.State.RUNNING } - } - if (otherRunningWorker != null) { + if (context.workManager.isRunning(WORK_NAME_MANUAL)) { return Result.retry() } } @@ -167,7 +160,10 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa override suspend fun getForegroundInfo(): ForegroundInfo { val notifier = MangaLibraryUpdateNotifier(context) - return ForegroundInfo(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build()) + return ForegroundInfo( + Notifications.ID_LIBRARY_PROGRESS, + notifier.progressNotificationBuilder.build(), + ) } /** @@ -538,7 +534,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa private const val KEY_TARGET = "target" fun cancelAllWorks(context: Context) { - WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + context.workManager.cancelAllWorkByTag(TAG) } fun setupTask( @@ -567,9 +563,9 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES) .build() - WorkManager.getInstance(context).enqueueUniquePeriodicWork(WORK_NAME_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) + context.workManager.enqueueUniquePeriodicWork(WORK_NAME_AUTO, ExistingPeriodicWorkPolicy.UPDATE, request) } else { - WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME_AUTO) + context.workManager.cancelUniqueWork(WORK_NAME_AUTO) } } @@ -578,9 +574,8 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa category: Category? = null, target: Target = Target.CHAPTERS, ): Boolean { - val wm = WorkManager.getInstance(context) - val infos = wm.getWorkInfosByTag(TAG).get() - if (infos.find { it.state == WorkInfo.State.RUNNING } != null) { + val wm = context.workManager + if (wm.isRunning(TAG)) { // Already running either as a scheduled or manual job return false } @@ -600,7 +595,7 @@ class MangaLibraryUpdateJob(private val context: Context, workerParams: WorkerPa } fun stop(context: Context) { - val wm = WorkManager.getInstance(context) + val wm = context.workManager val workQuery = WorkQuery.Builder.fromTags(listOf(TAG)) .addStates(listOf(WorkInfo.State.RUNNING)) .build() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt index 356dfd9615..9e58b550c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt @@ -9,6 +9,7 @@ import android.graphics.BitmapFactory import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat import coil.imageLoader import coil.request.ImageRequest import coil.transform.CircleCropTransformation @@ -21,9 +22,9 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.chop -import eu.kanade.tachiyomi.util.system.notification +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify import tachiyomi.core.util.lang.launchUI import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.items.chapter.model.Chapter @@ -82,7 +83,7 @@ class MangaLibraryUpdateNotifier(private val context: Context) { .setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder .setProgress(total, current, false) @@ -91,7 +92,10 @@ class MangaLibraryUpdateNotifier(private val context: Context) { } fun showQueueSizeWarningNotification() { - val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) { + context.notify( + Notifications.ID_LIBRARY_SIZE_WARNING, + Notifications.CHANNEL_LIBRARY_PROGRESS, + ) { setContentTitle(context.getString(R.string.label_warning)) setStyle( NotificationCompat.BigTextStyle() @@ -101,11 +105,6 @@ class MangaLibraryUpdateNotifier(private val context: Context) { setTimeoutAfter(MangaDownloader.WARNING_NOTIF_TIMEOUT_MS) setContentIntent(NotificationHandler.openUrl(context, HELP_WARNING_URL)) } - - context.notificationManager.notify( - Notifications.ID_LIBRARY_SIZE_WARNING, - notificationBuilder.build(), - ) } /** @@ -119,17 +118,16 @@ class MangaLibraryUpdateNotifier(private val context: Context) { return } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_ERROR, - context.notificationBuilder(Notifications.CHANNEL_LIBRARY_ERROR) { - setContentTitle(context.resources.getString(R.string.notification_update_error, failed)) - setContentText(context.getString(R.string.action_show_errors)) - setSmallIcon(R.drawable.ic_ani) + Notifications.CHANNEL_LIBRARY_ERROR, + ) { + setContentTitle(context.resources.getString(R.string.notification_update_error, failed)) + setContentText(context.getString(R.string.action_show_errors)) + setSmallIcon(R.drawable.ic_ani) - setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri)) - } - .build(), - ) + setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri)) + } } /** @@ -142,16 +140,15 @@ class MangaLibraryUpdateNotifier(private val context: Context) { return } - context.notificationManager.notify( + context.notify( Notifications.ID_LIBRARY_SKIPPED, - context.notificationBuilder(Notifications.CHANNEL_LIBRARY_SKIPPED) { - setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped)) - setContentText(context.getString(R.string.learn_more)) - setSmallIcon(R.drawable.ic_ani) - setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_MANGA_URL)) - } - .build(), - ) + Notifications.CHANNEL_LIBRARY_SKIPPED, + ) { + setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped)) + setContentText(context.getString(R.string.learn_more)) + setSmallIcon(R.drawable.ic_ani) + setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_MANGA_URL)) + } } /** @@ -161,58 +158,54 @@ class MangaLibraryUpdateNotifier(private val context: Context) { */ fun showUpdateNotifications(updates: List>>) { // Parent group notification - context.notificationManager.notify( + context.notify( Notifications.ID_NEW_CHAPTERS, - context.notification(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { - setContentTitle(context.getString(R.string.notification_new_chapters)) - if (updates.size == 1 && !preferences.hideNotificationContent().get()) { - setContentText(updates.first().first.title.chop(NOTIF_MANGA_TITLE_MAX_LEN)) - } else { - setContentText( - context.resources.getQuantityString( - R.plurals.notification_new_chapters_summary, - updates.size, - updates.size, + Notifications.CHANNEL_NEW_CHAPTERS_EPISODES, + ) { + setContentTitle(context.getString(R.string.notification_new_chapters)) + if (updates.size == 1 && !preferences.hideNotificationContent().get()) { + setContentText(updates.first().first.title.chop(NOTIF_MANGA_TITLE_MAX_LEN)) + } else { + setContentText(context.resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size)) + + if (!preferences.hideNotificationContent().get()) { + setStyle( + NotificationCompat.BigTextStyle().bigText( + updates.joinToString("\n") { + it.first.title.chop(NOTIF_MANGA_TITLE_MAX_LEN) + }, ), ) - - if (!preferences.hideNotificationContent().get()) { - setStyle( - NotificationCompat.BigTextStyle().bigText( - updates.joinToString("\n") { - it.first.title.chop(NOTIF_MANGA_TITLE_MAX_LEN) - }, - ), - ) - } } + } - setSmallIcon(R.drawable.ic_ani) - setLargeIcon(notificationBitmap) + setSmallIcon(R.drawable.ic_ani) + setLargeIcon(notificationBitmap) - setGroup(Notifications.GROUP_NEW_CHAPTERS) - setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) - setGroupSummary(true) - priority = NotificationCompat.PRIORITY_HIGH + setGroup(Notifications.GROUP_NEW_CHAPTERS) + setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) + setGroupSummary(true) + priority = NotificationCompat.PRIORITY_HIGH - setContentIntent(getNotificationIntent()) - setAutoCancel(true) - }, - ) + setContentIntent(getNotificationIntent()) + setAutoCancel(true) + } // Per-manga notification if (!preferences.hideNotificationContent().get()) { launchUI { - updates.forEach { (manga, chapters) -> - context.notificationManager.notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters)) - } + context.notify( + updates.map { (manga, chapters) -> + NotificationManagerCompat.NotificationWithIdAndTag(manga.id.hashCode(), createNewChaptersNotification(manga, chapters)) + }, + ) } } } private suspend fun createNewChaptersNotification(manga: Manga, chapters: Array): Notification { val icon = getMangaIcon(manga) - return context.notification(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { + return context.notificationBuilder(Notifications.CHANNEL_NEW_CHAPTERS_EPISODES) { setContentTitle(manga.title) val description = getNewChaptersDescription(chapters) @@ -274,14 +267,14 @@ class MangaLibraryUpdateNotifier(private val context: Context) { ), ) } - } + }.build() } /** * Cancels the progress notification. */ fun cancelProgressNotification() { - context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS) + context.cancelNotification(Notifications.ID_LIBRARY_PROGRESS) } private suspend fun getMangaIcon(manga: Manga): Bitmap? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 6518eee1d3..a21e801ac1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -6,11 +6,10 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build -import androidx.core.content.ContextCompat import androidx.core.net.toUri import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.Constants -import eu.kanade.tachiyomi.data.backup.BackupRestoreService +import eu.kanade.tachiyomi.data.backup.BackupRestoreJob import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob @@ -21,6 +20,7 @@ import eu.kanade.tachiyomi.ui.player.PlayerActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.getUriCompat +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toShareIntent @@ -101,10 +101,7 @@ class NotificationReceiver : BroadcastReceiver() { "application/x-protobuf+gzip", intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1), ) - ACTION_CANCEL_RESTORE -> cancelRestore( - context, - intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1), - ) + ACTION_CANCEL_RESTORE -> cancelRestore(context) // Cancel library update and dismiss notification ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context) ACTION_CANCEL_ANIMELIB_UPDATE -> cancelAnimelibUpdate(context) @@ -174,14 +171,6 @@ class NotificationReceiver : BroadcastReceiver() { downloadEpisodes(urls, animeId) } } - // Share crash dump file - ACTION_SHARE_CRASH_LOG -> - shareFile( - context, - intent.getParcelableExtraCompat(EXTRA_URI)!!, - "text/plain", - intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1), - ) } } @@ -191,7 +180,7 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId the id of the notification */ private fun dismissNotification(context: Context, notificationId: Int) { - context.notificationManager.cancel(notificationId) + context.cancelNotification(notificationId) context.notificationManager.cancelAll() } @@ -279,11 +268,9 @@ class NotificationReceiver : BroadcastReceiver() { * Method called when user wants to stop a backup restore job. * * @param context context of application - * @param notificationId id of notification */ - private fun cancelRestore(context: Context, notificationId: Int) { - BackupRestoreService.stop(context) - ContextCompat.getMainExecutor(context).execute { dismissNotification(context, notificationId) } + private fun cancelRestore(context: Context) { + BackupRestoreJob.stop(context) } /** @@ -403,8 +390,6 @@ class NotificationReceiver : BroadcastReceiver() { private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP" - private const val ACTION_SHARE_CRASH_LOG = "$ID.$NAME.SEND_CRASH_LOG" - private const val ACTION_CANCEL_RESTORE = "$ID.$NAME.CANCEL_RESTORE" private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE" @@ -559,13 +544,13 @@ class NotificationReceiver : BroadcastReceiver() { } if (notifications.size == 2) { - context.notificationManager.cancel(groupId) + context.cancelNotification(groupId) return } } } - context.notificationManager.cancel(notificationId) + context.cancelNotification(notificationId) } /** @@ -843,23 +828,6 @@ class NotificationReceiver : BroadcastReceiver() { return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) } - /** - * Returns [PendingIntent] that starts a share activity for a crash log dump file. - * - * @param context context of application - * @param uri uri of file - * @param notificationId id of notification - * @return [PendingIntent] - */ - internal fun shareCrashLogPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent { - val intent = Intent(context, NotificationReceiver::class.java).apply { - action = ACTION_SHARE_CRASH_LOG - putExtra(EXTRA_URI, uri) - putExtra(EXTRA_NOTIFICATION_ID, notificationId) - } - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - } - /** * Returns [PendingIntent] that cancels a backup restore job. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index 5c4c4bc588..c820bd0015 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -62,12 +62,6 @@ object Notifications { const val ID_BACKUP_COMPLETE = -502 const val ID_RESTORE_COMPLETE = -504 - /** - * Notification channel used for crash log file sharing. - */ - const val CHANNEL_CRASH_LOGS = "crash_logs_channel" - const val ID_CRASH_LOGS = -601 - /** * Notification channel used for Incognito Mode */ @@ -93,6 +87,7 @@ object Notifications { "library_progress_channel", "updates_ext_channel", "downloader_cache_renewal", + "crash_logs_channel", ) /** @@ -102,12 +97,12 @@ object Notifications { * @param context The application context. */ fun createChannels(context: Context) { - val notificationService = NotificationManagerCompat.from(context) + val notificationManager = NotificationManagerCompat.from(context) // Delete old notification channels - deprecatedChannels.forEach(notificationService::deleteNotificationChannel) + deprecatedChannels.forEach(notificationManager::deleteNotificationChannel) - notificationService.createNotificationChannelGroupsCompat( + notificationManager.createNotificationChannelGroupsCompat( listOf( buildNotificationChannelGroup(GROUP_BACKUP_RESTORE) { setName(context.getString(R.string.label_backup)) @@ -124,7 +119,7 @@ object Notifications { ), ) - notificationService.createNotificationChannelsCompat( + notificationManager.createNotificationChannelsCompat( listOf( buildNotificationChannel(CHANNEL_COMMON, IMPORTANCE_LOW) { setName(context.getString(R.string.channel_common)) @@ -168,9 +163,6 @@ object Notifications { setShowBadge(false) setSound(null, null) }, - buildNotificationChannel(CHANNEL_CRASH_LOGS, IMPORTANCE_HIGH) { - setName(context.getString(R.string.channel_crash_logs)) - }, buildNotificationChannel(CHANNEL_INCOGNITO_MODE, IMPORTANCE_LOW) { setName(context.getString(R.string.pref_incognito_mode)) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt deleted file mode 100644 index 667c2061fc..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ /dev/null @@ -1,37 +0,0 @@ -package eu.kanade.tachiyomi.data.preference - -import eu.kanade.tachiyomi.R - -const val FLAG_CATEGORIES = "1" -const val FLAG_CHAPTERS = "2" -const val FLAG_HISTORY = "4" -const val FLAG_TRACK = "8" -const val FLAG_SETTINGS = "10" -const val FLAG_EXT_SETTINGS = "20" -const val FLAG_EXTENSIONS = "40" - -/** - * This class stores the values for the preferences in the application. - */ -object PreferenceValues { - - enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) { - NONE, - HORIZONTAL(shouldInvertHorizontal = true), - VERTICAL(shouldInvertVertical = true), - BOTH(shouldInvertHorizontal = true, shouldInvertVertical = true), - } - - enum class ReaderHideThreshold(val threshold: Int) { - HIGHEST(5), - HIGH(13), - LOW(31), - LOWEST(47), - } - - enum class ExtensionInstaller(val titleResId: Int) { - LEGACY(R.string.ext_installer_legacy), - PACKAGEINSTALLER(R.string.ext_installer_packageinstaller), - SHIZUKU(R.string.ext_installer_shizuku), - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index e142b52e95..f660a2b004 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -277,7 +277,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc" private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0" - private const val baseUrl = "https://shikimori.one" + private const val baseUrl = "https://shikimori.me" private const val apiUrl = "$baseUrl/api" private const val oauthUrl = "$baseUrl/oauth/token" private const val loginUrl = "$baseUrl/oauth/authorize" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt index 9b4181b1a3..9a31b1e96e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt @@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify internal class AppUpdateNotifier(private val context: Context) { @@ -24,7 +24,7 @@ internal class AppUpdateNotifier(private val context: Context) { * @param id id of the notification channel. */ private fun NotificationCompat.Builder.show(id: Int = Notifications.ID_APP_UPDATER) { - context.notificationManager.notify(id, build()) + context.notify(id, build()) } @SuppressLint("LaunchActivityFromNotification") diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt index 9cc7dd724b..2aefe50506 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateNotifier.kt @@ -5,29 +5,28 @@ import androidx.core.app.NotificationCompat import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.util.system.notification -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify class ExtensionUpdateNotifier(private val context: Context) { fun promptUpdates(names: List) { - context.notificationManager.notify( + context.notify( Notifications.ID_UPDATES_TO_EXTS, - context.notification(Notifications.CHANNEL_EXTENSIONS_UPDATE) { - setContentTitle( - context.resources.getQuantityString( - R.plurals.update_check_notification_ext_updates, - names.size, - names.size, - ), - ) - val extNames = names.joinToString(", ") - setContentText(extNames) - setStyle(NotificationCompat.BigTextStyle().bigText(extNames)) - setSmallIcon(R.drawable.ic_extension_24dp) - setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context)) - setAutoCancel(true) - }, - ) + Notifications.CHANNEL_EXTENSIONS_UPDATE, + ) { + setContentTitle( + context.resources.getQuantityString( + R.plurals.update_check_notification_ext_updates, + names.size, + names.size, + ), + ) + val extNames = names.joinToString(", ") + setContentText(extNames) + setStyle(NotificationCompat.BigTextStyle().bigText(extNames)) + setSmallIcon(R.drawable.ic_extension_24dp) + setContentIntent(NotificationReceiver.openExtensionsPendingActivity(context)) + setAutoCancel(true) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt index f08af91d9c..000f4f35bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallService.kt @@ -5,9 +5,9 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.IBinder +import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime import eu.kanade.tachiyomi.extension.anime.installer.PackageInstallerInstallerAnime import eu.kanade.tachiyomi.extension.anime.installer.ShizukuInstallerAnime @@ -36,7 +36,7 @@ class AnimeExtensionInstallService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val uri = intent?.data val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L } - val installerUsed = intent?.getSerializableExtraCompat( + val installerUsed = intent?.getSerializableExtraCompat( EXTRA_INSTALLER, ) if (uri == null || id == null || installerUsed == null) { @@ -46,8 +46,8 @@ class AnimeExtensionInstallService : Service() { if (installer == null) { installer = when (installerUsed) { - PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(this) - PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstallerAnime(this) + BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerAnime(this) + BasePreferences.ExtensionInstaller.SHIZUKU -> ShizukuInstallerAnime(this) else -> { logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" } stopSelf() @@ -73,7 +73,7 @@ class AnimeExtensionInstallService : Service() { context: Context, downloadId: Long, uri: Uri, - installer: PreferenceValues.ExtensionInstaller, + installer: BasePreferences.ExtensionInstaller, ): Intent { return Intent(context, AnimeExtensionInstallService::class.java) .setDataAndType(uri, AnimeExtensionInstaller.APK_MIME) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt index b1610d87aa..fc555129c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstaller.kt @@ -12,7 +12,6 @@ import androidx.core.content.getSystemService import androidx.core.net.toUri import com.jakewharton.rxrelay.PublishRelay import eu.kanade.domain.base.BasePreferences -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.anime.installer.InstallerAnime import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension @@ -134,7 +133,7 @@ internal class AnimeExtensionInstaller(private val context: Context) { */ fun installApk(downloadId: Long, uri: Uri) { when (val installer = extensionInstaller.get()) { - PreferenceValues.ExtensionInstaller.LEGACY -> { + BasePreferences.ExtensionInstaller.LEGACY -> { val intent = Intent(context, AnimeExtensionInstallActivity::class.java) .setDataAndType(uri, APK_MIME) .putExtra(EXTRA_DOWNLOAD_ID, downloadId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt index 4568a8f2b1..e3c0bc0b60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallService.kt @@ -5,9 +5,9 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.IBinder +import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga import eu.kanade.tachiyomi.extension.manga.installer.PackageInstallerInstallerManga import eu.kanade.tachiyomi.extension.manga.installer.ShizukuInstallerManga @@ -36,7 +36,7 @@ class MangaExtensionInstallService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val uri = intent?.data val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L } - val installerUsed = intent?.getSerializableExtraCompat( + val installerUsed = intent?.getSerializableExtraCompat( EXTRA_INSTALLER, ) if (uri == null || id == null || installerUsed == null) { @@ -46,8 +46,8 @@ class MangaExtensionInstallService : Service() { if (installer == null) { installer = when (installerUsed) { - PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerManga(this) - PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstallerManga(this) + BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerManga(this) + BasePreferences.ExtensionInstaller.SHIZUKU -> ShizukuInstallerManga(this) else -> { logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" } stopSelf() @@ -73,7 +73,7 @@ class MangaExtensionInstallService : Service() { context: Context, downloadId: Long, uri: Uri, - installer: PreferenceValues.ExtensionInstaller, + installer: BasePreferences.ExtensionInstaller, ): Intent { return Intent(context, MangaExtensionInstallService::class.java) .setDataAndType(uri, MangaExtensionInstaller.APK_MIME) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt index e95f3eca67..88b2b42201 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstaller.kt @@ -12,7 +12,6 @@ import androidx.core.content.getSystemService import androidx.core.net.toUri import com.jakewharton.rxrelay.PublishRelay import eu.kanade.domain.base.BasePreferences -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.extension.InstallStep import eu.kanade.tachiyomi.extension.manga.installer.InstallerManga import eu.kanade.tachiyomi.extension.manga.model.MangaExtension @@ -134,7 +133,7 @@ internal class MangaExtensionInstaller(private val context: Context) { */ fun installApk(downloadId: Long, uri: Uri) { when (val installer = extensionInstaller.get()) { - PreferenceValues.ExtensionInstaller.LEGACY -> { + BasePreferences.ExtensionInstaller.LEGACY -> { val intent = Intent(context, MangaExtensionInstallActivity::class.java) .setDataAndType(uri, APK_MIME) .putExtra(EXTRA_DOWNLOAD_ID, downloadId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt index 138e66cbdc..a359d76566 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/anime/AnimeSourceExtensions.kt @@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.animesource.AnimeSource import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import tachiyomi.domain.source.anime.model.AnimeSourceData import tachiyomi.domain.source.anime.model.StubAnimeSource -import tachiyomi.source.local.entries.anime.LocalAnimeSource +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -31,6 +31,4 @@ fun AnimeSource.getNameForAnimeInfo(): String { } } -fun AnimeSource.isLocal(): Boolean = id == LocalAnimeSource.ID - fun AnimeSource.isLocalOrStub(): Boolean = isLocal() || this is StubAnimeSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt index c8d0794f61..afd17ceaa5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/manga/MangaSourceExtensions.kt @@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.source.MangaSource import tachiyomi.domain.source.manga.model.MangaSourceData import tachiyomi.domain.source.manga.model.StubMangaSource -import tachiyomi.source.local.entries.manga.LocalMangaSource +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -31,6 +31,4 @@ fun MangaSource.getNameForMangaInfo(): String { } } -fun MangaSource.isLocal(): Boolean = id == LocalMangaSource.ID - fun MangaSource.isLocalOrStub(): Boolean = isLocal() || this is StubMangaSource diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt index 178a8704f4..02589cec98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt @@ -97,7 +97,7 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS || secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode } - .onEach { activity.window.setSecureScreen(it) } + .onEach(activity.window::setSecureScreen) .launchIn(activity.lifecycleScope) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt index bcf3441a76..0463229369 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt @@ -74,6 +74,6 @@ class ThemingDelegateImpl : ThemingDelegate { override fun applyAppTheme(activity: Activity) { val uiPreferences = Injekt.get() ThemingDelegate.getThemeResIds(uiPreferences.appTheme().get(), uiPreferences.themeDarkAmoled().get()) - .forEach { activity.setTheme(it) } + .forEach(activity::setTheme) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionFilterScreen.kt index 2985a8c673..06180285f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionFilterScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionFilterScreen.kt @@ -34,7 +34,7 @@ class AnimeExtensionFilterScreen : Screen() { AnimeExtensionFilterScreen( navigateUp = navigator::pop, state = successState, - onClickToggle = { screenModel.toggle(it) }, + onClickToggle = screenModel::toggle, ) LaunchedEffect(Unit) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt index 8288643043..851bac08f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt @@ -142,7 +142,7 @@ class AnimeExtensionsScreenModel( else -> it.extension } } - .forEach { updateExtension(it) } + .forEach(::updateExtension) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreen.kt index cdb961217b..1e48494c79 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/details/AnimeExtensionDetailsScreen.kt @@ -40,9 +40,9 @@ data class AnimeExtensionDetailsScreen( onClickReadme = { uriHandler.openUri(screenModel.getReadmeUrl()) }, onClickEnableAll = { screenModel.toggleSources(true) }, onClickDisableAll = { screenModel.toggleSources(false) }, - onClickClearCookies = { screenModel.clearCookies() }, - onClickUninstall = { screenModel.uninstallExtension() }, - onClickSource = { screenModel.toggleSource(it) }, + onClickClearCookies = screenModel::clearCookies, + onClickUninstall = screenModel::uninstallExtension, + onClickSource = screenModel::toggleSource, ) LaunchedEffect(Unit) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/AnimeSourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/AnimeSourceSearchScreen.kt index 1ebdcea514..3238f45468 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/AnimeSourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/AnimeSourceSearchScreen.kt @@ -58,7 +58,7 @@ data class AnimeSourceSearchScreen( searchQuery = state.toolbarQuery ?: "", onChangeSearchQuery = screenModel::setToolbarQuery, onClickCloseSearch = navigator::pop, - onSearch = { screenModel.search(it) }, + onSearch = screenModel::search, scrollBehavior = scrollBehavior, ) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeDialog.kt index e1e3b79bfe..64592181fb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeDialog.kt @@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.anime.migration.search import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -23,6 +25,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import cafe.adriel.voyager.core.model.StateScreenModel import eu.kanade.domain.entries.anime.interactor.UpdateAnime @@ -112,7 +115,9 @@ internal fun MigrateAnimeDialog( } }, confirmButton = { - Row { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { TextButton( onClick = { onClickTitle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeSearchScreen.kt index 1abb2eb6bd..f04b83a56b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/migration/search/MigrateAnimeSearchScreen.kt @@ -22,9 +22,7 @@ class MigrateAnimeSearchScreen(private val animeId: Long) : Screen() { MigrateAnimeSearchScreen( navigateUp = navigator::pop, state = state, - getAnime = { source, anime -> - screenModel.getAnime(source = source, initialAnime = anime) - }, + getAnime = { screenModel.getAnime(it) }, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = screenModel::search, onClickSource = { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt index b8f559fe62..ecfcbf93e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreen.kt @@ -126,7 +126,7 @@ data class BrowseAnimeSourceScreen( onWebViewClick = onWebViewClick, onHelpClick = onHelpClick, onSettingsClick = { navigator.push(SourcePreferencesScreen(sourceId)) }, - onSearch = { screenModel.search(it) }, + onSearch = screenModel::search, ) Row( @@ -235,15 +235,9 @@ data class BrowseAnimeSourceScreen( SourceFilterAnimeDialog( onDismissRequest = onDismissRequest, filters = state.filters, - onReset = { - screenModel.resetFilters() - }, - onFilter = { - screenModel.search(filters = state.filters) - }, - onUpdate = { - screenModel.setFilters(it) - }, + onReset = screenModel::resetFilters, + onFilter = { screenModel.search(filters = state.filters) }, + onUpdate = screenModel::setFilters, ) } is BrowseAnimeSourceScreenModel.Dialog.AddDuplicateAnime -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt index 554605ff63..bc8627e320 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/BrowseAnimeSourceScreenModel.kt @@ -15,12 +15,11 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.core.preference.asState import eu.kanade.domain.entries.anime.interactor.UpdateAnime -import eu.kanade.domain.entries.anime.model.copyFrom import eu.kanade.domain.entries.anime.model.toDomainAnime -import eu.kanade.domain.entries.anime.model.toSAnime import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.track.anime.model.toDomainTrack +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.data.cache.AnimeCoverCache @@ -36,7 +35,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -44,12 +42,11 @@ import logcat.LogPriority import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.util.lang.launchIO -import tachiyomi.core.util.lang.withIOContext -import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.anime.interactor.GetAnimeCategories import tachiyomi.domain.category.anime.interactor.SetAnimeCategories import tachiyomi.domain.category.model.Category +import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime import tachiyomi.domain.entries.anime.model.Anime @@ -78,6 +75,7 @@ class BrowseAnimeSourceScreenModel( private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(), private val setAnimeCategories: SetAnimeCategories = Injekt.get(), private val setAnimeDefaultEpisodeFlags: SetAnimeDefaultEpisodeFlags = Injekt.get(), + private val getAnime: GetAnime = Injekt.get(), private val networkToLocalAnime: NetworkToLocalAnime = Injekt.get(), private val updateAnime: UpdateAnime = Injekt.get(), private val insertTrack: InsertAnimeTrack = Injekt.get(), @@ -121,23 +119,21 @@ class BrowseAnimeSourceScreenModel( ) { getRemoteAnime.subscribe(sourceId, listing.query ?: "", listing.filters) }.flow.map { pagingData -> - pagingData - .map { - flow { - val localAnime = withIOContext { networkToLocalAnime.await(it.toDomainAnime(sourceId)) } - emit(localAnime) + pagingData.map { + networkToLocalAnime.await(it.toDomainAnime(sourceId)) + .let { localAnime -> + getAnime.subscribe(localAnime.url, localAnime.source) } - .filterNotNull() - .filter { - !sourcePreferences.hideInAnimeLibraryItems().get() || !it.favorite - } - .onEach(::initializeAnime) - .stateIn(coroutineScope) - } + .filterNotNull() + .filter { localAnime -> + !sourcePreferences.hideInAnimeLibraryItems().get() || !localAnime.favorite + } + .stateIn(ioCoroutineScope) + } } - .cachedIn(coroutineScope) + .cachedIn(ioCoroutineScope) } - .stateIn(coroutineScope, SharingStarted.Lazily, emptyFlow()) + .stateIn(ioCoroutineScope, SharingStarted.Lazily, emptyFlow()) fun getColumnsPreference(orientation: Int): GridCells { val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE @@ -230,26 +226,6 @@ class BrowseAnimeSourceScreenModel( } } - /** - * Initialize an anime. - * - * @return anime to initialize. - */ - private suspend fun initializeAnime(anime: Anime) { - if (anime.thumbnailUrl != null || anime.initialized) return - withNonCancellableContext { - try { - val networkAnime = source.getAnimeDetails(anime.toSAnime()) - val updatedAnime = anime.copyFrom(networkAnime) - .copy(initialized = true) - - updateAnime.await(updatedAnime.toAnimeUpdate()) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - } - /** * Adds or removes an anime from the library. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt index 5b9d5a37c3..a69fb910f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt @@ -4,12 +4,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import cafe.adriel.voyager.core.model.StateScreenModel -import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.entries.anime.interactor.UpdateAnime -import eu.kanade.domain.entries.anime.model.copyFrom import eu.kanade.domain.entries.anime.model.toDomainAnime -import eu.kanade.domain.entries.anime.model.toSAnime import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager import kotlinx.coroutines.asCoroutineDispatcher @@ -18,15 +16,10 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import logcat.LogPriority import tachiyomi.core.util.lang.awaitSingle -import tachiyomi.core.util.lang.withIOContext -import tachiyomi.core.util.lang.withNonCancellableContext -import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime import tachiyomi.domain.entries.anime.model.Anime -import tachiyomi.domain.entries.anime.model.toAnimeUpdate import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.Executors @@ -57,43 +50,19 @@ abstract class AnimeSearchScreenModel( } @Composable - fun getAnime(source: AnimeCatalogueSource, initialAnime: Anime): State { + fun getAnime(initialAnime: Anime): State { return produceState(initialValue = initialAnime) { getAnime.subscribe(initialAnime.url, initialAnime.source) .collectLatest { anime -> if (anime == null) return@collectLatest - withIOContext { - initializeAnime(source, anime) - } value = anime } } } - /** - * Initialize a anime. - * - * @param source to interact with - * @param anime to initialize. - */ - private suspend fun initializeAnime(source: AnimeCatalogueSource, anime: Anime) { - if (anime.thumbnailUrl != null || anime.initialized) return - withNonCancellableContext { - try { - val networkAnime = source.getAnimeDetails(anime.toSAnime()) - val updatedAnime = anime.copyFrom(networkAnime) - .copy(initialized = true) - - updateAnime.await(updatedAnime.toAnimeUpdate()) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - } - abstract fun getEnabledSources(): List - fun getSelectedSources(): List { + private fun getSelectedSources(): List { val filter = extensionFilter val enabledSources = getEnabledSources() @@ -124,7 +93,7 @@ abstract class AnimeSearchScreenModel( abstract fun getItems(): Map - fun getAndUpdateItems(function: (Map) -> Map) { + private fun getAndUpdateItems(function: (Map) -> Map) { updateItems(function(getItems())) } @@ -136,7 +105,7 @@ abstract class AnimeSearchScreenModel( val initialItems = getSelectedSources().associateWith { AnimeSearchItemResult.Loading } updateItems(initialItems) - coroutineScope.launch { + ioCoroutineScope.launch { sources .map { source -> async { @@ -145,10 +114,8 @@ abstract class AnimeSearchScreenModel( source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle() } - val titles = withIOContext { - page.animes.map { - networkToLocalAnime.await(it.toDomainAnime(source.id)) - } + val titles = page.animes.map { + networkToLocalAnime.await(it.toDomainAnime(source.id)) } getAndUpdateItems { items -> @@ -159,7 +126,7 @@ abstract class AnimeSearchScreenModel( } catch (e: Exception) { getAndUpdateItems { items -> val mutableMap = items.toMutableMap() - mutableMap[source] = AnimeSearchItemResult.Error(throwable = e) + mutableMap[source] = AnimeSearchItemResult.Error(e) mutableMap.toSortedMap(sortComparator(mutableMap)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/GlobalAnimeSearchScreen.kt index 8014b30837..3f211d35e1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/GlobalAnimeSearchScreen.kt @@ -1,8 +1,12 @@ package eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -10,10 +14,11 @@ import eu.kanade.presentation.browse.anime.GlobalAnimeSearchScreen import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.browse.anime.source.browse.BrowseAnimeSourceScreen import eu.kanade.tachiyomi.ui.entries.anime.AnimeScreen +import tachiyomi.presentation.core.screens.LoadingScreen class GlobalAnimeSearchScreen( val searchQuery: String = "", - val extensionFilter: String = "", + private val extensionFilter: String = "", ) : Screen() { @Composable @@ -27,26 +32,44 @@ class GlobalAnimeSearchScreen( ) } val state by screenModel.state.collectAsState() + var showSingleLoadingScreen by remember { + mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1) + } + + if (showSingleLoadingScreen) { + LoadingScreen() - GlobalAnimeSearchScreen( - state = state, - navigateUp = navigator::pop, - onChangeSearchQuery = screenModel::updateSearchQuery, - onSearch = screenModel::search, - getAnime = { source, anime -> - screenModel.getAnime( - source = source, - initialAnime = anime, - ) - }, - onClickSource = { - if (!screenModel.incognitoMode.get()) { - screenModel.lastUsedSourceId.set(it.id) + LaunchedEffect(state.items) { + when (val result = state.items.values.singleOrNull()) { + AnimeSearchItemResult.Loading -> return@LaunchedEffect + is AnimeSearchItemResult.Success -> { + val anime = result.result.singleOrNull() + if (anime != null) { + navigator.replace(AnimeScreen(anime.id, true)) + } else { + // Backoff to result screen + showSingleLoadingScreen = false + } + } + else -> showSingleLoadingScreen = false } - navigator.push(BrowseAnimeSourceScreen(it.id, state.searchQuery)) - }, - onClickItem = { navigator.push(AnimeScreen(it.id, true)) }, - onLongClickItem = { navigator.push(AnimeScreen(it.id, true)) }, - ) + } + } else { + GlobalAnimeSearchScreen( + state = state, + navigateUp = navigator::pop, + onChangeSearchQuery = screenModel::updateSearchQuery, + onSearch = screenModel::search, + getAnime = { screenModel.getAnime(it) }, + onClickSource = { + if (!screenModel.incognitoMode.get()) { + screenModel.lastUsedSourceId.set(it.id) + } + navigator.push(BrowseAnimeSourceScreen(it.id, state.searchQuery)) + }, + onClickItem = { navigator.push(AnimeScreen(it.id, true)) }, + onLongClickItem = { navigator.push(AnimeScreen(it.id, true)) }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionFilterScreen.kt index dce3acd254..ef21c39c44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionFilterScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionFilterScreen.kt @@ -34,7 +34,7 @@ class MangaExtensionFilterScreen : Screen() { MangaExtensionFilterScreen( navigateUp = navigator::pop, state = successState, - onClickToggle = { screenModel.toggle(it) }, + onClickToggle = screenModel::toggle, ) LaunchedEffect(Unit) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt index 105b81007a..94736ab69e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt @@ -144,7 +144,7 @@ class MangaExtensionsScreenModel( else -> it.extension } } - .forEach { updateExtension(it) } + .forEach(::updateExtension) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreen.kt index 800be34723..5fc1ac1cfd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/details/MangaExtensionDetailsScreen.kt @@ -40,9 +40,9 @@ data class MangaExtensionDetailsScreen( onClickReadme = { uriHandler.openUri(screenModel.getReadmeUrl()) }, onClickEnableAll = { screenModel.toggleSources(true) }, onClickDisableAll = { screenModel.toggleSources(false) }, - onClickClearCookies = { screenModel.clearCookies() }, - onClickUninstall = { screenModel.uninstallExtension() }, - onClickSource = { screenModel.toggleSource(it) }, + onClickClearCookies = screenModel::clearCookies, + onClickUninstall = screenModel::uninstallExtension, + onClickSource = screenModel::toggleSource, ) LaunchedEffect(Unit) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MangaSourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MangaSourceSearchScreen.kt index c97b1f8338..2896e323a5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MangaSourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MangaSourceSearchScreen.kt @@ -58,7 +58,7 @@ data class MangaSourceSearchScreen( searchQuery = state.toolbarQuery ?: "", onChangeSearchQuery = screenModel::setToolbarQuery, onClickCloseSearch = navigator::pop, - onSearch = { screenModel.search(it) }, + onSearch = screenModel::search, scrollBehavior = scrollBehavior, ) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaDialog.kt index 3e6aa43c53..73e84908a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaDialog.kt @@ -2,7 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.manga.migration.search import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -23,6 +25,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEachIndexed import cafe.adriel.voyager.core.model.StateScreenModel import eu.kanade.domain.entries.manga.interactor.UpdateManga @@ -111,7 +114,9 @@ internal fun MigrateMangaDialog( } }, confirmButton = { - Row { + FlowRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { TextButton( onClick = { onClickTitle() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaSearchScreen.kt index 9d6de70c5c..153d17dffb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/migration/search/MigrateMangaSearchScreen.kt @@ -22,9 +22,7 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() { MigrateMangaSearchScreen( navigateUp = navigator::pop, state = state, - getManga = { source, manga -> - screenModel.getManga(source = source, initialManga = manga) - }, + getManga = { screenModel.getManga(it) }, onChangeSearchQuery = screenModel::updateSearchQuery, onSearch = screenModel::search, onClickSource = { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt index b55df06009..b5e6304b80 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreen.kt @@ -126,7 +126,7 @@ data class BrowseMangaSourceScreen( onWebViewClick = onWebViewClick, onHelpClick = onHelpClick, onSettingsClick = { navigator.push(MangaSourcePreferencesScreen(sourceId)) }, - onSearch = { screenModel.search(it) }, + onSearch = screenModel::search, ) Row( @@ -235,15 +235,9 @@ data class BrowseMangaSourceScreen( SourceFilterMangaDialog( onDismissRequest = onDismissRequest, filters = state.filters, - onReset = { - screenModel.resetFilters() - }, - onFilter = { - screenModel.search(filters = state.filters) - }, - onUpdate = { - screenModel.setFilters(it) - }, + onReset = screenModel::resetFilters, + onFilter = { screenModel.search(filters = state.filters) }, + onUpdate = screenModel::setFilters, ) } is BrowseMangaSourceScreenModel.Dialog.AddDuplicateManga -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt index 79be335626..a19b2c9b09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/BrowseMangaSourceScreenModel.kt @@ -15,12 +15,11 @@ import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.core.preference.asState import eu.kanade.domain.entries.manga.interactor.UpdateManga -import eu.kanade.domain.entries.manga.model.copyFrom import eu.kanade.domain.entries.manga.model.toDomainManga -import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.track.manga.model.toDomainTrack +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.data.track.EnhancedMangaTrackService import eu.kanade.tachiyomi.data.track.TrackManager @@ -36,7 +35,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -44,13 +42,12 @@ import logcat.LogPriority import tachiyomi.core.preference.CheckboxState import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.util.lang.launchIO -import tachiyomi.core.util.lang.withIOContext -import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.category.manga.interactor.SetMangaCategories import tachiyomi.domain.category.model.Category import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga +import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.toMangaUpdate @@ -78,6 +75,7 @@ class BrowseMangaSourceScreenModel( private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), + private val getManga: GetManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(), private val insertTrack: InsertMangaTrack = Injekt.get(), @@ -121,23 +119,21 @@ class BrowseMangaSourceScreenModel( ) { getRemoteManga.subscribe(sourceId, listing.query ?: "", listing.filters) }.flow.map { pagingData -> - pagingData - .map { - flow { - val localManga = withIOContext { networkToLocalManga.await(it.toDomainManga(sourceId)) } - emit(localManga) + pagingData.map { + networkToLocalManga.await(it.toDomainManga(sourceId)) + .let { localManga -> + getManga.subscribe(localManga.url, localManga.source) } - .filterNotNull() - .filter { - !sourcePreferences.hideInMangaLibraryItems().get() || !it.favorite - } - .onEach(::initializeManga) - .stateIn(coroutineScope) - } + .filterNotNull() + .filter { localManga -> + !sourcePreferences.hideInMangaLibraryItems().get() || !localManga.favorite + } + .stateIn(ioCoroutineScope) + } } - .cachedIn(coroutineScope) + .cachedIn(ioCoroutineScope) } - .stateIn(coroutineScope, SharingStarted.Lazily, emptyFlow()) + .stateIn(ioCoroutineScope, SharingStarted.Lazily, emptyFlow()) fun getColumnsPreference(orientation: Int): GridCells { val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE @@ -231,26 +227,6 @@ class BrowseMangaSourceScreenModel( } } - /** - * Initialize a manga. - * - * @param manga to initialize. - */ - private suspend fun initializeManga(manga: Manga) { - if (manga.thumbnailUrl != null || manga.initialized) return - withNonCancellableContext { - try { - val networkManga = source.getMangaDetails(manga.toSManga()) - val updatedManga = manga.copyFrom(networkManga) - .copy(initialized = true) - - updateManga.await(updatedManga.toMangaUpdate()) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - } - /** * Adds or removes a manga from the library. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/GlobalMangaSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/GlobalMangaSearchScreen.kt index 7836dd65e9..1570652a51 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/GlobalMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/GlobalMangaSearchScreen.kt @@ -1,8 +1,12 @@ package eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -10,10 +14,11 @@ import eu.kanade.presentation.browse.manga.GlobalMangaSearchScreen import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.browse.manga.source.browse.BrowseMangaSourceScreen import eu.kanade.tachiyomi.ui.entries.manga.MangaScreen +import tachiyomi.presentation.core.screens.LoadingScreen class GlobalMangaSearchScreen( val searchQuery: String = "", - val extensionFilter: String = "", + private val extensionFilter: String = "", ) : Screen() { @Composable @@ -27,26 +32,44 @@ class GlobalMangaSearchScreen( ) } val state by screenModel.state.collectAsState() + var showSingleLoadingScreen by remember { + mutableStateOf(searchQuery.isNotEmpty() && extensionFilter.isNotEmpty() && state.total == 1) + } + + if (showSingleLoadingScreen) { + LoadingScreen() - GlobalMangaSearchScreen( - state = state, - navigateUp = navigator::pop, - onChangeSearchQuery = screenModel::updateSearchQuery, - onSearch = screenModel::search, - getManga = { source, manga -> - screenModel.getManga( - source = source, - initialManga = manga, - ) - }, - onClickSource = { - if (!screenModel.incognitoMode.get()) { - screenModel.lastUsedSourceId.set(it.id) + LaunchedEffect(state.items) { + when (val result = state.items.values.singleOrNull()) { + MangaSearchItemResult.Loading -> return@LaunchedEffect + is MangaSearchItemResult.Success -> { + val manga = result.result.singleOrNull() + if (manga != null) { + navigator.replace(MangaScreen(manga.id, true)) + } else { + // Backoff to result screen + showSingleLoadingScreen = false + } + } + else -> showSingleLoadingScreen = false } - navigator.push(BrowseMangaSourceScreen(it.id, state.searchQuery)) - }, - onClickItem = { navigator.push(MangaScreen(it.id, true)) }, - onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, - ) + } + } else { + GlobalMangaSearchScreen( + state = state, + navigateUp = navigator::pop, + onChangeSearchQuery = screenModel::updateSearchQuery, + onSearch = screenModel::search, + getManga = { screenModel.getManga(it) }, + onClickSource = { + if (!screenModel.incognitoMode.get()) { + screenModel.lastUsedSourceId.set(it.id) + } + navigator.push(BrowseMangaSourceScreen(it.id, state.searchQuery)) + }, + onClickItem = { navigator.push(MangaScreen(it.id, true)) }, + onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, + ) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt index b5a19805c8..ef7373e563 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt @@ -4,12 +4,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.produceState import cafe.adriel.voyager.core.model.StateScreenModel -import cafe.adriel.voyager.core.model.coroutineScope import eu.kanade.domain.entries.manga.interactor.UpdateManga -import eu.kanade.domain.entries.manga.model.copyFrom import eu.kanade.domain.entries.manga.model.toDomainManga -import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager import eu.kanade.tachiyomi.source.CatalogueSource import kotlinx.coroutines.asCoroutineDispatcher @@ -18,15 +16,10 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import logcat.LogPriority import tachiyomi.core.util.lang.awaitSingle -import tachiyomi.core.util.lang.withIOContext -import tachiyomi.core.util.lang.withNonCancellableContext -import tachiyomi.core.util.system.logcat import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.model.Manga -import tachiyomi.domain.entries.manga.model.toMangaUpdate import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.concurrent.Executors @@ -57,43 +50,19 @@ abstract class MangaSearchScreenModel( } @Composable - fun getManga(source: CatalogueSource, initialManga: Manga): State { + fun getManga(initialManga: Manga): State { return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) .collectLatest { manga -> if (manga == null) return@collectLatest - withIOContext { - initializeManga(source, manga) - } value = manga } } } - /** - * Initialize a manga. - * - * @param source to interact with - * @param manga to initialize. - */ - private suspend fun initializeManga(source: CatalogueSource, manga: Manga) { - if (manga.thumbnailUrl != null || manga.initialized) return - withNonCancellableContext { - try { - val networkManga = source.getMangaDetails(manga.toSManga()) - val updatedManga = manga.copyFrom(networkManga) - .copy(initialized = true) - - updateManga.await(updatedManga.toMangaUpdate()) - } catch (e: Exception) { - logcat(LogPriority.ERROR, e) - } - } - } - abstract fun getEnabledSources(): List - fun getSelectedSources(): List { + private fun getSelectedSources(): List { val filter = extensionFilter val enabledSources = getEnabledSources() @@ -124,7 +93,7 @@ abstract class MangaSearchScreenModel( abstract fun getItems(): Map - fun getAndUpdateItems(function: (Map) -> Map) { + private fun getAndUpdateItems(function: (Map) -> Map) { updateItems(function(getItems())) } @@ -136,7 +105,7 @@ abstract class MangaSearchScreenModel( val initialItems = getSelectedSources().associateWith { MangaSearchItemResult.Loading } updateItems(initialItems) - coroutineScope.launch { + ioCoroutineScope.launch { sources .map { source -> async { @@ -145,10 +114,8 @@ abstract class MangaSearchScreenModel( source.fetchSearchManga(1, query, source.getFilterList()).awaitSingle() } - val titles = withIOContext { - page.mangas.map { - networkToLocalManga.await(it.toDomainManga(source.id)) - } + val titles = page.mangas.map { + networkToLocalManga.await(it.toDomainManga(source.id)) } getAndUpdateItems { items -> @@ -159,7 +126,7 @@ abstract class MangaSearchScreenModel( } catch (e: Exception) { getAndUpdateItems { items -> val mutableMap = items.toMutableMap() - mutableMap[source] = MangaSearchItemResult.Error(throwable = e) + mutableMap[source] = MangaSearchItemResult.Error(e) mutableMap.toSortedMap(sortComparator(mutableMap)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index 22bfce6e8e..f6caa932a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -13,7 +13,6 @@ import eu.kanade.core.util.addOrRemove import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags import eu.kanade.domain.entries.anime.interactor.UpdateAnime import eu.kanade.domain.entries.anime.model.downloadedFilter -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.domain.entries.anime.model.toSAnime import eu.kanade.domain.items.episode.interactor.SetSeenStatus import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource @@ -75,6 +74,7 @@ import tachiyomi.domain.items.episode.service.getEpisodeSort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.domain.track.anime.interactor.GetAnimeTracks +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.DecimalFormat @@ -982,6 +982,14 @@ class AnimeInfoScreenModel( } } + private val Throwable.snackbarMessage: String + get() = when (val className = this::class.simpleName) { + null -> message ?: "" + "SourceNotInstalledException" -> context.getString(R.string.loader_not_implemented_error) + "Exception", "HttpException", "IOException" -> message ?: className + else -> "$className: $message" + } + fun showAnimeSkipIntroDialog() { mutableState.update { state -> when (state) { @@ -1066,10 +1074,3 @@ val episodeDecimalFormat = DecimalFormat( DecimalFormatSymbols() .apply { decimalSeparator = '.' }, ) - -private val Throwable.snackbarMessage: String - get() = when (val className = this::class.simpleName) { - null -> message ?: "" - "Exception", "HttpException", "IOException", "SourceNotInstalledException" -> message ?: className - else -> "$className: $message" - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index 2ba4d6bd3f..71ba06edb8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -12,7 +12,6 @@ import eu.kanade.core.preference.asState import eu.kanade.core.util.addOrRemove import eu.kanade.domain.entries.manga.interactor.UpdateManga import eu.kanade.domain.entries.manga.model.downloadedFilter -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.domain.items.chapter.interactor.SetReadStatus import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource @@ -72,6 +71,7 @@ import tachiyomi.domain.items.chapter.service.getChapterSort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.domain.track.manga.interactor.GetMangaTracks +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.DecimalFormat @@ -972,6 +972,14 @@ class MangaInfoScreenModel( } } } + + private val Throwable.snackbarMessage: String + get() = when (val className = this::class.simpleName) { + null -> message ?: "" + "SourceNotInstalledException" -> context.getString(R.string.loader_not_implemented_error) + "Exception", "HttpException", "IOException" -> message ?: className + else -> "$className: $message" + } } sealed class MangaScreenState { @@ -1032,10 +1040,3 @@ val chapterDecimalFormat = DecimalFormat( DecimalFormatSymbols() .apply { decimalSeparator = '.' }, ) - -private val Throwable.snackbarMessage: String - get() = when (val className = this::class.simpleName) { - null -> message ?: "" - "Exception", "HttpException", "IOException", "SourceNotInstalledException" -> message ?: className - else -> "$className: $message" - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt index 92f69849b2..c71bc725d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt @@ -16,7 +16,6 @@ import eu.kanade.core.util.fastMapNotNull import eu.kanade.core.util.fastPartition import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.entries.anime.interactor.UpdateAnime -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.domain.items.episode.interactor.SetSeenStatus import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS import eu.kanade.presentation.entries.DownloadAction @@ -62,6 +61,7 @@ import tachiyomi.domain.library.anime.model.sort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.Collator @@ -424,7 +424,6 @@ class AnimeLibraryScreenModel( DownloadAction.NEXT_10_ITEMS -> downloadUnseenEpisodes(animes, 10) DownloadAction.NEXT_25_ITEMS -> downloadUnseenEpisodes(animes, 25) DownloadAction.UNVIEWED_ITEMS -> downloadUnseenEpisodes(animes, null) - else -> {} } clearSelection() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt index fe1dc0194e..d3ab376560 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryTab.kt @@ -31,7 +31,6 @@ 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.entries.anime.model.isLocal import eu.kanade.presentation.category.ChangeCategoryDialog import eu.kanade.presentation.entries.LibraryBottomActionMenu import eu.kanade.presentation.library.DeleteLibraryEntryDialog @@ -64,6 +63,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.injectLazy object AnimeLibraryTab : Tab { @@ -150,7 +150,7 @@ object AnimeLibraryTab : Tab { onClickUnselectAll = screenModel::clearSelection, onClickSelectAll = { screenModel.selectAll(screenModel.activeCategoryIndex) }, onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategoryIndex) }, - onClickFilter = { screenModel.showSettingsDialog() }, + onClickFilter = screenModel::showSettingsDialog, onClickRefresh = { onClickRefresh(state.categories[screenModel.activeCategoryIndex]) }, onClickGlobalUpdate = { onClickRefresh(null) }, onClickOpenRandomEntry = { @@ -216,7 +216,7 @@ object AnimeLibraryTab : Tab { } Unit }.takeIf { state.showAnimeContinueButton }, - onToggleSelection = { screenModel.toggleSelection(it) }, + onToggleSelection = screenModel::toggleSelection, onToggleRangeSelection = { screenModel.toggleRangeSelection(it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) @@ -276,7 +276,7 @@ object AnimeLibraryTab : Tab { } LaunchedEffect(state.selectionMode, state.dialog) { - HomeScreen.showBottomNav(!state.selectionMode && state.dialog !is AnimeLibraryScreenModel.Dialog.SettingsSheet) + HomeScreen.showBottomNav(!state.selectionMode) } LaunchedEffect(state.isLoading) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt index 8e1d99c75b..19483eabd8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt @@ -16,7 +16,6 @@ import eu.kanade.core.util.fastMapNotNull import eu.kanade.core.util.fastPartition import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.entries.manga.interactor.UpdateManga -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.domain.items.chapter.interactor.SetReadStatus import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS import eu.kanade.presentation.entries.DownloadAction @@ -62,6 +61,7 @@ import tachiyomi.domain.library.manga.model.sort import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.domain.track.manga.interactor.GetTracksPerManga +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.text.Collator @@ -418,7 +418,6 @@ class MangaLibraryScreenModel( DownloadAction.NEXT_10_ITEMS -> downloadUnreadChapters(mangas, 10) DownloadAction.NEXT_25_ITEMS -> downloadUnreadChapters(mangas, 25) DownloadAction.UNVIEWED_ITEMS -> downloadUnreadChapters(mangas, null) - else -> {} } clearSelection() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt index e2f95759a4..6d7bda0f26 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryTab.kt @@ -30,7 +30,6 @@ 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.entries.manga.model.isLocal import eu.kanade.presentation.category.ChangeCategoryDialog import eu.kanade.presentation.entries.LibraryBottomActionMenu import eu.kanade.presentation.library.DeleteLibraryEntryDialog @@ -60,6 +59,7 @@ import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreenAction import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.injectLazy object MangaLibraryTab : Tab { @@ -132,7 +132,7 @@ object MangaLibraryTab : Tab { onClickUnselectAll = screenModel::clearSelection, onClickSelectAll = { screenModel.selectAll(screenModel.activeCategoryIndex) }, onClickInvertSelection = { screenModel.invertSelection(screenModel.activeCategoryIndex) }, - onClickFilter = { screenModel.showSettingsDialog() }, + onClickFilter = screenModel::showSettingsDialog, onClickRefresh = { onClickRefresh(state.categories[screenModel.activeCategoryIndex]) }, onClickGlobalUpdate = { onClickRefresh(null) }, onClickOpenRandomEntry = { @@ -203,7 +203,7 @@ object MangaLibraryTab : Tab { } Unit }.takeIf { state.showMangaContinueButton }, - onToggleSelection = { screenModel.toggleSelection(it) }, + onToggleSelection = screenModel::toggleSelection, onToggleRangeSelection = { screenModel.toggleRangeSelection(it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) @@ -263,7 +263,7 @@ object MangaLibraryTab : Tab { } LaunchedEffect(state.selectionMode, state.dialog) { - HomeScreen.showBottomNav(!state.selectionMode && state.dialog !is MangaLibraryScreenModel.Dialog.SettingsSheet) + HomeScreen.showBottomNav(!state.selectionMode) } LaunchedEffect(state.isLoading) { 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 5677e03936..5568402370 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 @@ -8,7 +8,6 @@ import androidx.lifecycle.viewModelScope import eu.kanade.core.util.asFlow import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.domain.items.episode.model.toDbEpisode import eu.kanade.domain.track.anime.model.toDbTrack import eu.kanade.domain.track.anime.service.DelayedAnimeTrackingUpdateJob @@ -74,6 +73,7 @@ import tachiyomi.domain.items.episode.service.getEpisodeSort import tachiyomi.domain.source.anime.service.AnimeSourceManager import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.InputStream diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/SkipIntroLengthDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/SkipIntroLengthDialog.kt index 7a1036d1c5..f9e9798865 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/SkipIntroLengthDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/dialogs/SkipIntroLengthDialog.kt @@ -47,7 +47,7 @@ fun SkipIntroLengthDialog( content = { WheelTextPicker( modifier = Modifier.align(Alignment.Center), - texts = remember { 1..255 }.map { stringResource(R.string.seconds_short, it) }, + items = remember { 1..255 }.map { stringResource(R.string.seconds_short, it) }, onSelectionChanged = { newLength = it + 1 }, startIndex = if (currentSkipIntroLength > 0) { currentSkipIntroLength - 1 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt new file mode 100644 index 0000000000..8a2f23cb91 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ChapterTransition.kt @@ -0,0 +1,170 @@ +package eu.kanade.tachiyomi.ui.reader + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.OfflinePin +import androidx.compose.material.icons.outlined.Warning +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ProvideTextStyle +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.database.models.manga.Chapter +import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter +import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager +import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader +import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.domain.items.service.calculateChapterGap +import tachiyomi.presentation.core.components.material.SecondaryItemAlpha + +@Composable +fun ChapterTransition( + transition: ChapterTransition, + downloadManager: MangaDownloadManager, + manga: Manga?, +) { + manga ?: return + + val currChapter = transition.from.chapter + val currChapterDownloaded = transition.from.pageLoader is DownloadPageLoader + + val goingToChapter = transition.to?.chapter + val goingToChapterDownloaded = if (goingToChapter != null) { + downloadManager.isChapterDownloaded( + goingToChapter.name, + goingToChapter.scanlator, + manga.title, + manga.source, + skipCache = true, + ) + } else { + false + } + + ProvideTextStyle(MaterialTheme.typography.bodyMedium) { + when (transition) { + is ChapterTransition.Prev -> { + TransitionText( + topLabel = stringResource(R.string.transition_previous), + topChapter = goingToChapter, + topChapterDownloaded = goingToChapterDownloaded, + bottomLabel = stringResource(R.string.transition_current), + bottomChapter = currChapter, + bottomChapterDownloaded = currChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_previous), + chapterGap = calculateChapterGap(currChapter.toDomainChapter(), goingToChapter?.toDomainChapter()), + ) + } + is ChapterTransition.Next -> { + TransitionText( + topLabel = stringResource(R.string.transition_finished), + topChapter = currChapter, + topChapterDownloaded = currChapterDownloaded, + bottomLabel = stringResource(R.string.transition_next), + bottomChapter = goingToChapter, + bottomChapterDownloaded = goingToChapterDownloaded, + fallbackLabel = stringResource(R.string.transition_no_next), + chapterGap = calculateChapterGap(goingToChapter?.toDomainChapter(), currChapter.toDomainChapter()), + ) + } + } + } +} + +@Composable +private fun TransitionText( + topLabel: String, + topChapter: Chapter? = null, + topChapterDownloaded: Boolean, + bottomLabel: String, + bottomChapter: Chapter? = null, + bottomChapterDownloaded: Boolean, + fallbackLabel: String, + chapterGap: Int, +) { + val hasTopChapter = topChapter != null + val hasBottomChapter = bottomChapter != null + + Column { + Text( + text = if (hasTopChapter) topLabel else fallbackLabel, + fontWeight = FontWeight.Bold, + textAlign = if (hasTopChapter) TextAlign.Start else TextAlign.Center, + ) + topChapter?.let { ChapterText(chapter = it, downloaded = topChapterDownloaded) } + + Spacer(Modifier.height(16.dp)) + + if (chapterGap > 0) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = Icons.Outlined.Warning, + tint = MaterialTheme.colorScheme.error, + contentDescription = null, + ) + + Text(text = pluralStringResource(R.plurals.missing_chapters_warning, count = chapterGap, chapterGap)) + } + + Spacer(Modifier.height(16.dp)) + } + + Text( + text = if (hasBottomChapter) bottomLabel else fallbackLabel, + fontWeight = FontWeight.Bold, + textAlign = if (hasBottomChapter) TextAlign.Start else TextAlign.Center, + ) + bottomChapter?.let { ChapterText(chapter = it, downloaded = bottomChapterDownloaded) } + } +} + +@Composable +private fun ColumnScope.ChapterText( + chapter: Chapter, + downloaded: Boolean, +) { + FlowRow( + verticalAlignment = Alignment.CenterVertically, + ) { + if (downloaded) { + Icon( + imageVector = Icons.Outlined.OfflinePin, + contentDescription = stringResource(R.string.label_downloaded), + ) + + Spacer(Modifier.width(8.dp)) + } + + Text(chapter.name) + } + + chapter.scanlator?.let { + ProvideTextStyle( + MaterialTheme.typography.bodyMedium.copy( + color = LocalContentColor.current.copy(alpha = SecondaryItemAlpha), + ), + ) { + Text(it) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index a21fd4f097..244e2819f0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -99,10 +99,6 @@ import uy.kohesive.injekt.injectLazy import kotlin.math.abs import kotlin.math.max -/** - * Activity containing the reader of Tachiyomi. This activity is mostly a container of the - * viewers, to which calls from the presenter or UI events are delegated. - */ class ReaderActivity : BaseActivity() { companion object { @@ -662,7 +658,7 @@ class ReaderActivity : BaseActivity() { * Called from the presenter when a manga is ready. Used to instantiate the appropriate viewer * and the toolbar title. */ - fun setManga(manga: Manga) { + private fun setManga(manga: Manga) { val prevViewer = viewer val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false)) @@ -777,7 +773,7 @@ class ReaderActivity : BaseActivity() { * Called from the presenter if the initial load couldn't load the pages of the chapter. In * this case the activity is closed and a toast is shown to the user. */ - fun setInitialChapterError(error: Throwable) { + private fun setInitialChapterError(error: Throwable) { logcat(LogPriority.ERROR, error) finish() toast(error.message) @@ -952,7 +948,7 @@ class ReaderActivity : BaseActivity() { * cover to the presenter. */ fun setAsCover(page: ReaderPage) { - viewModel.setAsCover(this, page) + viewModel.setAsCover(page) } /** @@ -1040,21 +1036,21 @@ class ReaderActivity : BaseActivity() { .launchIn(lifecycleScope) readerPreferences.showPageNumber().changes() - .onEach { setPageNumberVisibility(it) } + .onEach(::setPageNumberVisibility) .launchIn(lifecycleScope) readerPreferences.trueColor().changes() - .onEach { setTrueColor(it) } + .onEach(::setTrueColor) .launchIn(lifecycleScope) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { readerPreferences.cutoutShort().changes() - .onEach { setCutoutShort(it) } + .onEach(::setCutoutShort) .launchIn(lifecycleScope) } readerPreferences.keepScreenOn().changes() - .onEach { setKeepScreenOn(it) } + .onEach(::setKeepScreenOn) .launchIn(lifecycleScope) readerPreferences.customBrightness().changes() @@ -1062,7 +1058,7 @@ class ReaderActivity : BaseActivity() { .launchIn(lifecycleScope) readerPreferences.colorFilter().changes() - .onEach { setColorFilter(it) } + .onEach(::setCustomBrightness) .launchIn(lifecycleScope) readerPreferences.colorFilterMode().changes() @@ -1139,7 +1135,7 @@ class ReaderActivity : BaseActivity() { if (enabled) { readerPreferences.customBrightnessValue().changes() .sample(100) - .onEach { setCustomBrightnessValue(it) } + .onEach(::setCustomBrightnessValue) .launchIn(lifecycleScope) } else { setCustomBrightnessValue(0) @@ -1153,7 +1149,7 @@ class ReaderActivity : BaseActivity() { if (enabled) { readerPreferences.colorFilterValue().changes() .sample(100) - .onEach { setColorFilterValue(it) } + .onEach(::setColorFilterValue) .launchIn(lifecycleScope) } else { binding.colorOverlay.isVisible = false diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 2d851261f9..cd156c36db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -1,14 +1,12 @@ package eu.kanade.tachiyomi.ui.reader import android.app.Application -import android.content.Context import android.net.Uri import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.entries.manga.interactor.SetMangaViewerFlags -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.domain.entries.manga.model.orientationType import eu.kanade.domain.entries.manga.model.readingModeType import eu.kanade.domain.items.chapter.model.toDbChapter @@ -37,6 +35,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.setting.OrientationType import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType +import eu.kanade.tachiyomi.util.chapter.removeDuplicates import eu.kanade.tachiyomi.util.editCover import eu.kanade.tachiyomi.util.lang.byteSize import eu.kanade.tachiyomi.util.lang.takeBytes @@ -78,6 +77,7 @@ import tachiyomi.domain.items.chapter.service.getChapterSort import tachiyomi.domain.source.manga.service.MangaSourceManager import tachiyomi.domain.track.manga.interactor.GetMangaTracks import tachiyomi.domain.track.manga.interactor.InsertMangaTrack +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.util.Date @@ -176,12 +176,7 @@ class ReaderViewModel( else -> chapters }.run { if (readerPreferences.skipDupe().get()) { - groupBy { it.chapterNumber } - .map { (_, chapters) -> - chapters.find { it.id == selectedChapter.id } - ?: chapters.find { it.scanlator == selectedChapter.scanlator } - ?: chapters.first() - } + removeDuplicates(selectedChapter) } else { this } @@ -201,17 +196,6 @@ class ReaderViewModel( private val incognitoMode = preferences.incognitoMode().get() - override fun onCleared() { - val currentChapters = state.value.viewerChapters - if (currentChapters != null) { - currentChapters.unref() - saveReadingProgress(currentChapters.currChapter) - chapterToDownload?.let { - downloadManager.addDownloadsToStartOfQueue(listOf(it)) - } - } - } - init { // To save state state.map { it.viewerChapters?.currChapter } @@ -226,6 +210,17 @@ class ReaderViewModel( .launchIn(viewModelScope) } + override fun onCleared() { + val currentChapters = state.value.viewerChapters + if (currentChapters != null) { + currentChapters.unref() + saveReadingProgress(currentChapters.currChapter) + chapterToDownload?.let { + downloadManager.addDownloadsToStartOfQueue(listOf(it)) + } + } + } + /** * Called when the user pressed the back button and is going to leave the reader. Used to * trigger deletion of the downloaded chapters. @@ -338,10 +333,11 @@ class ReaderViewModel( } /** - * Called when the user is going to load the prev/next chapter through the menu button. + * Called when the user is going to load the prev/next chapter through the toolbar buttons. */ private suspend fun loadAdjacent(chapter: ReaderChapter) { val loader = loader ?: return + saveCurrentChapterReadingProgress() logcat { "Loading adjacent ${chapter.chapter.url}" } @@ -456,8 +452,13 @@ class ReaderViewModel( ) if (!isNextChapterDownloaded) return@launchIO - val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!) - .take(amount) + val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!).run { + if (readerPreferences.skipDupe().get()) { + removeDuplicates(nextChapter.toDomainChapter()!!) + } else { + this + } + }.take(amount) downloadManager.downloadChapters( manga, chaptersToDownload, @@ -767,7 +768,7 @@ class ReaderViewModel( /** * Sets the image of this [page] as cover and notifies the UI of the result. */ - fun setAsCover(context: Context, page: ReaderPage) { + fun setAsCover(page: ReaderPage) { if (page.status != Page.State.READY) return val manga = manga ?: return val stream = page.stream ?: return diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 46d6a63a60..24a6441a1f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -12,8 +12,9 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.notify /** * Class used to show BigPictureStyle notifications @@ -49,7 +50,7 @@ class SaveImageNotifier(private val context: Context) { * Clears the notification message. */ fun onClear() { - context.notificationManager.cancel(notificationId) + context.cancelNotification(notificationId) } /** @@ -97,6 +98,6 @@ class SaveImageNotifier(private val context: Context) { private fun updateNotification() { // Displays the progress bar on notification - context.notificationManager.notify(notificationId, notificationBuilder.build()) + context.notify(notificationId, notificationBuilder.build()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt index 2695ce198b..7f76dbb764 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderColorFilterSettings.kt @@ -33,7 +33,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr addView(binding.root) readerPreferences.colorFilter().changes() - .onEach { setColorFilter(it) } + .onEach(::setColorFilter) .launchIn((context as ReaderActivity).lifecycleScope) readerPreferences.colorFilterMode().changes() @@ -41,7 +41,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr .launchIn(context.lifecycleScope) readerPreferences.customBrightness().changes() - .onEach { setCustomBrightness(it) } + .onEach(::setCustomBrightness) .launchIn(context.lifecycleScope) // Get color and update values @@ -141,7 +141,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr if (enabled) { readerPreferences.customBrightnessValue().changes() .sample(100) - .onEach { setCustomBrightnessValue(it) } + .onEach(::setCustomBrightnessValue) .launchIn((context as ReaderActivity).lifecycleScope) } else { setCustomBrightnessValue(0, true) @@ -169,7 +169,7 @@ class ReaderColorFilterSettings @JvmOverloads constructor(context: Context, attr if (enabled) { readerPreferences.colorFilterValue().changes() .sample(100) - .onEach { setColorFilterValue(it) } + .onEach(::setColorFilterValue) .launchIn((context as ReaderActivity).lifecycleScope) } setColorFilterSeekBar(enabled) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderGeneralSettings.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderGeneralSettings.kt index 742c1b8f21..50beac220c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderGeneralSettings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderGeneralSettings.kt @@ -9,9 +9,9 @@ import androidx.lifecycle.lifecycleScope import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.ReaderGeneralSettingsBinding import eu.kanade.tachiyomi.ui.reader.ReaderActivity -import eu.kanade.tachiyomi.util.preference.asHotFlow import eu.kanade.tachiyomi.util.preference.bindToPreference import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.injectLazy /** @@ -37,8 +37,8 @@ class ReaderGeneralSettings @JvmOverloads constructor(context: Context, attrs: A binding.backgroundColor.bindToIntPreference(readerPreferences.readerTheme(), R.array.reader_themes_values) binding.showPageNumber.bindToPreference(readerPreferences.showPageNumber()) binding.fullscreen.bindToPreference(readerPreferences.fullscreen()) - readerPreferences.fullscreen() - .asHotFlow { + readerPreferences.fullscreen().changes() + .onEach { // If the preference is explicitly disabled, that means the setting was configured since there is a cutout binding.cutoutShort.isVisible = it && ((context as ReaderActivity).hasCutout || !readerPreferences.cutoutShort().get()) binding.cutoutShort.bindToPreference(readerPreferences.cutoutShort()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index 855327c043..7c8ad1f729 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.reader.setting -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.util.system.isReleaseBuildType import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.getEnum @@ -19,6 +18,7 @@ class ReaderPreferences( fun showReadingMode() = preferenceStore.getBoolean("pref_show_reading_mode", true) + // TODO: default this to true if reader long strip ever goes stable fun trueColor() = preferenceStore.getBoolean("pref_true_color_key", false) fun fullscreen() = preferenceStore.getBoolean("fullscreen", true) @@ -54,7 +54,7 @@ class ReaderPreferences( fun webtoonSidePadding() = preferenceStore.getInt("webtoon_side_padding", 0) - fun readerHideThreshold() = preferenceStore.getEnum("reader_hide_threshold", PreferenceValues.ReaderHideThreshold.LOW) + fun readerHideThreshold() = preferenceStore.getEnum("reader_hide_threshold", ReaderHideThreshold.LOW) fun folderPerManga() = preferenceStore.getBoolean("create_folder_per_manga", false) @@ -76,6 +76,10 @@ class ReaderPreferences( fun dualPageInvertWebtoon() = preferenceStore.getBoolean("pref_dual_page_invert_webtoon", false) + fun dualPageRotateToFit() = preferenceStore.getBoolean("pref_dual_page_rotate", false) + + fun dualPageRotateToFitInvert() = preferenceStore.getBoolean("pref_dual_page_rotate_invert", false) + // endregion // region Color filter @@ -108,13 +112,27 @@ class ReaderPreferences( fun navigationModeWebtoon() = preferenceStore.getInt("reader_navigation_mode_webtoon", 0) - fun pagerNavInverted() = preferenceStore.getEnum("reader_tapping_inverted", PreferenceValues.TappingInvertMode.NONE) + fun pagerNavInverted() = preferenceStore.getEnum("reader_tapping_inverted", TappingInvertMode.NONE) - fun webtoonNavInverted() = preferenceStore.getEnum("reader_tapping_inverted_webtoon", PreferenceValues.TappingInvertMode.NONE) + fun webtoonNavInverted() = preferenceStore.getEnum("reader_tapping_inverted_webtoon", TappingInvertMode.NONE) fun showNavigationOverlayNewUser() = preferenceStore.getBoolean("reader_navigation_overlay_new_user", true) fun showNavigationOverlayOnStart() = preferenceStore.getBoolean("reader_navigation_overlay_on_start", false) // endregion + + enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) { + NONE, + HORIZONTAL(shouldInvertHorizontal = true), + VERTICAL(shouldInvertVertical = true), + BOTH(shouldInvertHorizontal = true, shouldInvertVertical = true), + } + + enum class ReaderHideThreshold(val threshold: Int) { + HIGHEST(5), + HIGH(13), + LOW(31), + LOWEST(47), + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt index 24dd81f9ab..0b0859ea08 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt @@ -9,14 +9,14 @@ import androidx.lifecycle.lifecycleScope import eu.kanade.domain.entries.manga.model.orientationType import eu.kanade.domain.entries.manga.model.readingModeType import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.databinding.ReaderReadingModeSettingsBinding import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer -import eu.kanade.tachiyomi.util.preference.asHotFlow import eu.kanade.tachiyomi.util.preference.bindToPreference +import eu.kanade.tachiyomi.util.system.isReleaseBuildType import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.injectLazy /** @@ -71,12 +71,12 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr binding.webtoonPrefsGroup.root.isVisible = false binding.pagerPrefsGroup.root.isVisible = true - binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), PreferenceValues.TappingInvertMode::class.java) + binding.pagerPrefsGroup.tappingInverted.bindToPreference(readerPreferences.pagerNavInverted(), ReaderPreferences.TappingInvertMode::class.java) binding.pagerPrefsGroup.navigatePan.bindToPreference(readerPreferences.navigateToPan()) binding.pagerPrefsGroup.pagerNav.bindToPreference(readerPreferences.navigationModePager()) - readerPreferences.navigationModePager() - .asHotFlow { + readerPreferences.navigationModePager().changes() + .onEach { val isTappingEnabled = it != 5 binding.pagerPrefsGroup.tappingInverted.isVisible = isTappingEnabled binding.pagerPrefsGroup.navigatePan.isVisible = isTappingEnabled @@ -84,8 +84,8 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr .launchIn((context as ReaderActivity).lifecycleScope) // Makes so that landscape zoom gets hidden away when image scale type is not fit screen binding.pagerPrefsGroup.scaleType.bindToPreference(readerPreferences.imageScaleType(), 1) - readerPreferences.imageScaleType() - .asHotFlow { binding.pagerPrefsGroup.landscapeZoom.isVisible = it == 1 } + readerPreferences.imageScaleType().changes() + .onEach { binding.pagerPrefsGroup.landscapeZoom.isVisible = it == 1 } .launchIn((context as ReaderActivity).lifecycleScope) binding.pagerPrefsGroup.landscapeZoom.bindToPreference(readerPreferences.landscapeZoom()) @@ -93,11 +93,26 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders()) binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged()) - // Makes it so that dual page invert gets hidden away when dual page split is turned off - readerPreferences.dualPageSplitPaged() - .asHotFlow { binding.pagerPrefsGroup.dualPageInvert.isVisible = it } + readerPreferences.dualPageSplitPaged().changes() + .onEach { + binding.pagerPrefsGroup.dualPageInvert.isVisible = it + if (it) { + binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false + } + } .launchIn((context as ReaderActivity).lifecycleScope) binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged()) + + binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit()) + readerPreferences.dualPageRotateToFit().changes() + .onEach { + binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it + if (it) { + binding.pagerPrefsGroup.dualPageSplit.isChecked = false + } + } + .launchIn((context as ReaderActivity).lifecycleScope) + binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert()) } /** @@ -107,21 +122,22 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr binding.pagerPrefsGroup.root.isVisible = false binding.webtoonPrefsGroup.root.isVisible = true - binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), PreferenceValues.TappingInvertMode::class.java) + binding.webtoonPrefsGroup.tappingInverted.bindToPreference(readerPreferences.webtoonNavInverted(), ReaderPreferences.TappingInvertMode::class.java) binding.webtoonPrefsGroup.webtoonNav.bindToPreference(readerPreferences.navigationModeWebtoon()) - readerPreferences.navigationModeWebtoon() - .asHotFlow { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 } + readerPreferences.navigationModeWebtoon().changes() + .onEach { binding.webtoonPrefsGroup.tappingInverted.isVisible = it != 5 } .launchIn((context as ReaderActivity).lifecycleScope) binding.webtoonPrefsGroup.cropBordersWebtoon.bindToPreference(readerPreferences.cropBordersWebtoon()) binding.webtoonPrefsGroup.webtoonSidePadding.bindToIntPreference(readerPreferences.webtoonSidePadding(), R.array.webtoon_side_padding_values) binding.webtoonPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitWebtoon()) // Makes it so that dual page invert gets hidden away when dual page split is turned off - readerPreferences.dualPageSplitWebtoon() - .asHotFlow { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it } + readerPreferences.dualPageSplitWebtoon().changes() + .onEach { binding.webtoonPrefsGroup.dualPageInvert.isVisible = it } .launchIn((context as ReaderActivity).lifecycleScope) binding.webtoonPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertWebtoon()) + binding.webtoonPrefsGroup.longStripSplit.isVisible = !isReleaseBuildType binding.webtoonPrefsGroup.longStripSplit.bindToPreference(readerPreferences.longStripSplitWebtoon()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt index 14386d3c55..0f684736a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt @@ -2,20 +2,27 @@ package eu.kanade.tachiyomi.ui.reader.setting import android.animation.ValueAnimator import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import com.google.android.material.tabs.TabLayout import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.listener.SimpleTabSelectedListener -import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog +import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog class ReaderSettingsSheet( private val activity: ReaderActivity, private val showColorFilterSettings: Boolean = false, -) : TabbedBottomSheetDialog(activity) { +) : BaseBottomSheetDialog(activity) { - private val readingModeSettings = ReaderReadingModeSettings(activity) - private val generalSettings = ReaderGeneralSettings(activity) - private val colorFilterSettings = ReaderColorFilterSettings(activity) + private val tabs = listOf( + ReaderReadingModeSettings(activity) to R.string.pref_category_reading_mode, + ReaderGeneralSettings(activity) to R.string.pref_category_general, + ReaderColorFilterSettings(activity) to R.string.custom_filter, + ) private val backgroundDimAnimator by lazy { val sheetBackgroundDim = window?.attributes?.dimAmount ?: 0.25f @@ -27,13 +34,26 @@ class ReaderSettingsSheet( } } + private lateinit var binding: CommonTabbedSheetBinding + + override fun createView(inflater: LayoutInflater): View { + binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater) + + val adapter = Adapter() + binding.pager.offscreenPageLimit = 2 + binding.pager.adapter = adapter + binding.tabs.setupWithViewPager(binding.pager) + + return binding.root + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) behavior.isFitToContents = false behavior.halfExpandedRatio = 0.25f - val filterTabIndex = getTabViews().indexOf(colorFilterSettings) + val filterTabIndex = tabs.indexOfFirst { it.first is ReaderColorFilterSettings } binding.tabs.addOnTabSelectedListener( object : SimpleTabSelectedListener() { override fun onTabSelected(tab: TabLayout.Tab?) { @@ -63,15 +83,18 @@ class ReaderSettingsSheet( } } - override fun getTabViews() = listOf( - readingModeSettings, - generalSettings, - colorFilterSettings, - ) + private inner class Adapter : ViewPagerAdapter() { - override fun getTabTitles() = listOf( - R.string.pref_category_reading_mode, - R.string.pref_category_general, - R.string.custom_filter, - ) + override fun createView(container: ViewGroup, position: Int): View { + return tabs[position].first + } + + override fun getCount(): Int { + return tabs.size + } + + override fun getPageTitle(position: Int): CharSequence { + return activity.resources!!.getString(tabs[position].second) + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt index cbea70e6fc..a2948127ec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/MissingChapters.kt @@ -2,45 +2,11 @@ package eu.kanade.tachiyomi.ui.reader.viewer import eu.kanade.tachiyomi.data.database.models.manga.toDomainChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter -import tachiyomi.domain.items.chapter.model.Chapter -import kotlin.math.floor +import tachiyomi.domain.items.service.calculateChapterGap as domainCalculateChapterGap -private val pattern = Regex("""\d+""") - -fun hasMissingChapters(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Boolean { - if (higherReaderChapter == null || lowerReaderChapter == null) return false - return hasMissingChapters(higherReaderChapter.chapter.toDomainChapter(), lowerReaderChapter.chapter.toDomainChapter()) -} - -fun hasMissingChapters(higherChapter: Chapter?, lowerChapter: Chapter?): Boolean { - if (higherChapter == null || lowerChapter == null) return false - // Check if name contains a number that is potential chapter number - if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return false - // Check if potential chapter number was recognized as chapter number - if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return false - return hasMissingChapters(higherChapter.chapterNumber, lowerChapter.chapterNumber) -} - -fun hasMissingChapters(higherChapterNumber: Float, lowerChapterNumber: Float): Boolean { - if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return false - return calculateChapterDifference(higherChapterNumber, lowerChapterNumber) > 0f -} - -fun calculateChapterDifference(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Float { - if (higherReaderChapter == null || lowerReaderChapter == null) return 0f - return calculateChapterDifference(higherReaderChapter.chapter.toDomainChapter(), lowerReaderChapter.chapter.toDomainChapter()) -} - -fun calculateChapterDifference(higherChapter: Chapter?, lowerChapter: Chapter?): Float { - if (higherChapter == null || lowerChapter == null) return 0f - // Check if name contains a number that is potential chapter number - if (!pattern.containsMatchIn(higherChapter.name) || !pattern.containsMatchIn(lowerChapter.name)) return 0f - // Check if potential chapter number was recognized as chapter number - if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return 0f - return calculateChapterDifference(higherChapter.chapterNumber, lowerChapter.chapterNumber) -} - -fun calculateChapterDifference(higherChapterNumber: Float, lowerChapterNumber: Float): Float { - if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return 0f - return floor(higherChapterNumber) - floor(lowerChapterNumber) - 1f +fun calculateChapterGap(higherReaderChapter: ReaderChapter?, lowerReaderChapter: ReaderChapter?): Int { + return domainCalculateChapterGap( + higherReaderChapter?.chapter?.toDomainChapter(), + lowerReaderChapter?.chapter?.toDomainChapter(), + ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index 49ad3fb1d1..f010e9c365 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -115,7 +115,7 @@ open class ReaderPageImageView @JvmOverloads constructor( val point = when (config!!.zoomStartPosition) { ZoomStartPosition.LEFT -> if (forward) PointF(0F, 0F) else PointF(sWidth.toFloat(), 0F) ZoomStartPosition.RIGHT -> if (forward) PointF(sWidth.toFloat(), 0F) else PointF(0F, 0F) - ZoomStartPosition.CENTER -> center.also { it?.y = 0F } + ZoomStartPosition.CENTER -> center } val targetScale = height.toFloat() / sHeight.toFloat() @@ -249,7 +249,7 @@ open class ReaderPageImageView @JvmOverloads constructor( when (config?.zoomStartPosition) { ZoomStartPosition.LEFT -> setScaleAndCenter(scale, PointF(0F, 0F)) ZoomStartPosition.RIGHT -> setScaleAndCenter(scale, PointF(sWidth.toFloat(), 0F)) - ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center.also { it?.y = 0F }) + ZoomStartPosition.CENTER -> setScaleAndCenter(scale, center) null -> {} } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt index a29dd16448..425937ef66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderTransitionView.kt @@ -1,30 +1,17 @@ package eu.kanade.tachiyomi.ui.reader.viewer import android.content.Context -import android.text.SpannableStringBuilder -import android.text.style.ImageSpan import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.LinearLayout -import androidx.core.content.ContextCompat -import androidx.core.text.bold -import androidx.core.text.buildSpannedString -import androidx.core.text.inSpans -import androidx.core.view.isVisible -import eu.kanade.tachiyomi.R +import android.widget.FrameLayout +import androidx.compose.ui.platform.ComposeView import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager -import eu.kanade.tachiyomi.databinding.ReaderTransitionViewBinding -import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader +import eu.kanade.tachiyomi.ui.reader.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition -import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.setComposeContent import tachiyomi.domain.entries.manga.model.Manga -import kotlin.math.roundToInt class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : - LinearLayout(context, attrs) { - - private val binding: ReaderTransitionViewBinding = - ReaderTransitionViewBinding.inflate(LayoutInflater.from(context), this, true) + FrameLayout(context, attrs) { init { layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) @@ -32,138 +19,18 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At fun bind(transition: ChapterTransition, downloadManager: MangaDownloadManager, manga: Manga?) { manga ?: return - when (transition) { - is ChapterTransition.Prev -> bindPrevChapterTransition(transition, downloadManager, manga) - is ChapterTransition.Next -> bindNextChapterTransition(transition, downloadManager, manga) - } - missingChapterWarning(transition) - } - - /** - * Binds a previous chapter transition on this view and subscribes to the page load status. - */ - private fun bindPrevChapterTransition( - transition: ChapterTransition, - downloadManager: MangaDownloadManager, - manga: Manga, - ) { - val prevChapter = transition.to?.chapter - - binding.lowerText.isVisible = prevChapter != null - if (prevChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isPrevDownloaded = downloadManager.isChapterDownloaded( - prevChapter.name, - prevChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_previous)) } - append("\n${prevChapter.name}") - if (!prevChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${prevChapter.scanlator}") - } - if (isPrevDownloaded) addDLImageSpan() - } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_current)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_previous) - } - } - /** - * Binds a next chapter transition on this view and subscribes to the load status. - */ - private fun bindNextChapterTransition( - transition: ChapterTransition, - downloadManager: MangaDownloadManager, - manga: Manga, - ) { - val nextChapter = transition.to?.chapter + removeAllViews() - binding.lowerText.isVisible = nextChapter != null - if (nextChapter != null) { - binding.upperText.textAlignment = TEXT_ALIGNMENT_TEXT_START - val isCurrentDownloaded = transition.from.pageLoader is DownloadPageLoader - val isNextDownloaded = downloadManager.isChapterDownloaded( - nextChapter.name, - nextChapter.scanlator, - manga.title, - manga.source, - skipCache = true, - ) - binding.upperText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_finished)) } - append("\n${transition.from.chapter.name}") - if (!transition.from.chapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${transition.from.chapter.scanlator}") - } - if (isCurrentDownloaded) addDLImageSpan() + val transitionView = ComposeView(context).apply { + setComposeContent { + ChapterTransition( + transition = transition, + downloadManager = downloadManager, + manga = manga, + ) } - binding.lowerText.text = buildSpannedString { - bold { append(context.getString(R.string.transition_next)) } - append("\n${nextChapter.name}") - if (!nextChapter.scanlator.isNullOrBlank()) { - append(DOT_SEPERATOR) - append("${nextChapter.scanlator}") - } - if (isNextDownloaded) addDLImageSpan() - } - } else { - binding.upperText.textAlignment = TEXT_ALIGNMENT_CENTER - binding.upperText.text = context.getString(R.string.transition_no_next) } - } - - private fun SpannableStringBuilder.addDLImageSpan() { - val icon = ContextCompat.getDrawable(context, R.drawable.ic_offline_pin_24dp)?.mutate() - ?.apply { - val size = binding.lowerText.textSize + 4.dpToPx - setTint(binding.lowerText.currentTextColor) - setBounds(0, 0, size.roundToInt(), size.roundToInt()) - } ?: return - append(" ") - inSpans(ImageSpan(icon)) { append("image") } - } - - private fun missingChapterWarning(transition: ChapterTransition) { - if (transition.to == null) { - binding.warning.isVisible = false - return - } - - val hasMissingChapters = when (transition) { - is ChapterTransition.Prev -> hasMissingChapters(transition.from, transition.to) - is ChapterTransition.Next -> hasMissingChapters(transition.to, transition.from) - } - - if (!hasMissingChapters) { - binding.warning.isVisible = false - return - } - - val chapterDifference = when (transition) { - is ChapterTransition.Prev -> calculateChapterDifference(transition.from, transition.to) - is ChapterTransition.Next -> calculateChapterDifference(transition.to, transition.from) - } - - binding.warningText.text = resources.getQuantityString(R.plurals.missing_chapters_warning, chapterDifference.toInt(), chapterDifference.toInt()) - binding.warning.isVisible = true + addView(transitionView) } } - -private const val DOT_SEPERATOR = " • " diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt index 7c9279746a..44dd19db42 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.ui.reader.viewer -import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.distinctUntilChanged @@ -17,7 +16,7 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc var navigationModeChangedListener: (() -> Unit)? = null - var tappingInverted = TappingInvertMode.NONE + var tappingInverted = ReaderPreferences.TappingInvertMode.NONE var longTapEnabled = true var usePageTransitions = false var doubleTapAnimDuration = 500 @@ -38,6 +37,12 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc var dualPageInvert = false protected set + var dualPageRotateToFit = false + protected set + + var dualPageRotateToFitInvert = false + protected set + abstract var navigator: ViewerNavigation protected set diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerNavigation.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerNavigation.kt index 224747466f..d4acd01b1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerNavigation.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerNavigation.kt @@ -4,7 +4,7 @@ import android.graphics.PointF import android.graphics.RectF import androidx.annotation.StringRes import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferenceValues +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.lang.invert abstract class ViewerNavigation { @@ -21,8 +21,8 @@ abstract class ViewerNavigation { val rectF: RectF, val type: NavigationRegion, ) { - fun invert(invertMode: PreferenceValues.TappingInvertMode): Region { - if (invertMode == PreferenceValues.TappingInvertMode.NONE) return this + fun invert(invertMode: ReaderPreferences.TappingInvertMode): Region { + if (invertMode == ReaderPreferences.TappingInvertMode.NONE) return this return this.copy( rectF = this.rectF.invert(invertMode), ) @@ -33,7 +33,7 @@ abstract class ViewerNavigation { abstract var regions: List - var invertMode: PreferenceValues.TappingInvertMode = PreferenceValues.TappingInvertMode.NONE + var invertMode: ReaderPreferences.TappingInvertMode = ReaderPreferences.TappingInvertMode.NONE fun getAction(pos: PointF): NavigationRegion { val x = pos.x diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt index c316274250..b319fd9287 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt @@ -94,6 +94,18 @@ class PagerConfig( readerPreferences.dualPageInvertPaged() .register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() }) + + readerPreferences.dualPageRotateToFit() + .register( + { dualPageRotateToFit = it }, + { imagePropertyChangedListener?.invoke() }, + ) + + readerPreferences.dualPageRotateToFitInvert() + .register( + { dualPageRotateToFitInvert = it }, + { imagePropertyChangedListener?.invoke() }, + ) } private fun zoomTypeFromPreference(value: Int) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 801e03c4bb..76a920c22e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -180,6 +180,10 @@ class PagerPageHolder( } private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream { + if (viewer.config.dualPageRotateToFit) { + return rotateDualPage(imageStream) + } + if (!viewer.config.dualPageSplit) { return imageStream } @@ -198,6 +202,16 @@ class PagerPageHolder( return splitInHalf(imageStream) } + private fun rotateDualPage(imageStream: BufferedInputStream): InputStream { + val isDoublePage = ImageUtil.isWideImage(imageStream) + return if (isDoublePage) { + val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f + ImageUtil.rotateImage(imageStream, rotation) + } else { + imageStream + } + } + private fun splitInHalf(imageStream: InputStream): InputStream { var side = when { viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt index d11ef8e4d6..7ba46f5e00 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerTransitionHolder.kt @@ -63,7 +63,7 @@ class PagerTransitionHolder( transitionView.bind(transition, viewer.downloadManager, viewer.activity.viewModel.manga) - transition.to?.let { observeStatus(it) } + transition.to?.let(::observeStatus) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index d9e44017e3..07b9a2671f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -74,9 +74,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { setChaptersInternal(viewerChapters) awaitingIdleViewerChapters = null if (viewerChapters.currChapter.pages?.size == 1) { - adapter.nextTransition?.to?.let { - activity.requestPreloadChapter(it) - } + adapter.nextTransition?.to?.let(activity::requestPreloadChapter) } } } @@ -234,9 +232,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { val inPreloadRange = pages.size - page.number < 5 if (inPreloadRange && allowPreload && page.chapter == adapter.currentChapter) { logcat { "Request preload next chapter because we're at page ${page.number} of ${pages.size}" } - adapter.nextTransition?.to?.let { - activity.requestPreloadChapter(it) - } + adapter.nextTransition?.to?.let(activity::requestPreloadChapter) } } @@ -320,7 +316,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { */ protected open fun moveRight() { if (pager.currentItem != adapter.count - 1) { - val holder = (currentPage as? ReaderPage)?.let { getPageHolder(it) } + val holder = (currentPage as? ReaderPage)?.let(::getPageHolder) if (holder != null && config.navigateToPan && holder.canPanRight()) { holder.panRight() } else { @@ -334,7 +330,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { */ protected open fun moveLeft() { if (pager.currentItem != 0) { - val holder = (currentPage as? ReaderPage)?.let { getPageHolder(it) } + val holder = (currentPage as? ReaderPage)?.let(::getPageHolder) if (holder != null && config.navigateToPan && holder.canPanLeft()) { holder.panLeft() } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index 59c5fb52bd..ec78dc8c6c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters -import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters +import eu.kanade.tachiyomi.ui.reader.viewer.calculateChapterGap import eu.kanade.tachiyomi.util.system.createReaderThemeContext import eu.kanade.tachiyomi.widget.ViewPagerAdapter import tachiyomi.core.util.system.logcat @@ -48,8 +48,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { val newItems = mutableListOf() // Forces chapter transition if there is missing chapters - val prevHasMissingChapters = hasMissingChapters(chapters.currChapter, chapters.prevChapter) - val nextHasMissingChapters = hasMissingChapters(chapters.nextChapter, chapters.currChapter) + val prevHasMissingChapters = calculateChapterGap(chapters.currChapter, chapters.prevChapter) > 0 + val nextHasMissingChapters = calculateChapterGap(chapters.nextChapter, chapters.currChapter) > 0 // Add previous chapter pages and transition. if (chapters.prevChapter != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt index 71766a2e6c..87edd1da45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonAdapter.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.StencilPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView -import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters +import eu.kanade.tachiyomi.ui.reader.viewer.calculateChapterGap import eu.kanade.tachiyomi.util.system.createReaderThemeContext import tachiyomi.core.util.system.logcat @@ -62,8 +62,8 @@ class WebtoonAdapter(val viewer: WebtoonViewer) : RecyclerView.Adapter() // Forces chapter transition if there is missing chapters - val prevHasMissingChapters = hasMissingChapters(chapters.currChapter, chapters.prevChapter) - val nextHasMissingChapters = hasMissingChapters(chapters.nextChapter, chapters.currChapter) + val prevHasMissingChapters = calculateChapterGap(chapters.currChapter, chapters.prevChapter) > 0 + val nextHasMissingChapters = calculateChapterGap(chapters.nextChapter, chapters.currChapter) > 0 // Add previous chapter pages and transition. if (chapters.prevChapter != null) { 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 index 7196fc4cb3..3ef449d791 100644 --- 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 @@ -7,7 +7,6 @@ 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.entries.anime.model.isLocal import eu.kanade.presentation.more.stats.StatsScreenState import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.tachiyomi.animesource.model.SAnime @@ -25,6 +24,7 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_C import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED import tachiyomi.domain.track.anime.interactor.GetAnimeTracks import tachiyomi.domain.track.anime.model.AnimeTrack +import tachiyomi.source.local.entries.anime.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt index 9cf3671d4c..8aa3cb552b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt @@ -7,7 +7,6 @@ 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.entries.manga.model.isLocal import eu.kanade.presentation.more.stats.StatsScreenState import eu.kanade.presentation.more.stats.data.StatsData import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager @@ -25,6 +24,7 @@ import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_C import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_VIEWED import tachiyomi.domain.track.manga.interactor.GetMangaTracks import tachiyomi.domain.track.manga.model.MangaTrack +import tachiyomi.source.local.entries.manga.isLocal import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/AnimeExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/AnimeExtensions.kt index 10c0814372..8baf8be4c9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/AnimeExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/AnimeExtensions.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.util import eu.kanade.domain.entries.anime.interactor.UpdateAnime import eu.kanade.domain.entries.anime.model.hasCustomCover -import eu.kanade.domain.entries.anime.model.isLocal import eu.kanade.domain.entries.anime.model.toSAnime import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.data.cache.AnimeCoverCache import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.anime.model.Anime +import tachiyomi.source.local.entries.anime.isLocal import tachiyomi.source.local.image.anime.LocalAnimeCoverManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index 05611d8ef3..a3f8fe1779 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -1,33 +1,25 @@ package eu.kanade.tachiyomi.util import android.content.Context -import android.net.Uri import android.os.Build import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.notification.NotificationReceiver -import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.createFileInCacheDir -import eu.kanade.tachiyomi.util.system.notificationBuilder -import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import tachiyomi.core.util.lang.withNonCancellableContext import tachiyomi.core.util.lang.withUIContext class CrashLogUtil(private val context: Context) { - private val notificationBuilder = context.notificationBuilder(Notifications.CHANNEL_CRASH_LOGS) { - setSmallIcon(R.drawable.ic_ani) - } - suspend fun dumpLogs() = withNonCancellableContext { try { val file = context.createFileInCacheDir("aniyomi_crash_logs.txt") Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor() file.appendText(getDebugInfo()) - showNotification(file.getUriCompat(context)) + val uri = file.getUriCompat(context) + context.startActivity(uri.toShareIntent(context, "text/plain")) } catch (e: Throwable) { withUIContext { context.toast("Failed to get logs") } } @@ -45,26 +37,4 @@ class CrashLogUtil(private val context: Context) { Device product name: ${Build.PRODUCT} """.trimIndent() } - - private fun showNotification(uri: Uri) { - context.notificationManager.cancel(Notifications.ID_CRASH_LOGS) - - with(notificationBuilder) { - setContentTitle(context.getString(R.string.crash_log_saved)) - - clearActions() - addAction( - R.drawable.ic_folder_24dp, - context.getString(R.string.action_open_log), - NotificationReceiver.openErrorLogPendingActivity(context, uri), - ) - addAction( - R.drawable.ic_share_24dp, - context.getString(R.string.action_share), - NotificationReceiver.shareCrashLogPendingBroadcast(context, uri, Notifications.ID_CRASH_LOGS), - ) - - context.notificationManager.notify(Notifications.ID_CRASH_LOGS, build()) - } - } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt index 12bf4cb33f..b9889f5dc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt @@ -2,12 +2,12 @@ package eu.kanade.tachiyomi.util import eu.kanade.domain.entries.manga.interactor.UpdateManga import eu.kanade.domain.entries.manga.model.hasCustomCover -import eu.kanade.domain.entries.manga.model.isLocal import eu.kanade.domain.entries.manga.model.toSManga import eu.kanade.tachiyomi.data.cache.MangaCoverCache import eu.kanade.tachiyomi.source.model.SManga import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.entries.manga.model.Manga +import tachiyomi.source.local.entries.manga.isLocal import tachiyomi.source.local.image.manga.LocalMangaCoverManager import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRemoveDuplicates.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRemoveDuplicates.kt new file mode 100644 index 0000000000..ee79988c42 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterRemoveDuplicates.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.util.chapter + +import tachiyomi.domain.items.chapter.model.Chapter + +/** + * Returns a copy of the list with duplicate chapters removed + */ +fun List.removeDuplicates(currentChapter: Chapter): List { + return groupBy { it.chapterNumber } + .map { (_, chapters) -> + chapters.find { it.id == currentChapter.id } + ?: chapters.find { it.scanlator == currentChapter.scanlator } + ?: chapters.first() + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RectFExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RectFExtensions.kt index d1ad0b5b9a..2072bcd421 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/RectFExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/RectFExtensions.kt @@ -1,9 +1,9 @@ package eu.kanade.tachiyomi.util.lang import android.graphics.RectF -import eu.kanade.tachiyomi.data.preference.PreferenceValues +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences -fun RectF.invert(invertMode: PreferenceValues.TappingInvertMode): RectF { +fun RectF.invert(invertMode: ReaderPreferences.TappingInvertMode): RectF { val horizontal = invertMode.shouldInvertHorizontal val vertical = invertMode.shouldInvertVertical return when { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt index e123496306..c6076b790d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/preference/PreferenceExtensions.kt @@ -3,8 +3,6 @@ package eu.kanade.tachiyomi.util.preference import android.widget.CompoundButton import eu.kanade.core.preference.PreferenceMutableState import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.onEach import tachiyomi.core.preference.Preference /** @@ -15,12 +13,6 @@ fun CompoundButton.bindToPreference(pref: Preference) { setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) } } -fun Preference.asHotFlow(block: (T) -> Unit): Flow { - block(get()) - return changes() - .onEach { block(it) } -} - operator fun Preference>.plusAssign(item: T) { set(get() + item) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt deleted file mode 100644 index bbe0cb1344..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ActivityExtensions.kt +++ /dev/null @@ -1,14 +0,0 @@ -package eu.kanade.tachiyomi.util.system - -import android.app.Activity -import android.os.Build - -/** - * Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.). - * - * Only works in Android 9+. - */ -fun Activity.hasDisplayCutout(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && - window.decorView.rootWindowInsets?.displayCutout != null -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt index 3136e38c56..01c6b581bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/AnimationExtensions.kt @@ -1,10 +1,18 @@ package eu.kanade.tachiyomi.util.system import android.content.Context +import android.provider.Settings import android.view.ViewPropertyAnimator import android.view.animation.Animation import androidx.constraintlayout.motion.widget.MotionScene.Transition +/** + * Gets the duration multiplier for general animations on the device + * @see Settings.Global.ANIMATOR_DURATION_SCALE + */ +val Context.animatorDurationScale: Float + get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) + /** Scale the duration of this [Animation] by [Context.animatorDurationScale] */ fun Animation.applySystemAnimatorScale(context: Context) { this.duration = (this.duration * context.animatorDurationScale).toLong() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index b8f590c57f..7043544ce1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -1,31 +1,22 @@ package eu.kanade.tachiyomi.util.system import android.app.ActivityManager -import android.app.NotificationManager import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration -import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable -import android.net.ConnectivityManager -import android.net.NetworkCapabilities import android.net.Uri -import android.net.wifi.WifiManager import android.os.Build import android.os.PowerManager -import android.provider.Settings import android.util.TypedValue -import android.view.Display -import android.view.View -import android.view.WindowManager import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.appcompat.view.ContextThemeWrapper -import androidx.core.content.ContextCompat +import androidx.core.content.PermissionChecker import androidx.core.content.getSystemService import androidx.core.graphics.alpha import androidx.core.graphics.blue @@ -34,7 +25,6 @@ import androidx.core.graphics.red import androidx.core.net.toUri import com.hippo.unifile.UniFile import eu.kanade.domain.ui.UiPreferences -import eu.kanade.domain.ui.model.TabletUiMode import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate @@ -78,7 +68,7 @@ fun Context.copyToClipboard(label: String, content: String) { * @param permission the permission to check. * @return true if it has permissions. */ -fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED +fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermission(this, permission) == PermissionChecker.PERMISSION_GRANTED /** * Returns the color for the given attribute. @@ -112,42 +102,9 @@ fun Context.hasPermission(permission: String) = ContextCompat.checkSelfPermissio } } -/** - * Converts to px and takes into account LTR/RTL layout. - */ -val Float.dpToPxEnd: Float - get() = this * Resources.getSystem().displayMetrics.density * - if (Resources.getSystem().isLTR) 1 else -1 - -val Resources.isLTR - get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR - -val Context.notificationManager: NotificationManager - get() = getSystemService()!! - -val Context.connectivityManager: ConnectivityManager - get() = getSystemService()!! - -val Context.wifiManager: WifiManager - get() = getSystemService()!! - val Context.powerManager: PowerManager get() = getSystemService()!! -val Context.displayCompat: Display? - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - display - } else { - @Suppress("DEPRECATION") - getSystemService()?.defaultDisplay - } - -/** Gets the duration multiplier for general animations on the device - * @see Settings.Global.ANIMATOR_DURATION_SCALE - */ -val Context.animatorDurationScale: Float - get() = Settings.Global.getFloat(this.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) - /** * Convenience method to acquire a partial wake lock. */ @@ -186,7 +143,7 @@ fun Context.openInBrowser(uri: Uri, forceDefaultBrowser: Boolean = false) { } } -fun Context.defaultBrowserPackageName(): String? { +private fun Context.defaultBrowserPackageName(): String? { val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri()) val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())) @@ -208,59 +165,6 @@ fun Context.createFileInCacheDir(name: String): File { return file } -private const val TABLET_UI_REQUIRED_SCREEN_WIDTH_DP = 720 - -// some tablets have screen width like 711dp = 1600px / 2.25 -private const val TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP = 700 - -// make sure icons on the nav rail fit -private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600 - -fun Context.isTabletUi(): Boolean { - return resources.configuration.isTabletUi() -} - -fun Configuration.isTabletUi(): Boolean { - return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP -} - -fun Configuration.isAutoTabletUiAvailable(): Boolean { - return smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP -} - -// TODO: move the logic to `isTabletUi()` when main activity is rewritten in Compose -fun Context.prepareTabletUiContext(): Context { - val configuration = resources.configuration - val expected = when (Injekt.get().tabletUiMode().get()) { - TabletUiMode.AUTOMATIC -> - configuration.smallestScreenWidthDp >= when (configuration.orientation) { - Configuration.ORIENTATION_PORTRAIT -> TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP - else -> TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP - } - TabletUiMode.ALWAYS -> true - TabletUiMode.LANDSCAPE -> configuration.orientation == Configuration.ORIENTATION_LANDSCAPE - TabletUiMode.NEVER -> false - } - if (configuration.isTabletUi() != expected) { - val overrideConf = Configuration() - overrideConf.setTo(configuration) - overrideConf.smallestScreenWidthDp = if (expected) { - overrideConf.smallestScreenWidthDp.coerceAtLeast(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP) - } else { - overrideConf.smallestScreenWidthDp.coerceAtMost(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP - 1) - } - return createConfigurationContext(overrideConf) - } - return this -} - -/** - * Returns true if current context is in night mode - */ -fun Context.isNightMode(): Boolean { - return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES -} - /** * Creates night mode Context depending on reader theme/background * @@ -290,35 +194,6 @@ fun Context.createReaderThemeContext(): Context { return this } -fun Context.isOnline(): Boolean { - val activeNetwork = connectivityManager.activeNetwork ?: return false - val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false - val maxTransport = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE - else -> NetworkCapabilities.TRANSPORT_VPN - } - return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(networkCapabilities::hasTransport) -} - -/** - * Returns true if device is connected to Wifi. - */ -fun Context.isConnectedToWifi(): Boolean { - if (!wifiManager.isWifiEnabled) return false - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val activeNetwork = connectivityManager.activeNetwork ?: return false - val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false - - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - } else { - @Suppress("DEPRECATION") - wifiManager.connectionInfo.bssid != null - } -} - /** * Gets document size of provided [Uri] * @@ -368,11 +243,3 @@ fun Context.getApplicationIcon(pkgName: String): Drawable? { null } } - -/** - * Gets system's config_navBarNeedsScrim boolean flag added in Android 10, defaults to true. - */ -fun Context.isNavigationBarNeedsScrim(): Boolean { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || - InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt new file mode 100644 index 0000000000..e93905c749 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DisplayExtensions.kt @@ -0,0 +1,106 @@ +package eu.kanade.tachiyomi.util.system + +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.os.Build +import android.view.Display +import android.view.View +import android.view.WindowManager +import androidx.core.content.getSystemService +import eu.kanade.domain.ui.UiPreferences +import eu.kanade.domain.ui.model.TabletUiMode +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +private const val TABLET_UI_REQUIRED_SCREEN_WIDTH_DP = 720 + +// some tablets have screen width like 711dp = 1600px / 2.25 +private const val TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP = 700 + +// make sure icons on the nav rail fit +private const val TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP = 600 + +fun Context.isTabletUi(): Boolean { + return resources.configuration.isTabletUi() +} + +fun Configuration.isTabletUi(): Boolean { + return smallestScreenWidthDp >= TABLET_UI_REQUIRED_SCREEN_WIDTH_DP +} + +fun Configuration.isAutoTabletUiAvailable(): Boolean { + return smallestScreenWidthDp >= TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP +} + +// TODO: move the logic to `isTabletUi()` when main activity is rewritten in Compose +fun Context.prepareTabletUiContext(): Context { + val configuration = resources.configuration + val expected = when (Injekt.get().tabletUiMode().get()) { + TabletUiMode.AUTOMATIC -> + configuration.smallestScreenWidthDp >= when (configuration.orientation) { + Configuration.ORIENTATION_PORTRAIT -> TABLET_UI_MIN_SCREEN_WIDTH_PORTRAIT_DP + else -> TABLET_UI_MIN_SCREEN_WIDTH_LANDSCAPE_DP + } + TabletUiMode.ALWAYS -> true + TabletUiMode.LANDSCAPE -> configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + TabletUiMode.NEVER -> false + } + if (configuration.isTabletUi() != expected) { + val overrideConf = Configuration() + overrideConf.setTo(configuration) + overrideConf.smallestScreenWidthDp = if (expected) { + overrideConf.smallestScreenWidthDp.coerceAtLeast(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP) + } else { + overrideConf.smallestScreenWidthDp.coerceAtMost(TABLET_UI_REQUIRED_SCREEN_WIDTH_DP - 1) + } + return createConfigurationContext(overrideConf) + } + return this +} + +/** + * Returns true if current context is in night mode + */ +fun Context.isNightMode(): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES +} + +val Context.displayCompat: Display? + get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + display + } else { + @Suppress("DEPRECATION") + getSystemService()?.defaultDisplay + } + +val Resources.isLTR + get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR + +/** + * Converts to px and takes into account LTR/RTL layout. + */ +val Float.dpToPxEnd: Float + get() = ( + this * Resources.getSystem().displayMetrics.density * + if (Resources.getSystem().isLTR) 1 else -1 + ) + +/** + * Checks whether if the device has a display cutout (i.e. notch, camera cutout, etc.). + * + * Only works in Android 9+. + */ +fun Activity.hasDisplayCutout(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && + window.decorView.rootWindowInsets?.displayCutout != null +} + +/** + * Gets system's config_navBarNeedsScrim boolean flag added in Android 10, defaults to true. + */ +fun Context.isNavigationBarNeedsScrim(): Boolean { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || + InternalResourceHelper.getBoolean(this, "config_navBarNeedsScrim", true) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt new file mode 100644 index 0000000000..8d72a9c865 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NetworkExtensions.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.net.wifi.WifiManager +import android.os.Build +import androidx.core.content.getSystemService + +val Context.connectivityManager: ConnectivityManager + get() = getSystemService()!! + +val Context.wifiManager: WifiManager + get() = getSystemService()!! + +fun Context.isOnline(): Boolean { + val activeNetwork = connectivityManager.activeNetwork ?: return false + val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false + val maxTransport = when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> NetworkCapabilities.TRANSPORT_LOWPAN + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> NetworkCapabilities.TRANSPORT_WIFI_AWARE + else -> NetworkCapabilities.TRANSPORT_VPN + } + return (NetworkCapabilities.TRANSPORT_CELLULAR..maxTransport).any(networkCapabilities::hasTransport) +} + +/** + * Returns true if device is connected to Wifi. + */ +fun Context.isConnectedToWifi(): Boolean { + if (!wifiManager.isWifiEnabled) return false + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val activeNetwork = connectivityManager.activeNetwork ?: return false + val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false + + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && + networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } else { + @Suppress("DEPRECATION") + wifiManager.connectionInfo.bssid != null + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt index 19ef75a8b2..5a37d66154 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/NotificationExtensions.kt @@ -1,12 +1,63 @@ package eu.kanade.tachiyomi.util.system +import android.Manifest import android.app.Notification +import android.app.NotificationManager import android.content.Context +import android.os.Build import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelGroupCompat import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.app.NotificationManagerCompat.NotificationWithIdAndTag +import androidx.core.content.PermissionChecker +import androidx.core.content.getSystemService import eu.kanade.tachiyomi.R +val Context.notificationManager: NotificationManager + get() = getSystemService()!! + +fun Context.notify(id: Int, channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null) { + val notification = notificationBuilder(channelId, block).build() + this.notify(id, notification) +} + +fun Context.notify(id: Int, notification: Notification) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { + return + } + + NotificationManagerCompat.from(this).notify(id, notification) +} + +fun Context.notify(notificationWithIdAndTags: List) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && PermissionChecker.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PermissionChecker.PERMISSION_GRANTED) { + return + } + + NotificationManagerCompat.from(this).notify(notificationWithIdAndTags) +} + +fun Context.cancelNotification(id: Int) { + NotificationManagerCompat.from(this).cancel(id) +} + +/** + * Helper method to create a notification builder. + * + * @param id the channel id. + * @param block the function that will execute inside the builder. + * @return a notification to be displayed or updated. + */ +fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder { + val builder = NotificationCompat.Builder(this, channelId) + .setColor(getColor(R.color.accent_blue)) + if (block != null) { + builder.block() + } + return builder +} + /** * Helper method to build a notification channel group. * @@ -40,31 +91,3 @@ fun buildNotificationChannel( builder.block() return builder.build() } - -/** - * Helper method to create a notification builder. - * - * @param id the channel id. - * @param block the function that will execute inside the builder. - * @return a notification to be displayed or updated. - */ -fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder { - val builder = NotificationCompat.Builder(this, channelId) - .setColor(getColor(R.color.accent_blue)) - if (block != null) { - builder.block() - } - return builder -} - -/** - * Helper method to create a notification. - * - * @param id the channel id. - * @param block the function that will execute inside the builder. - * @return a notification to be displayed or updated. - */ -fun Context.notification(channelId: String, block: (NotificationCompat.Builder.() -> Unit)?): Notification { - val builder = notificationBuilder(channelId, block) - return builder.build() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/WorkManagerExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/WorkManagerExtensions.kt new file mode 100644 index 0000000000..02d721725f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/WorkManagerExtensions.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.Context +import androidx.work.WorkInfo +import androidx.work.WorkManager + +val Context.workManager: WorkManager + get() = WorkManager.getInstance(this) + +fun WorkManager.isRunning(tag: String): Boolean { + val list = this.getWorkInfosByTag(tag).get() + return list.any { it.state == WorkInfo.State.RUNNING } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt index 14b0f76d39..60eaacbe93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt @@ -19,7 +19,6 @@ import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.TooltipCompat -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -34,10 +33,11 @@ import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.system.getResourceColor -inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> Unit) { - consumeWindowInsets = false - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setContent { +inline fun ComponentActivity.setComposeContent( + parent: CompositionContext? = null, + crossinline content: @Composable () -> Unit, +) { + setContent(parent) { TachiyomiTheme { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall, @@ -49,11 +49,11 @@ inline fun ComposeView.setComposeContent(crossinline content: @Composable () -> } } -inline fun ComponentActivity.setComposeContent( - parent: CompositionContext? = null, - crossinline content: @Composable () -> Unit, +fun ComposeView.setComposeContent( + content: @Composable () -> Unit, ) { - setContent(parent) { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { TachiyomiTheme { CompositionLocalProvider( LocalTextStyle provides MaterialTheme.typography.bodySmall, diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt index bf77d38fdf..a571b68fa9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt @@ -7,13 +7,13 @@ import androidx.core.view.inputmethod.EditorInfoCompat import com.google.android.material.textfield.TextInputEditText import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.preference.asHotFlow import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -49,8 +49,8 @@ class TachiyomiTextInputEditText @JvmOverloads constructor( * if [BasePreferences.incognitoMode] is true. Some IMEs may not respect this flag. */ fun EditText.setIncognito(viewScope: CoroutineScope) { - Injekt.get().incognitoMode() - .asHotFlow { + Injekt.get().incognitoMode().changes() + .onEach { imeOptions = if (it) { imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt deleted file mode 100644 index 447a9c6b63..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/sheet/TabbedBottomSheetDialog.kt +++ /dev/null @@ -1,43 +0,0 @@ -package eu.kanade.tachiyomi.widget.sheet - -import android.app.Activity -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import eu.kanade.tachiyomi.databinding.CommonTabbedSheetBinding -import eu.kanade.tachiyomi.widget.ViewPagerAdapter - -abstract class TabbedBottomSheetDialog(private val activity: Activity) : BaseBottomSheetDialog(activity) { - - lateinit var binding: CommonTabbedSheetBinding - - override fun createView(inflater: LayoutInflater): View { - binding = CommonTabbedSheetBinding.inflate(activity.layoutInflater) - - val adapter = LibrarySettingsSheetAdapter() - binding.pager.offscreenPageLimit = 2 - binding.pager.adapter = adapter - binding.tabs.setupWithViewPager(binding.pager) - - return binding.root - } - - abstract fun getTabViews(): List - - abstract fun getTabTitles(): List - - private inner class LibrarySettingsSheetAdapter : ViewPagerAdapter() { - - override fun createView(container: ViewGroup, position: Int): View { - return getTabViews()[position] - } - - override fun getCount(): Int { - return getTabViews().size - } - - override fun getPageTitle(position: Int): CharSequence { - return activity.resources!!.getString(getTabTitles()[position]) - } - } -} diff --git a/app/src/main/res/layout/compose_controller.xml b/app/src/main/res/layout/compose_controller.xml deleted file mode 100644 index 617287296b..0000000000 --- a/app/src/main/res/layout/compose_controller.xml +++ /dev/null @@ -1,4 +0,0 @@ - - diff --git a/app/src/main/res/layout/reader_pager_settings.xml b/app/src/main/res/layout/reader_pager_settings.xml index 3da1b46bee..a180918899 100644 --- a/app/src/main/res/layout/reader_pager_settings.xml +++ b/app/src/main/res/layout/reader_pager_settings.xml @@ -91,10 +91,30 @@ android:visibility="gone" tools:visibility="visible" /> + + + + + app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert,dual_page_rotate_to_fit,dual_page_rotate_to_fit_invert" /> diff --git a/app/src/main/res/layout/reader_transition_view.xml b/app/src/main/res/layout/reader_transition_view.xml deleted file mode 100644 index 2ec51224b2..0000000000 --- a/app/src/main/res/layout/reader_transition_view.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 57a9a3396f..dcbbdf1218 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 57a9a3396f..dcbbdf1218 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_local_source.webp b/app/src/main/res/mipmap-hdpi/ic_local_source.webp index 406c5bd47e4aece892eb64cffc9360a1d1592d4c..87baab9bdfbff78b567c3800e9f1fe54fc301fbb 100644 GIT binary patch literal 882 zcmV-&1C9JrNk&F$0{{S5MM6+kP&il$0000G0000-002h-06|PpNVfn000D5K?SIoG z|1i7vZq@cE?twyc0X(XENmNe*j)%)YtMlNJKIvAChzUTyTMUng?|`9hc}w9vs}5Xb z5F|7co+iP7a6l`}9yDwL6PE4tQBqrG0)0szg)VhqDxR{3Jr&tX=f^ph{`}a|N1ge zZQ{I$cn1@d2lq{_mw;Ay7gvV98}*zGE#IO$4O*lY!Fd|{ z!^Z%C0RH~P)I|m>|MTrUoe#*b%x6Gd?&&-alKc9W$?yNky7kr6c1B$^qUtjh@;h0Q7J5r+kHI3H|vD8MKv{nAd+SY7Y zpagDdJ#^FAvg*_0%~|t&i)-NW>x1^Ve>Wfbqd)1K` zyh2fpV%zyjeqgd^E`J_ajCA#HXf9|Ys#SiC5DY7#se0}j{-hfChlMjn*nxbC#Bxgr zURha-{M>&9N&W5|X-2!1J0+_fJOuv+d=^w$tq$nazZ>Qy7-ugp{k$G|x+}~;m9Rwr z^@4pHU-$UDizHp|@^`P`U0O(clSKeIdIQa+=K%9*e}=kb1G4GO&DJ*&qVYN5l(G~5 zXovh5V!^Pt8QC8bD$uk1%z~9!~Y)4r2qMy9AI>k@VXV)Vi}hK)cXNJq=rv-xk~?L_<;XUjTxXlumKGJ@R8B* IMEn;30N4DVX#fBK literal 1050 zcmV+#1m*iuNk&Ez1ONb6MM6+kP&iBm1ONapN5ByfpXRu>Z6`V8Ffx>43-(`%%Q5oa z0enTcjU-8t>KLR4m(G7v{#Bakg4;-vBPnL~xWrsK|D`jA{ZBHAB-=@oae$B ze(-{kcmuLY8!{K{8vqDOAOZo1$_~UJ0Rc!s1OgCfhk_9lBzj@^7yVCz2PSD0gCskO z0zm`@44MqtfhYo!31qKCML-}7H1K_Ynf!jqV7wd}QQpGA`) zlRsI2AOa#_6h%NF0usF-0)hycXcPev5Qw5die~%$E3`_1L32_51Q=vNV894Ub~M7@ z4uhl;VQ3%;(tz?mBOnbZC$MN>_=~1}dR?y~v)1SHao+E@VOaL*a-8>J*^kq9OuK3O z+Ht};Jb{ji2 z z``r~$kXmJ=Z_($jkbPt#eZs88T`^lwvLVY3NJBe>XhG zO_{29nqKr&j=5Wpo;gc&UgrgI5!a7UiaERE5VN;HBO{$CQ)D6LP%alxJ{B_PCG49A!6%6m%;_2WUUn8?E*sXO!eUW^iQ67-~~cNDMTFkBp%5pT+4C!cz@-{%*@ZkbDb~6D+Avm#+TJr`^&yxG+W*k5asJE)LSe{^p;YY-l}S5 zuBp|Twtcwl_sPON@wr9$Tv$|CTvQ^YW-7Sapz3@4VgDk4_tN5>=Pm~f@d{ow^cr2i zVc<=@mv$}jfOx-YCeE5IQ<|vxb>7MUk+dpDs}ZakM=1pgD@jZ&9lI1NC`kHyR%LEC z*f}s}G`NhNgZAbsBj5WDZVgS%;3B&@NVkY8wWV-!c5ww(IJ3QlT4nUFin*n=jV-W* UmgWZkLyUIRYzi#1uJ_L*0ug)mrT_o{ diff --git a/app/src/main/res/mipmap-mdpi/ic_local_source.webp b/app/src/main/res/mipmap-mdpi/ic_local_source.webp index cfd88e75ebe8de2d0f44377d398b861c0d817ed7..d2104a55e0097fd2d08e96c96faffa96c47e9d05 100644 GIT binary patch literal 674 zcmV;T0$u%5Nk&GR0ssJ4MM6+kP&il$0000G0000l001ul06|PpNM!&300D3!$+lYE z6K1s03=2+0^X^DL75I!IA|`-dMDW7JdF+8vP3+=RW$X}Yc9pfT0b{<7*}#U!uGHJ| zuf_kPwp_>JZCTBAG}Z>m;kaII9YdKmp$AEM@g&f(MG#f};;IOGP5}T`P&gnC0ssJT z3;>-0Dlh;r06tM7jzuG)Ar)G!R5%3$pbTCv0}Ka0{`dy-PwgrEH#Gy88WG-=IrVyD z*dO^9^k4P=m>gyQvCC2GTgIP=zx-eGV=)`XDf!$Q$ zOKJcB{;!tT{l!)ItobAe(oG`n*E9e7{=^Q4{O-EB59k$e1U^xsiv=^N!XcA96S9@UTkn zGLHe$^<5X=5|jg;cmvnp_=_JsVU?fj&Y!GSUAbBq#uM3<#C;3P09QswKo0FcyyPG8 zVfPL!^7!Gxa}W)l36WoX?}GedZ1w6%9<}{|`x|e2Rq0qEEg|VZv9qsK%BOmSzemEL z<9vgBlBvx?so$_V%(s1V2l)PK=zgF3(N2SJV5vf>rDUZd$aa?%tYk)O|<3;muH)3-$HVXjWO`{ycm^jlF;ooxQj{L09_<9$)Hrt0*PG|SVl zhv(JQeOUY0N26Fiy1p;#aNyV++{i{&~1=pU>h; z3~nPyQlvTtOU{h*-wSy8)#WY6kt8`vo_I-I!fhsAk-<`MIw$ArvM=}fHOQ7E+qA8HY}>YNTh}<& zwrza|t{{-j?!{XAiG%RJ2T1jfCB@KsIqEb{b+>(~VELJM1X-U)S5~b4m zbg3^@X($xBXSa8D$vy1Pq{-nYNj(4)H(S%8=UK&IG(E3%eX+AuYT}dBBcV6ZYys>x z2c2-az4YCU3ymniU^LMJSZz?C$LGIplbQI`|G(ia?(qHF5B$V0{N~?3|8VaQjL3}Y zG1nrxj0%leq?Usbp$Uss zXf+a*vPvyS#cYlxnbl}q42x;za4reO#hh49uU?b;4P5#1j%qD%2*t@Y7y#LKR8mnI6gfg$@8;wlGx~z(!PMG??{h!wUWB2j&$f? wZHZEGRdRYz`vD}Yij|7WsvDb{HD_aWS&kra zKH2YJ@=CIYK^fnjlCH8Q<*tMzHtTDs z+Lu{TKB&t;?qNump@0AV$}?Ejq&(%Fx=uGF_d(U@0D&V;Z<%+d*hu688JBP8eLf5*$aMWePOgKW5^6RZR;BzpPUMO za(uY2EoB&=uu33K-1q8ByJTn*+0BwNt0np3+zWWCBh4}|)zu|0y{X0aj_Z{Gj zb^e2l3@8sx4BtHo>wmgk;5`KYXHpJjtdjQMfgBKe*GRbUJ#7gQ-z-`d z<&lyRVO@-_4U541WC(jqIGWmFg25r1ChmY|&PT$I9VKZe`Y~DBwLXgZMzoEJ&;b7Y z_l*8MGi(5K4M!90&Q8BpK3?f3{S^~4r}AQSIn2If-Cf9U&=*}l zZ8wvR4k6+b`l=_)Ba6oWNc%nim2fE!|A6wG_|N}7it!)kKhN8^St0_t)m`hRfr}ev zfb_Z~zag%Yyip{zdKNz2gxpOj3iF#zTX-#xj?^7N)fvsIcUKU1d9m@~_!L397egPB zD{9qu5wakuYqf{TC_A7SR%{1|2RIPvQ%P3_vQEm5U-t}%E#nP4eQx+vi*WnIs5>Z< zo*gA8{xgN4s$u~cn<8w@lS`ysZg=X!Vurj%p6n9E=b`>IXb>ROGl!uY zwD+;X2AmVBzMlUDAM#82`}>(eO*K|U2hP|j>}GToty&ZQrWR?O&_sbSN_w=Cl;uF( zSJwW&b3esj4F`;0CRKfxxQ%7UM~v|oFYAx|8)!}d6#tBnxuX2DA!dSB))KgNe|lk? QFy#T!Tb09Gilr)m01j02{r~^~ literal 1180 zcmV;N1Y`SBNk&GL1ONb6MM6+kP&iD71ONapU%(d-Pv#)DZB^M%J}wbRME@lIcy}Z3 zlfgkG$*wXl5KT}B!oQH8|L5&Eh$Kl;a_^4U1d33KMRtl+B*{+t1%eaABmw*)ghB)o z(1HZCWYo58!yr=+?*KsfA0Unyi1Rs+K>;EtKm|A7bwh=3@4 zB*uuKi6nx_^BM?5i4YkA(vET8w(cNHWGIS8dF-145fF%g2#KZr$L|s%AXF~XUyQVK zYuAX8KJqxW{*M4b1Vlg}6jFh*w0%gP$9U|2hA={md>jKZL9X17F<8gB1Ys0dOa?Oq zfkmKz`D%Y;STMpI2g?s|{QlbW?>m%su8aG9I(jiZx>sL?|0LYN+tc_>eHe=g)i0@vUJiiy)w$8R~`|h`PdT_NBtExJp|BT#7 zYE%Wyz=mV~f$Q%D-Rwai^K4dpfgl3?DPtLz-@9{%Be!pc=6(l9 zP#Kl8h-7{8_B$-{u4w%peL(X5haY?>AAS7Rt-w5BKEk>t1Ca_{Nkr9LBT`3H&)gu= zq?@1K4Ntz!5oN5bNF`NKH55UDIMyJLM*l<>}W$j=j`eG*}59V7QVY8xBbyba3 z%_2%gEqpsD#_H>jKBCBGA2)o$?2}q&nmqNU(H?`1l&3ds0F%xbkpa$&m^o&g^9(30 z+$tHkC#eP-h54n$sN-0EkrbcC%<)npItz+On`DAUv4#hFl7^6W$pi*@oG337IS0v0 zZLM8$3N*@?VR^Y#qysnvI@*oE47YZ4c4b0xvRo?kdC=&RGz2gmoxq%|(3Ri;kFLZ` zRVDLfV0s9aN+P-d9o^7T%@=V;iVK)|sHR%v5+s*<2s)~nYc2u<9)dg`HhAUGfrE-N zdk?<~&K-4BPbHsz{#h#Z7n; zTxIjBO-&zP4g6kz_}SXj@gmZs?e=IBE$!w#ENXfF2>5r2jr(r6$)PvPEe^bK|3>`Z zo&E-7@it7*;+Q>p(I_TH7un;DE*#j3hpht=_1Y4ew{m1Fnl4NsJJ=IQEa@GZKyd&G z_by2UJTChmS96!G?i(CK;2>+67fy|~%^#h&bk&-*>(-}PCcSFuyr_f!hU^x9B)MS8 uvgIq%tQKC7j5z(V>J3KXi8*OEsUyKz{2BI=Kah5rv|2eVkm&kTEdu~_M=-Mh diff --git a/app/src/main/res/mipmap-xxhdpi/ic_local_source.webp b/app/src/main/res/mipmap-xxhdpi/ic_local_source.webp index 1f10a36401eba8a3081d928d186e045bf14623ef..3e5d4498642fd9592dffe52248d7f4039b5451f0 100644 GIT binary patch literal 1476 zcmV;#1v~muNk&Gz1pok7MM6+kP&il$0000G0001w0055w06|PpNUZ??01c3A+qP*t zbFTeRdM~zZ+bS6~*7Fbflhj$WZCiEQDLtGuBJsZdVc%N=Cd!G0VSb~He*#Sau|9{TF|iWs7p!#gDstpY}U?L zv2(LSibpWaoQs^cU9%!1=}2o zM1VFXJD6}$E<`5Dj``{ONRlFfB=Q>|iHbuYM=$|GY$C^mkdWURRF3}p|G)qL`~SC7 zu?X2If|vk$&?P(fI}-_o%cHY%sT;sp006tM9k3^%QA(NXPNH_%qv$t?y039sQ=-U1P z>I0Qm)CZUkIIlZP=zNGDAa+Il_Q+4#*nu3*IP=vcC=tUMeZi+oW$C_Ir>D5K2bfu?RFYqAetPzb>TAB00EqPyd z@5C1|{?RG75hKq1eNHfX73d_@Lbl%K1)8<;VL+WEs9QV{p-a;7;KSRWWp2JoqtB@} zGFQz-td~{%v7*4}diFHK4D5Y%I&^Z=ou2*D(am#b+73m$|I?1?D?0&)MUOo>XVB-K zW&Xv_(-?kT0E0^|^6qg}#YulG+K(SCL!=leJUn zsOcP7v5aLBN^&-JsRqamSDEB-?*z)y89=6y{UmQnM_o&HVl4~yR+Cr6vJ=3bCStIn zrJ2H+$Nu0Wp08=|BwE4PkLSkx;v=fE=FOV%gVsrVf#Iz$Ootf?;4YU21`ydM4`mLp~_LhZGvuZ+n!I)r&PX7#+))S2tg1#hQ4^}x_kHJ zCwz=SF3`#8ig>FYJU7yq=^EibP{&l|Y2i}L43`?+HHwL5c9D4+Cz`AP`{MSPV~-zK zBtfCtpkPn{^d%2gdUa6qn?Neg@dSFuYZh2qb~!a!`qQnn$np83;(5VPW{?sDF>teO zJN8_^Hn36I7~pRitI7sWCNRouVcV%}^=9eheLn|o+c=UunMv8b-tTe`ptEgz8rgZY zbWWI=nW;;~Nrh$d>o|2PX6A~yWoDY${jO^)$+MTgN&RXx@<@mz$&n<_nmZDAFZfeb z6Oi1t5hPKWnce;WPxn3z2SiDd>NZOWxPZDLq za|hDt=?2t{J5;QuYys&x*bJ=SxckK33ntev0wfSXX`E!cKxT?wn$c%=FVVS?Js?CS znl4cQ&9tP;)K4)5G|6_H6lkI?ogfRezeb7^ip_M<7$4XNx)Q^uM8!$UX-+4pDiH}Q zV?8;0tfvgK*)Zy!Ise!V!aU&3q%B+1B_EBNf6i3Ju-Vh=DxV_CX55wu|1 zTF1p&C={gb=;zCYftXMWrQ_E*_p%GKk*ywk77b)sR$(B~WU@t83#9LG-RtO}Wq<{F zJC{eciWFvX^flb`A7%sTXf`1=Gg1lyCbFV|t)*avuW*-FK>M(}T#ba-0`0MB2lGb) zFyMg^VIY5Gl9dmw{(ir;lDN-Yi(29u5@D<$N+*B)pZ?p{Ckzorkv~<6?~cj+(Tbtk zy-)XF&p!9v?2QN6shZcWprjy;1eN$zFi=ekYwVEp`t{m#AKtyIuW)hCruE(LBC{@S za!3aVs!_~Hio{pKQWQxWAV2Wa-QR2PZg^!Y5u5CaX=|3xX^X&od9}NdfTnZ8+Gco} zMMqPWYcaEvSXfF;JP?>gYbuq*k76T{2ejA*iAb>4dGAir@LEa=sHkc_J9ioHJ5LVc zu-&cf&fv0%k@=5W%nj1i3&P|8Gpz5<1Rtm>o4XA~=h_B@)GVW^Kta^2R2Y`>VJJTJmPKD zwmYpZiH1#Qp2Lotm6j#LGC2S^Ni_ly{BNW(LsRpxEd^{w8gT&;2^(HbXvR?v{O{0& zJU}Wka}i$~AOaVVMuoC*;J=^8n$qpDR*JDxOhlN6Tx|?1S~SbpLGcyf0T!%Epva;u z7myI3nF_Oz3)JXTdH_a1)kSm*ivLO$`xg}_AWa?sZXl_tk%=)?Gg3SNvOFbX-yX-U5bV?X`XR;@Hl4oXKa`+_ot zRz@T*?+r-P@L;iJ5D&T3p{Wgz9|OtW)2>;k7}B zQKRGo4r&s?8ZDY{B!|}(RU+Wxxlg1(X(fw2s*mU51Q6sg$ps`Q>w=%Jm+2rGq^rE3 zR4?b%6-ZN!Nr8te-giy@JhyJ=%-N8G9w@MV>W2go`g66#zdc@t3-j3Z#6=`&lOwi+ z0|*0KA<5hRrp#KHc#`|BQY?-1uLb}1T-%ONq@4*hw6=KX?vannt_ijz^>l02!dDWXb6jFmc}D9FY>XnuxNAcB@X3ewi#L* z{IQ<9`#qo#E$69$8SzZc+xg!yNbQ`i(J$8~gICZjux!9nP?cE6+jvzZBbTzG*GfQcdmz~2e<1t!fI1L%sQocnHVbN+rL0WqcXIsgCw diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp b/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp index 80acaf13d04ac41ef363b7928c16c095f6c7b120..5cd7482c4f20b859091334658869e24f93e61787 100644 GIT binary patch literal 1774 zcmV{N>}9A9yR~WFs*}wmE-Yrr|1^?S0(W zm%c+pqcBlVI*}=P?1i`ARzYJx)U#G+VqW*-RaG{eG1C)QV{%^n(``jIlqqg?JQMVq zyWXo}a8b`;l3s8{1r1}&aGSNJ$x+|G9dibknMW~EkA1j+h7obssj|l_RSar~Za7so z`MjV(K*Z&z%1VDmF|3J9*BOxwf{BwRvq2D>E+#QTCk!#l34`OX>0&klW6A$Z{$KL{ zlK+?d|Np?}q{$Hwf2=rBeqR9@=Vu#Dl+V^@l)-B|Pn1{J0~{>qsY55q6YGc!91tJ= zaL^=qdiAvsY!Ha(rZXqW^&5d0LuOWea_;N- z=O5=zjLTQp4F3^_$*AUZ?}MlAHzA(CdHXH=r`Ry?5A%Xg@BI0|?blm=0{r>ao6oGe z-NxzcA4b?eooAoE@Xj}X(Zv=mx6U>@Y-}_C`Cu8XxgYCBm!G-60&Rt2(j>uWjOu^HdatLAe00E05Bf_odGJq0Kfn~Q6`Z^ zBcdV`n?)=*1q8FVaJATk4y{amQsx@@scrhJ?pyR8@DDKld7gRq(3lEcg8!$lQ+ydy zg9S2w8DCQS2L7$}ZuC3-vn|3AR~mC#8Ll&KKDWq4bPTSM`Vx^7|Yf(l*D0YXG9$u=o!`1pL18Fyv2G^S03U@2T8?aHF-}x~y4*^|N z_r}bWt+zNtQz-)ND3>wj0aTU8$PZLjNIyqoRIRXw29N*%{{I~mC})+SPWr}IoD!~ZiAoICGCQWI5x(~De zp#h@4^TDve5|w#_aR5Ng#b2`pP$E*cGyh~Vft5unibxbZG2+&D3>tXWEZY@=Z~ApU znEe_q@}%5~gS?);+f#@wMA!%n$e=Vp58&$UXwVY7?+w`Z$yOOJdyiKn`~}Q3=INTK zB5*=&{3V<#XzgXx6ijvVF786+Z6B`_% zI;fByDhRtmQ@^IOe=QYNZ3_dQR#V!wX01G_DgxP7i1kd{n)hAU6NM?@{rzR&kJ*#K z-eECsqI=pF{(MmSP?Y~fnNRH_Z0kWS$W406*a%2?!mfj$5K~wPd#uV-g8Co~?cS|L z?Pg90T6mhn6w}ILf!qA-e_?ZCP<-71tYk$;qHX9XHxvc^-=3z+#?o4x?oYQgGRZ_2 zM&QK3!$O9OfyHMB5mmk< ze&=82d0-Q@9&%odGV?b5i#3I$#(!@=$`~PGVev!p8|v<36K`g8ogWHpDMC;(u$1Xu zZZ{n?=JhWLU&WvqT@5cIdbrpHy?@hnP^k-EiV z*uqCrG^Cw$ocPs*DuS8um~@d%WRa4P;}IgHs;Qwnh2YkLLM6ps5|y&+;08Svd@m^?|or`nwGh+D+t$2}NX%_d!en8|IWY>T` zw_3KR7hV)I$eji(79cQekift0iq&f=tgzY{V=HO}aS%=9)-|xu=XGWe*%vEkgr8|V QO_Wk$tvsPr9D0BN0Duco=>Px# literal 2546 zcmV<|BMM6+kP&iE82><{uzrZgLO*m{LNm5k3a}V^8f51q+1D8NV z|0jS4uI{}8UOghvh`uEKN&}*R(w>PckrVqLoM}o<6ghGvO*s;AC**64$9%fC`t<^; z*;|YGh0{ioBq{v2GVwj5v7)qSF`G7$Bsp3f#UIyxchN#X$VQSTmj!r#x*|M6|0jT6 z>YYc4@WkCqDG`nyNGTBl5QH4NcheC?ejp+QhoglQjX^77AuKV0)_g1yi^!7Fgo)Ps zPecL{Km;9Iexpw(fB;B|?%REDd6f#w+ddhxZT;eGZQCB&_kXxE8zYSF)V6Kg zwz<7`JhwA4vpQFF&h9IzlQ-#p&~VLgRA`0osuHimaQsl`@p{j z=J;ULD}dj;B}dz~V~@`7+dypFPAVrgm*C7yHRu&;a7UFb$?S z7x^n|K|E{&iN#rDfLzW-0z^6JI2iCi=WqD}8M0+EKR^SR;Ed!O+#S|R zX5NK$oDz<3^8UPxSl^W=31a43X#xeDlf0LmAS6g)4BebzP6|I|&trK4RzS!BN6gHN z>6#V(#hq)jd4(a%tvpEitZA_#Wk8l#4#LPG^T^o%exMQEYy32OiW_zUydxY%m&Ar zuMZdX-sR;=ppunT+}k z=z!6o=OB87(9tHy>NqXZg-6O>%tighIRXGnuMI)Z3`9#OLZZ|=I8I?I_}leEVVq^( z-&l{M(X3H?ze;7p9Y4KVS%n4*w+J&PtUU;f6}RkX04UfX02$}va=BJqSq_M6=m2`% z%vFsA9;anQXIYlD30)iP$w#qWpeMVRt6$tZbT~$Lr6QpNV6%ossnMp^2^3@IBgFP~ zJswz%9YoStr3VQ;G1mOzcK#*6#)yJly;%rg_VfiY0$36oYknX5&T$VN5^WDtW>rY& zC?#5Y!v+E&sB~;lfDkm2tNBLO^U>->&d08pfgaqExHji{*t2Yc-mI}6mHgx~BAWzJ zBcg`}={eVudtBL!)J5*T6))3KU>3wy@5bB}Prh&)#fnuL^Ubs=(58J9j96F$P1^6m z=$A;E@uJeaj?o1`oL=64AHXXFh7r2-_2}!(xV5mf1EwEWrz+7$?JcdPyURh&;{cRyv7k>YwS84UI35sn(ra0B1jAb9p_X6A>a~_YG&&KxD5*T z_l0UoVAf(umqhplOkoHj1I8!RFo-buFm3@coD?`s7NNlS9|Yj=V|9JIV-jfC7CfWq zt|^(WgTgQ&FbJQ}h8k50jPrFD3gGqHSQg4K`6R?Qnd@t>$r|)e5(wL%(`1|PnKX(D z<9G!Sx*$x6VET=ZReEG zAj}=ylI+~?C%5!Ukdp5?nGFJ*lJRYuK?1+CnlyyPE$qD`*H<+~$3OsR+(JffQ{WeM zzJx9SV(x?m4aE7nH{27jMfhEJwHo(I!Bjy*fR5wvf7e5Xd9n&K>-$c|QMp0Al=pE)?&D)F2cz&(gd`h^kqE zhFi!&fnkbqeOc-Gx&xsAbN~T?@k@k;r3%$pA!*o;{a?4 z46mcA9$QSuWY{mUCWPxN0|)@|PoCFY*q|fbCF=LLoL20# z?%nqYfJuvcA~%OPX`B}bl0fraMi@vn4(z)C5H*6OzbnuvcC>z*MvabL8r$q+mpH}M z9V{zBS^7oWHV0yFX2gIUagS=`!{fZ-H3Yx+bs2?A3{M6rzec9dreYte0h2b|(= zIEbE;TbLTGQg=}2F5EA0k5e4lxw!y(4!iTsvK9qY@GNKWv3uU!!(U3*&xpx|^|dtSo2_B`7z45B_ahyghYRj; zzoz&X-tf$aE_AWw`<`Y|7q8z2b#(Q+na5ubyPOU9?f2jCGyMGGgy;gW)2D?^&u+7^ z4p<^8VlotKJi8X`Uao;_$Autph5tg3kf$&!Y{?iC;$Qrk6*6L!fq&xDtng<(j@pU% zIrnFUpYZ@B5iw)~;p3e9JLZH)5C&{fwtNAvzRSfqAzr{s5VAz;W)*;yH?jAZm=PjD z$Xt7i!np;@ujKWI+58*K2itfhufZ}{$(26hFW)lrCER!r^H*oh)i4|Vcf5EfuLz>d zlD_VKGRfp~Irl{VUX}G%!g-iUYrn;>*!C74*DbDzATv$%r*rtm9KStt=hA;34&wlJ zV+P*-DE^JV;~!l80tdfC>*_DN#{RiT@^yT88;2gikDD`eh*nT?7O4}!8UBH<|KZ6u zxb%LqK?|rXg~qPF0z~mzK74{}f8hC1zMtSfh;q{xfdNuL5oiK!pb|vD*0#L { kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } } @@ -44,8 +44,8 @@ subprojects { } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 isCoreLibraryDesugaringEnabled = true } diff --git a/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt b/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt index b1e53fde90..2c352f1319 100644 --- a/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt +++ b/buildSrc/src/main/kotlin/LocalesConfigPlugin.kt @@ -3,25 +3,22 @@ import org.gradle.api.Task import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.TaskContainerScope +private val emptyResourcesElement = "\\s*|".toRegex() +private val valuesPrefix = "values(-(b\\+)?)?".toRegex() + fun TaskContainerScope.registerLocalesConfigTask(project: Project): TaskProvider { return with(project) { register("generateLocalesConfig") { - val emptyResourcesElement = "\\s*|".toRegex() - val valuesPrefix = "values-?".toRegex() - val languages = fileTree("$projectDir/src/main/res/") - .matching { - include("**/strings.xml") - } - .filterNot { - it.readText().contains(emptyResourcesElement) - } + .matching { include("**/strings.xml") } + .filterNot { it.readText().contains(emptyResourcesElement) } .map { it.parentFile.name } .sorted() .joinToString(separator = "\n") { val language = it .replace(valuesPrefix, "") .replace("-r", "-") + .replace("+", "-") .takeIf(String::isNotBlank) ?: "en" " " } diff --git a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt index 2362b78c60..1de824381b 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/network/interceptor/UncaughtExceptionInterceptor.kt @@ -18,7 +18,11 @@ class UncaughtExceptionInterceptor : Interceptor { return try { chain.proceed(chain.request()) } catch (e: Exception) { - throw IOException(e) + if (e is IOException) { + throw e + } else { + throw IOException(e) + } } } } diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt index 7f3f6eaaf5..57950eda34 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/system/DeviceUtil.kt @@ -14,15 +14,15 @@ object DeviceUtil { /** * Extracts the MIUI major version code from a string like "V12.5.3.0.QFGMIXM". * - * @return MIUI major version code (e.g., 13) or -1 if can't be parsed. + * @return MIUI major version code (e.g., 13) or null if can't be parsed. */ val miuiMajorVersion by lazy { - if (!isMiui) return@lazy -1 + if (!isMiui) return@lazy null Build.VERSION.INCREMENTAL .substringBefore('.') .trimStart('V') - .toIntOrNull() ?: -1 + .toIntOrNull() } @SuppressLint("PrivateApi") @@ -45,6 +45,20 @@ object DeviceUtil { Build.MANUFACTURER.equals("samsung", ignoreCase = true) } + val oneUiVersion by lazy { + try { + val semPlatformIntField = Build.VERSION::class.java.getDeclaredField("SEM_PLATFORM_INT") + val version = semPlatformIntField.getInt(null) - 90000 + if (version < 0) { + 1.0 + } else { + ((version / 10000).toString() + "." + version % 10000 / 100).toDouble() + } + } catch (e: Exception) { + null + } + } + val invalidDefaultBrowsers = listOf( "android", "com.huawei.android.internal.app", diff --git a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt index 638f33726c..5f29c7d853 100644 --- a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt +++ b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt @@ -7,6 +7,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.BitmapRegionDecoder import android.graphics.Color +import android.graphics.Matrix import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable @@ -151,6 +152,23 @@ object ImageUtil { return ByteArrayInputStream(output.toByteArray()) } + fun rotateImage(imageStream: InputStream, degrees: Float): InputStream { + val imageBytes = imageStream.readBytes() + + val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + val rotated = rotateBitMap(imageBitmap, degrees) + + val output = ByteArrayOutputStream() + rotated.compress(Bitmap.CompressFormat.JPEG, 100, output) + + return ByteArrayInputStream(output.toByteArray()) + } + + private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap { + val matrix = Matrix().apply { postRotate(degrees) } + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } + /** * Split the image into left and right parts, then merge them into a new image. */ diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 76832447f1..da6715940c 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -22,5 +22,5 @@ dependencies { api(libs.sqldelight.android.paging) - testImplementation(libs.junit) + testImplementation(libs.bundles.test) } diff --git a/domain/src/main/java/tachiyomi/domain/backup/service/PreferenceValues.kt b/domain/src/main/java/tachiyomi/domain/backup/service/PreferenceValues.kt index fee6400eef..148cf5d101 100644 --- a/domain/src/main/java/tachiyomi/domain/backup/service/PreferenceValues.kt +++ b/domain/src/main/java/tachiyomi/domain/backup/service/PreferenceValues.kt @@ -4,3 +4,6 @@ const val FLAG_CATEGORIES = "1" const val FLAG_CHAPTERS = "2" const val FLAG_HISTORY = "4" const val FLAG_TRACK = "8" +const val FLAG_SETTINGS = "10" +const val FLAG_EXT_SETTINGS = "20" +const val FLAG_EXTENSIONS = "40" diff --git a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt index 922d7a9f06..0fbd603416 100644 --- a/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt @@ -18,6 +18,8 @@ class DownloadPreferences( fun saveChaptersAsCBZ() = preferenceStore.getBoolean("save_chapter_as_cbz", true) + fun splitTallImages() = preferenceStore.getBoolean("split_tall_images", false) + fun autoDownloadWhileReading() = preferenceStore.getInt("auto_download_while_reading", 0) fun autoDownloadWhileWatching() = preferenceStore.getInt("auto_download_while_watching", 0) diff --git a/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt b/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt new file mode 100644 index 0000000000..e2485f9de9 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/items/service/MissingItems.kt @@ -0,0 +1,61 @@ +package tachiyomi.domain.items.service + +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.episode.model.Episode +import kotlin.math.floor + +fun List.missingItemsCount(): Int { + if (this.isEmpty()) { + return 0 + } + + val items = this + // Ignore unknown item numbers + .filter { it != -1f } + // Convert to integers, as we cannot check if 16.5 is missing + .map(Float::toInt) + // Only keep unique chapters so that -1 or 16 are not counted multiple times + .distinct() + .sorted() + + if (items.isEmpty()) { + return 0 + } + + var missingItemsCount = 0 + var previousItem = 0 // The actual chapter number, not the array index + + // We go from 0 to lastChapter - Make sure to use the current index instead of the value + for (i in items.indices) { + val currentItem = items[i] + if (currentItem > previousItem + 1) { + // Add the amount of missing chapters + missingItemsCount += currentItem - previousItem - 1 + } + previousItem = currentItem + } + + return missingItemsCount +} + +fun calculateChapterGap(higherChapter: Chapter?, lowerChapter: Chapter?): Int { + if (higherChapter == null || lowerChapter == null) return 0 + if (!higherChapter.isRecognizedNumber || !lowerChapter.isRecognizedNumber) return 0 + return calculateChapterGap(higherChapter.chapterNumber, lowerChapter.chapterNumber) +} + +fun calculateChapterGap(higherChapterNumber: Float, lowerChapterNumber: Float): Int { + if (higherChapterNumber < 0f || lowerChapterNumber < 0f) return 0 + return floor(higherChapterNumber).toInt() - floor(lowerChapterNumber).toInt() - 1 +} + +fun calculateEpisodeGap(higherEpisode: Episode?, lowerEpisode: Episode?): Int { + if (higherEpisode == null || lowerEpisode == null) return 0 + if (!higherEpisode.isRecognizedNumber || !lowerEpisode.isRecognizedNumber) return 0 + return calculateChapterGap(higherEpisode.episodeNumber, lowerEpisode.episodeNumber) +} + +fun calculateEpisodeGap(higherEpisodeNumber: Float, lowerEpisodeNumber: Float): Int { + if (higherEpisodeNumber < 0f || lowerEpisodeNumber < 0f) return 0 + return floor(higherEpisodeNumber).toInt() - floor(lowerEpisodeNumber).toInt() - 1 +} diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index be93b2410f..d19deccec3 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -25,7 +25,7 @@ class LibraryPreferences( fun libraryAnimeSortingMode() = preferenceStore.getObject("animelib_sorting_mode", AnimeLibrarySort.default, AnimeLibrarySort.Serializer::serialize, AnimeLibrarySort.Serializer::deserialize) - fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 24) + fun libraryUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0) fun libraryUpdateLastTimestamp() = preferenceStore.getLong("library_update_last_timestamp", 0L) diff --git a/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt b/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt index f7be94b511..431be6974d 100644 --- a/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt +++ b/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt @@ -1,6 +1,6 @@ package tachiyomi.domain.items.chapter.service -import org.junit.jupiter.api.Assertions.assertEquals +import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode @@ -261,7 +261,6 @@ class ChapterRecognitionTest { } private fun assertChapter(mangaTitle: String, name: String, expected: Float) { - val chapterNumber = ChapterRecognition.parseChapterNumber(mangaTitle, name) - assertEquals(chapterNumber, expected) + ChapterRecognition.parseChapterNumber(mangaTitle, name) shouldBe expected } } diff --git a/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt b/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt new file mode 100644 index 0000000000..2007beffc1 --- /dev/null +++ b/domain/src/test/java/tachiyomi/domain/items/service/MissingItemsTest.kt @@ -0,0 +1,84 @@ +package tachiyomi.domain.items.service + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode +import tachiyomi.domain.items.chapter.model.Chapter +import tachiyomi.domain.items.episode.model.Episode + +@Execution(ExecutionMode.CONCURRENT) +class MissingItemsTest { + + @Test + fun `missingItemsCount returns 0 when empty list`() { + emptyList().missingItemsCount() shouldBe 0 + } + + @Test + fun `missingItemsCount returns 0 when all unknown item numbers`() { + listOf(-1f, -1f, -1f).missingItemsCount() shouldBe 0 + } + + @Test + fun `missingItemsCount handles repeated base item numbers`() { + listOf(1f, 1.0f, 1.1f, 1.5f, 1.6f, 1.99f).missingItemsCount() shouldBe 0 + } + + @Test + fun `missingItemsCount returns number of missing items`() { + listOf(-1f, 1f, 2f, 2.2f, 4f, 6f, 10f, 11f).missingItemsCount() shouldBe 5 + } + + @Test + fun `calculateChapterGap returns difference`() { + calculateChapterGap(chapter(10f), chapter(9f)) shouldBe 0f + calculateChapterGap(chapter(10f), chapter(8f)) shouldBe 1f + calculateChapterGap(chapter(10f), chapter(8.5f)) shouldBe 1f + calculateChapterGap(chapter(10f), chapter(1.1f)) shouldBe 8f + + calculateChapterGap(10f, 9f) shouldBe 0f + calculateChapterGap(10f, 8f) shouldBe 1f + calculateChapterGap(10f, 8.5f) shouldBe 1f + calculateChapterGap(10f, 1.1f) shouldBe 8f + } + + @Test + fun `calculateChapterGap returns 0 if either are not valid chapter numbers`() { + calculateChapterGap(chapter(-1f), chapter(10f)) shouldBe 0 + calculateChapterGap(chapter(99f), chapter(-1f)) shouldBe 0 + + calculateChapterGap(-1f, 10f) shouldBe 0 + calculateChapterGap(99f, -1f) shouldBe 0 + } + + private fun chapter(number: Float) = Chapter.create().copy( + chapterNumber = number, + ) + + @Test + fun `calculateEpisodeGap returns difference`() { + calculateEpisodeGap(episode(10f), episode(9f)) shouldBe 0f + calculateEpisodeGap(episode(10f), episode(8f)) shouldBe 1f + calculateEpisodeGap(episode(10f), episode(8.5f)) shouldBe 1f + calculateEpisodeGap(episode(10f), episode(1.1f)) shouldBe 8f + + calculateEpisodeGap(10f, 9f) shouldBe 0f + calculateEpisodeGap(10f, 8f) shouldBe 1f + calculateEpisodeGap(10f, 8.5f) shouldBe 1f + calculateEpisodeGap(10f, 1.1f) shouldBe 8f + } + + @Test + fun `calculateEpisodeGap returns 0 if either are not valid episode numbers`() { + calculateEpisodeGap(episode(-1f), episode(10f)) shouldBe 0 + calculateEpisodeGap(episode(99f), episode(-1f)) shouldBe 0 + + calculateEpisodeGap(-1f, 10f) shouldBe 0 + calculateEpisodeGap(99f, -1f) shouldBe 0 + } + + private fun episode(number: Float) = Episode.create().copy( + episodeNumber = number, + ) +} diff --git a/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt b/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt index 7703c5bba3..26b155996a 100644 --- a/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt +++ b/domain/src/test/java/tachiyomi/domain/library/model/LibraryFlagsTest.kt @@ -1,20 +1,23 @@ package tachiyomi.domain.library.model -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotEquals +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.Execution +import org.junit.jupiter.api.parallel.ExecutionMode import tachiyomi.domain.library.anime.model.AnimeLibrarySort import tachiyomi.domain.library.manga.model.MangaLibrarySort +@Execution(ExecutionMode.CONCURRENT) class LibraryFlagsTest { @Test fun `Check the amount of flags`() { - assertEquals(4, LibraryDisplayMode.values.size) - assertEquals(8, MangaLibrarySort.types.size) - assertEquals(2, MangaLibrarySort.directions.size) - assertEquals(8, AnimeLibrarySort.types.size) - assertEquals(2, AnimeLibrarySort.directions.size) + LibraryDisplayMode.values.size shouldBe 4 + MangaLibrarySort.types.size shouldBe 8 + MangaLibrarySort.directions.size shouldBe 2 + AnimeLibrarySort.types.size shouldBe 8 + AnimeLibrarySort.directions.size shouldBe 2 } @Test @@ -23,7 +26,7 @@ class LibraryFlagsTest { val new = LibraryDisplayMode.CoverOnlyGrid val flag = current + new - assertEquals(0b00000011, flag) + flag shouldBe 0b00000011 } @Test @@ -35,8 +38,8 @@ class LibraryFlagsTest { val mangaflag = mangacurrent + newmanga val animeflag = animecurrent + newanime - assertEquals(0b01011100, mangaflag) - assertEquals(0b01011100, animeflag) + mangaflag shouldBe 0b01011100 + animeflag shouldBe 0b01011100 } @Test @@ -47,8 +50,8 @@ class LibraryFlagsTest { val mangaflag = display + mangasort val animeflag = display + animesort - assertEquals(0b01011111, mangaflag) - assertEquals(0b01011111, animeflag) + mangaflag shouldBe 0b01011111 + animeflag shouldBe 0b01011111 } @Test @@ -65,12 +68,12 @@ class LibraryFlagsTest { val animesort = AnimeLibrarySort(AnimeLibrarySort.Type.DateAdded, AnimeLibrarySort.Direction.Ascending) val animeflag = currentanimeFlag + display + animesort - assertEquals(0b00001110, currentmangaFlag) - assertEquals(0b01011111, mangaflag) - assertNotEquals(currentmangaFlag, mangaflag) - assertEquals(0b00001110, currentanimeFlag) - assertEquals(0b01011111, animeflag) - assertNotEquals(currentanimeFlag, animeflag) + currentmangaFlag shouldBe 0b00001110 + mangaflag shouldBe 0b01011111 + mangaflag shouldNotBe currentmangaFlag + currentanimeFlag shouldBe 0b00001110 + animeflag shouldBe 0b01011111 + animeflag shouldNotBe currentanimeFlag } @Test @@ -81,7 +84,7 @@ class LibraryFlagsTest { val mangaflag = display + mangasort.type + mangasort.direction val animeflag = display + animesort.type + animesort.direction - assertEquals(0b01000000, mangaflag) - assertEquals(0b01000000, animeflag) + mangaflag shouldBe 0b01000000 + animeflag shouldBe 0b01000000 } } diff --git a/gradle.properties b/gradle.properties index f84d9dfb74..097bd50251 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,7 +20,9 @@ org.gradle.parallel=true org.gradle.caching=true -# AndroidX support -android.useAndroidX=true - kotlin.mpp.androidSourceSetLayoutVersion=2 + +android.useAndroidX=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index de27923dd6..56b1522536 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,27 +1,27 @@ [versions] -agp_version = "7.4.2" -lifecycle_version = "2.6.0" +agp_version = "8.0.0" +lifecycle_version = "2.6.1" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } -annotation = "androidx.annotation:annotation:1.6.0" +annotation = "androidx.annotation:annotation:1.7.0-alpha02" appcompat = "androidx.appcompat:appcompat:1.6.1" biometricktx = "androidx.biometric:biometric-ktx:1.2.0-alpha05" constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0" -corektx = "androidx.core:core-ktx:1.10.0-rc01" +corektx = "androidx.core:core-ktx:1.11.0-alpha03" splashscreen = "androidx.core:core-splashscreen:1.0.0-alpha02" recyclerview = "androidx.recyclerview:recyclerview:1.3.0" viewpager = "androidx.viewpager:viewpager:1.1.0-alpha01" glance = "androidx.glance:glance-appwidget:1.0.0-alpha03" -profileinstaller = "androidx.profileinstaller:profileinstaller:1.2.2" +profileinstaller = "androidx.profileinstaller:profileinstaller:1.3.0" lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref = "lifecycle_version" } lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" } lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" } -work-runtime = "androidx.work:work-runtime-ktx:2.8.0" +work-runtime = "androidx.work:work-runtime-ktx:2.8.1" guava = "com.google.guava:guava:31.1-android" paging-runtime = "androidx.paging:paging-runtime:3.1.1" diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index a18f82e436..73cdd7e3bf 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,10 +1,10 @@ [versions] -compiler = "1.4.3" -compose-bom = "2023.02.00-rc02" -accompanist = "0.29.1-alpha" +compiler = "1.4.6" +compose-bom = "2023.03.00" +accompanist = "0.30.1" [libraries] -activity = "androidx.activity:activity-compose:1.6.1" +activity = "androidx.activity:activity-compose:1.7.1" bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } foundation = { module = "androidx.compose.foundation:foundation" } animation = { module = "androidx.compose.animation:animation" } diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index d462eb7069..16704bf3c7 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlin_version = "1.8.10" +kotlin_version = "1.8.20" serialization_version = "1.5.0" xml_serialization_version = "0.85.0" @@ -10,6 +10,7 @@ gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = " coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version = "1.6.4" } coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" } coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android" } +coroutines-guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava" } serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization_version" } serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization_version" } @@ -18,7 +19,7 @@ serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-android", v serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-android", version.ref = "xml_serialization_version" } [bundles] -coroutines = ["coroutines-core", "coroutines-android"] +coroutines = ["coroutines-core", "coroutines-android", "coroutines-guava"] serialization = ["serialization-json", "serialization-json-okio", "serialization-protobuf", "serialization-xml-core", "serialization-xml"] [plugins] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 09ab9a065a..12f030c920 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -aboutlib_version = "10.6.1" +aboutlib_version = "10.6.2" okhttp_version = "5.0.0-alpha.11" -coil_version = "2.2.2" +coil_version = "2.3.0" shizuku_version = "12.2.0" -sqlite = "2.3.0" +sqlite = "2.3.1" sqldelight = "1.5.5" leakcanary = "2.10" voyager = "1.0.0-rc07" richtext = "0.16.0" [libraries] -desugar = "com.android.tools:desugar_jdk_libs:2.0.2" +desugar = "com.android.tools:desugar_jdk_libs:2.0.3" android-shortcut-gradle = "com.github.zellius:android-shortcut-gradle-plugin:0.1.2" google-services-gradle = "com.google.gms:google-services:4.3.15" google-crashlytics-gradle = "com.google.firebase:firebase-crashlytics-gradle:2.8.0" @@ -47,7 +47,7 @@ coil-core = { module = "io.coil-kt:coil", version.ref = "coil_version" } coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil_version" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil_version" } -subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:846abe0" +subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:c8e2650" image-decoder = "com.github.tachiyomiorg:image-decoder:7879b45" natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" @@ -62,13 +62,13 @@ photoview = "com.github.chrisbanes:PhotoView:2.3.0" directionalviewpager = "com.github.tachiyomiorg:DirectionalViewPager:1.0.0" insetter = "dev.chrisbanes.insetter:insetter:0.6.1" compose-cascade = "me.saket.cascade:cascade-compose:2.0.0-rc02" -compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.10.4" +compose-materialmotion = "io.github.fornewid:material-motion-compose-core:0.11.3" compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0" logcat = "com.squareup.logcat:logcat:0.1" acra-http = "ch.acra:acra-http:5.9.7" -firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.2.0" +firebase-analytics = "com.google.firebase:firebase-analytics-ktx:21.2.2" firebase-crashlytics = "com.google.firebase:firebase-crashlytics-ktx:18.3.1" aboutLibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutlib_version" } @@ -87,6 +87,7 @@ sqldelight-android-paging = { module = "com.squareup.sqldelight:android-paging3- sqldelight-gradle = { module = "com.squareup.sqldelight:gradle-plugin", version.ref = "sqldelight" } junit = "org.junit.jupiter:junit-jupiter:5.9.2" +kotest-assertions = "io.kotest:kotest-assertions-core:5.6.1" voyager-navigator = { module = "ca.gosyer:voyager-navigator", version.ref = "voyager" } voyager-tab-navigator = { module = "ca.gosyer:voyager-tab-navigator", version.ref = "voyager" } @@ -109,3 +110,4 @@ coil = ["coil-core", "coil-gif", "coil-compose"] shizuku = ["shizuku-api", "shizuku-provider"] voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"] richtext = ["richtext-commonmark", "richtext-m3"] +test = ["junit", "kotest-assertions"] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1e..37aef8d3f0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d421c..c8b0079d93 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -197,6 +194,9 @@ if "$cygwin" || "$msys" ; then done fi + # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/i18n/src/main/res/values-am/strings.xml b/i18n/src/main/res/values-am/strings.xml index 7d74524cf9..816a12117a 100644 --- a/i18n/src/main/res/values-am/strings.xml +++ b/i18n/src/main/res/values-am/strings.xml @@ -105,7 +105,6 @@ እንደገና ሞክር ቀጣይ ምዕራፍ ቀዳሚ ምዕራፍ - ተው ለአፍታ አቁም ምዕራፎችን ይመልከቱ ሽፋን አርትዕ diff --git a/i18n/src/main/res/values-ar/strings-aniyomi.xml b/i18n/src/main/res/values-ar/strings-aniyomi.xml index ea5e7658e9..812bf70965 100644 --- a/i18n/src/main/res/values-ar/strings-aniyomi.xml +++ b/i18n/src/main/res/values-ar/strings-aniyomi.xml @@ -5,7 +5,7 @@ آخر تحديث للمانجا عرض المانجا المانجا المحلية - مع فصل (فصول) غير مقروءة / حلقة (حلقات) غير مشاهدة + فيها فصول لم تُقرأ إظهار عدد الغير مقروء/ الغير مشاهد على أيقونة التحديثات فئة المانغا الإفتراضية المانجا في الفئات المستبعدة لن يتم تحديثها حتى وإن كانت أيضا في الفئات المضمنة. @@ -34,7 +34,7 @@ تطبيق أيضًا على جميع المانجا في المكتبة إعادة تعيين جميع الفصول لهذه المانجا تعذّر تنزيل الفصول بسبب انخفاض مساحة التخزين - تحذير: التحميلات كبيرة الحجم والعدد يمكن أن تؤدي إلى تباطؤ المصادر و/أو ان تقوم المصادر بحظر Tachiyomi۔ اضغط لمعرفة المزيد۔ + تحذير: يمكن أن تؤدِّي التنزيلات كبيرة الحجم والعدد إلى إبطاء المصادر، وقد يُحظر Tachiyomi منها بسبب ذلك. اضغط لمعرفة المزيد۔ يتطلب Tachiyomi وجود WebView تم إيقاف التنزيل مؤقتاً تحديثات الفصول @@ -120,7 +120,7 @@ الحلقة التالية غير موجودة! المستشعر الرأسي %1$s: %2$s، %3$s - إظهار زر \"متابعة المشاهدة/القراءة\" + زرُّ متابعة القراءة غير مشاهد تعديل فئات الأنمي انقل \"المانغا\" إلى علامة التبويب \"المزيد\" diff --git a/i18n/src/main/res/values-ar/strings.xml b/i18n/src/main/res/values-ar/strings.xml index 3e74dbd34c..fda7f3e5f0 100644 --- a/i18n/src/main/res/values-ar/strings.xml +++ b/i18n/src/main/res/values-ar/strings.xml @@ -4,7 +4,7 @@ مانجا الفصول التعقب - السجل + التاريخ اﻹعدادات المكتبة السجل @@ -36,7 +36,6 @@ إعادة تسمية الفئة حدد الفئات تعديل صورة الغلاف - إيقاف إيقاف موقت الفصل السابق الفصل التالي @@ -66,8 +65,8 @@ اﻹعدادات المتقدمة حول التطبيق العناصر لكل صف - رأسي - افقي + طوليٌّ + عرضيٌّ التحديثات التلقائية إيقاف كل ٦ ساعات @@ -78,7 +77,8 @@ الكل تقييد التحديث التلقائي للجهاز عند الشحن - مع حالة \"مكتمل\" + حالها «تمَّت» + الفئة المبدئية السؤال دائماً وضع الشاشة الكاملة مؤثرات الانتقال بين الصفحات @@ -94,9 +94,9 @@ أبيض أسود وضع القراءة الافتراضي - من اليسار لليمين - من اليمين لليسار - عموديا + يسارًا فيمينًا + يمينًا فيسارًا + عموديٌّ الويبتون كيفية ضبط الأبعاد ملائمة الشاشة @@ -132,11 +132,11 @@ النسخ الاحتياطيّة التلقائيّة معدل النسخ الإحتياطي أقصى عدد للنسخ الاحتياطيّة - تم إنشاء النسخة الإحتياطية + أُنشئت نسخة احتياطية اكتملت الاستعادة ما الذي تريد نسخه احتياطيّاً؟ - جار استعادة النسخة الاحتياطية - جار إنشاء النسخة الاحتياطية + تُستعاد النسخة الاحتياطية + تُنشأ النسخة الاحتياطية بحث شامل R G @@ -159,16 +159,16 @@ تم تسجيل الدخول خطأ غير معروف جار تحديث الفئة - لا يوجد نتائج أخرى - مصدر محلّي + لا توجد نتائج أخرى + مصدر محلِّي أخرى البحث الشامل… - اﻷخيرة - تصفّح - مُستمرّة - غير معروف - مُرخّصة - إزالة من المكتبة + الأحدث + تصفَّح + مستمرَّة + مجهول + مرخَّصة + أزل من المكتبة حذف الفصول المنزلة؟ الفصل %1$s جاري التنزيل (%1$d/%2$d) @@ -181,24 +181,24 @@ غير مقروء هل أنت متأكد من أنك تريد حذف الفصول المحددة؟ التتبع - أقرأها - مكتملة + القراءة + تمَّت متروكة - معلّقة - أنوي قرأتها + معلَّقة + أنوي قراءتها التقييم العنوان - الحالة + الحال توجد بالفعل فئة بهذا الاسم! تم حذف الفئات سيؤدي هذا إلى إزالة تاريخ قراءة هذا الفصل. هل أنت متأكد؟ تم حفظ الصورة مُرشح مخصص - تعيين كغلاف - تم تحديث صورة الغلاف + عيِّنها غلافًا + حُدِّث الغلاف الصفحة: %1$d لم يتم العثور على الفصل التالي - تعذّر تحميل الصورة + تعذَّر تحميل هذه الصورة هل تريد تعيين هذه الصورة كغلاف؟ فشل تنزيل الفصول. يمكنك إعادة المحاولة في قسم التنزيلات تم إيجاد فصول جديدة @@ -236,7 +236,7 @@ الثقة غير موثوق فيه إلغاء التثبيت - إضافة غير موثوق بها + إضافة ذات ريبة سرعة مؤثر النقر المزدوج عارض الصفحات لا مؤثرات @@ -253,7 +253,7 @@ تم البدء فيها النوع اختر بيانات لتضمينها - ترحيل + رحِّل نسخ ليست لديك أيّة فئات، اضغط زر اﻹضافة لإنشاء واحدة لتنظيم مكتبتك. تمَّ الانتهاء من: @@ -275,7 +275,7 @@ مُرشح الألوان ”وضع الألوان الممزوجة“ مراوغة / تفتيح حرق / تغميق - لم يتم العثور على أيّة نتائج + لم يُعثر على أيِّ نتائج اختيار مصدر للترحيل من عودة تقدم @@ -294,10 +294,10 @@ نقل إلى الأسفل أبدًا دائماً - قفل في حالة السكون - اطلب فكَّ القفل + أوصد في حالة السكون + اطلب فتح القفل الأمان و الخصوصية - إدارة الإشعارات + أدر الإشعارات صيغة التاريخ اتبع مظهر النظام مفعّل @@ -315,23 +315,23 @@ التحديثات معلقة التحديث العام العرض - اخفاء محتوى الإشعارات - إخفاء محتوى الشاشة عند تغير التطبيقات و منع لقط الشاشة + أخفِ محتوى الإشعارات + تُخفي الشاشة الآمنة محتوى التطبيق عند التبديل بين التطبيقات، وتمنع التقاط الشاشة كذلك الشاشة الآمنة المُنزل فقط - تحقق من وجود تحديثات + تحقَّق من وجود تحديثات التراخيص مفتوحة المصدر الموقع الإلكتروني - تم الغاء الاستعادة + أُلغيت الاستعادة فشل استعادة النسخ الاحتياطي الاستعادة قيد التقدم بالفعل فشل النسخ الاحتياطي - النسخ الاحتياطي قيد التقدم بالفعل + يُنسخ احتياطيًّا بالفعل %02d دقيقة و %02d ثانية - 25% - 20% - 15% - 10% + ٢٥٪ + ٢٠٪ + ١٥٪ + ١٠٪ بلا تأكيد الخروج إلغاء التثبيت @@ -378,16 +378,16 @@ جارٍ التحقق من وجود فصول جديدة الفصل %1$s - %2$s - يجري تحديث المكتبة + تُحدَّث المكتبة وضع القراءة القسم الخاص بهذه السلسلة - متوقّف - إضافة تتبع + متوقِّفة + أضف تتبُّعًا أقل المزيد في المكتبة إضافة إلى المكتبة - دليل استخدام ميزة المصدر المحلي + دليل استخدام المصادر المحلية المثبتة آخر مصدر مُستخدم التحقق من الموقع الإلكتروني في WebView @@ -430,25 +430,24 @@ البيانات المصادر المفقودة: النسخة الإحتياطية لا تحتوي على أيّة مانجا. - ملف النسخ الاحتياطي غير صالح + ملفُّ النسخ الاحتياطيِّ غير صالح مزامنة أحاديّة لتحديث تقدم الفصول في خدمات التتبع. قم بإعداد التتبع الخاص بمانجا محدّدة من زر التتبع الخاص بها. - هذه الإضافة ليست من قائمة إضافات Tachiyomi الرسميّة. + هذه الإضافة ليست من قائمة إضافات Tachiyomi الرسمية. غير رسمي تحقق من وجود غلاف جديد وتفاصيل جديدة عند تحديث المكتبة تحديث البيانات الوصفية تلقائياً - تم في %1$s مع %2$sأخطاء - تم في %1$s ووجد خطأ واحد - تم في %1$s ووجد اثنين من الأخطاء - تم في %1$s ووجد %2$s من الأخطاء - تم في %1$s ووجد %2$s من الأخطاء - تم في %1$s ووجد %2$s من الأخطاء + تمَّ في %1$s وبدون أخطاء + تمَّ في %1$s وفيه خطأ + تمَّ في %1$s وفيه خطآن + تمَّ في %1$s وفيه %2$s أخطاء + تمَّ في %1$s وفيه %2$s خطأً + تمَّ في %1$s وفيه %2$s خطأ حسب تاريخ الرفع حدّث صور اغلفة المكتبة شبكة مريحة قوائم - شارات إظهار قوائم الفئة لم يتم العثور على أيّة صفحات تفعيل الكل @@ -499,12 +498,12 @@ إبحث عن \"%1$s\" بشكلٍ شامل حُدِّث إلى الإصدار v%1$s - لم يتم تخطِّي أي فصل - تم تخطِّي فصل - تم تخطِّي فصلان - تم تخطِّي بعض الفصول - تم تخطِّي فصول - غير ذلك + لا يُتخطَّى أيُّ فصل + يُتخطَّى فصل، وذلك إما لأن المصدر مفقود أو لأنه مصفًّى + يُتخطَّى فصلان، وذلك إما لأن المصدر مفقود أو لأنهما مصفَّان + تُتخطَّى %d فصول، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون + يُتخطَّى %d فصلًا، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون + يُتخطَّى %d فصل، وذلك إما لأن المصدر مفقود أو لأنهم مصفَّون لم يتم العثور على الفصول %1$s: %2$s, صفحة %3$d @@ -513,9 +512,9 @@ هل أنت متأكد أنك تريد حفظ هذه الإعدادات كافتراضي؟ إعدادات الفصل إعدادات البحث - الفصول المنزّلة - هل أنت متأكد؟ سيتم حذف السجل. - تم حذف السجل + الفصول المنزَّلة + أمتأكِّد؟ سوف يُحذف التاريخ. + حُذف التاريخ الوضع المخفي مسح السجل الصفحة التالية @@ -526,18 +525,16 @@ رجاء سجل دخولك في MAL مجدداً لم يتم العثور على تطبيق منتقي الملفات مناطق اللمس - على شكل حرف L - حافة - كيندل العش + في شكل حرف L + حافَّة + كأنه كِندِل مُتتبَعة إظهار عدد العناصر - سجلات الاخطاء تاريخ انهاء القراءة تاريخ بدء القراءة - تم حفظ سجلات الأخطاء يحفظ سجلات الأخطاء في ملف للمشاركة مع المطورين - استخراج سجلات الاخطاء - يميناً و يساراً + شارك سجلَّات الانهيار + يميناً ويساراً إذا كان موضع تقسيم الصفحات العريضة لا يتطابق مع اتجاه القراءة عكس موضع تقسيم الصفحة تقسيم الصفحات العريضة @@ -574,7 +571,7 @@ فشل النسخ إلى الحافظة نسخة الاندرويد هذه اصبحت غير مدعومة المصدر غير مدعوم - غير مقروء + يُقرأ التاريخ ترتيب حسب لم يتم العثور على الفصل @@ -582,9 +579,9 @@ تشغيل قيود: %s لا يوجد مطابقات - فشل مشاركة الغطاء - فشل حفظ الغطاء - تم حفظ صورة الغطاء + فشلت مشاركة الغلاف + خطأ في حفظ الغلاف + حُفظ الغلاف غطاء صيغة الفصل غير صالح تعليمات التعقب @@ -598,11 +595,11 @@ منتصف الليل أخضر مظهر التطبيق - أَدان - أدنى - أعلى - أعلَى - حساسية التمرير لاخفاء القائمة + أدنى + دنيا + عالية + أعلى + حساسية التمرير لإخفاء القائمة وضع داكن الأسود النقي يوتسوبا ين & يانغ @@ -611,9 +608,9 @@ جار تحديث المكتبة…( (%2$d) / (%1$d) ) هذا المتتبّع متوافق فقط مع المصدر (‌Komga)۔ تقوم بعض الشركات المصنعة بوضع قيود إضافية على التطبيقات التي قد تقضي على الخدمات التي تعمل في الخلفية. يحتوي هذا الموقع على مزيد من المعلومات حول كيفية إصلاحه. - النسخ الاحتياطي/الإستعادة ربما لا يعمل بطريقه صحيحة عندما يكون خيار MIUI Optimization غير مفعل. + قد لا يعمل النسخ الاحتياطيُّ أو الاستعادة إن عطِّلت أمثَلَة MIUI. الخدمات التي تقدم ميزات محسنة لأجل مصادر معينه. المانجات سوف يتم تتبعها عندما يتم إضافتها الي المكتبة. - خدمات محسنة + خدمات محسَّنة اليوم مؤخراً المصادقة لتأكيد التغيير @@ -652,10 +649,10 @@ تحديث الكل كل ثلاثة ايام فقط على شبكة WI-FI - \"Shizuku\" لا يعمل - تم النشر - ملغي - في فترة راحة + «Shizuku» لا يعمل + انتهى النشر + أُلغيت + منقطعة للحصول على المساعده في إصلاح أخطاء تحديث المكتبة، أنظر هنا%1$s احفظ كأرشيف CBZ سياسة الخصوصية @@ -664,23 +661,23 @@ لا شئ للتنظيف عدد المانغا غير المتواجدة بقاعدة بيانات المكتبه%1$d فشل الحصول على قائمة الملحقات - قم بتثبيت \"Shizuku\" لاستخدامه كمثبت ملحق. + ثبِّت «شيزوكو» وشغِّله لتستخدمه مثبِّت إضافات. الاسئله الشائعه والأدلة - 5% + ٥٪ بدأت شبكة بالاغلفة - تم التخطي لعدم وجود فصول مقروءه - تم التخطي لوجود فصول غير مقروءه - هذا لم يبدأ بعد - تم تخطيها لإنتهاء السلسلة + تُخُطِّيت بسبب عدم وجود فصول قُرئت + تُخُطِّيت لوجود فصول لم تُقرأ + لم يبدأ هذا بعد + تُخُطِّيت لأنها تمِّت تحريك الصور أثناء النقر كبر الصوره الأفقية - متجاوز + متجاوَز خطأ أثناء حفظ الصورة معطل رأسي بالعكس - فشل تحديث %1$d عناصر - تم تخطي تحديث %1$d عناصر + فشل %1$d تحديث أو تحديثات + تُخُطِّي %1$d تحديث أو تحديثات اضغط لقراءة المزيد نسخة جديدة متاحة من الإصدارات الرسمية. انقر لمعرفة كيفية الهجرة من إصدارات F-Droid غير الرسمية. نقل سلسلة الفصول للأعلى @@ -695,9 +692,8 @@ لا يوجد مصادر مثبته لا يوجد مصدر المانجا الغير مقروءة - يحسن أداء القارئ - تقسيم للصور الطويلة - لم يتم العثور على الصفحة %d أثناء التقسيم + يحسِّن أداء القارئ + لم يُعثر على الصفحة %d أثناء التقسيم لم يتم العثور على مسار الصفخة %d اعادة تعيين جميع اعدادات القارئ إعادة تعيين اعدادات القراءه لكل القصص @@ -705,15 +701,14 @@ التقييم العمري الإصدار لم يمكن إعادة تعيين إعدادات القارئ - لم يمكن تقسيم الصورة المُنزلَة هذا محرج قائمة غير المنتهية على الشبكات غير المحدودة فقط إعادة ضبط إعدادات قراءة السلسلة قائمة القراءة قائمة الرغبات - قائمة الكاملة - قائمة المعلقة + قائمة المكتملة + قائمة المعلَّقة غلاف مخصص غير قادر على فتح آخر فصل تم قراءته غير مثبت @@ -725,10 +720,10 @@ مسح الفئة لا وصف أُرجواني - القطعة غير متاحة عند تمكين قفل التطبيق - شاهد المانقا المجدده حديثاً + لا تتاح الأداة حال تمكين قفل التطبيق + اطَّلع على ما حُدِّث مؤخَّرًا في مكتبتك حذف كل شيء - الصيغه RARv5 غير مدعومه + تنسيق RARv5 ليس مدعومًا موجة مد و جزر متعدد إنقسام الصور الطويلة (بيتا) @@ -742,17 +737,17 @@ شائع أنت على وشك أن تحذف \"%s\" من مكتبتك - الفصل التالي غير المقروء - الفصل التالي غير المقروء - الفصلان غير المقروءان التاليان - بضعة ال %d فصول غير المقروءة التالية - عدة ال %d فصول غير المقروءة التالية - الفصول %d غير المقروءة التالية + لا يوجد فصل تالٍ لم يُقرأ + الفصل غير المقروء التالي + الفصلان غير المقروءين التاليان + %d فصول تالية لم تُقرأ + %d فصلًا تاليًا لم يُقرؤوا + %d فصل تالٍ لم يُقرؤوا لم يتم منح أذن التخزين بحث… - تم التخطي لأن السلسلة لا تتطلب تحديثات - %s واجه خطأ غير متوقع. نقترح عليك أخد لقطة شاشة لهذه الرسالة، وتفريغ سجلات التعطل ، ثم مشاركتها في قناة الدعم الخاصة بنا على Discord. + تُخُطِّيت لأن السلسلة لا تتطلب تحديثات + واجه %s خطأً غير متوقَّع. تنبغي لك مشاركة سجلَّات الانهيار في قناة الدعم في دسكورد. عفوًا! أعد تشغيل التطبيق لغة التطبيق، الإشعارات @@ -787,4 +782,16 @@ إخفاء الإدخالات الموجودة بالفعل في المكتبة تحديث الفئة + التراكب + + لا يوجد أيُّ فصل مفقود + فصل واحد مفقود + فصلان مفقودان + %1$s فصول مفقودة + %1$s فصلًا مفقودًا + %1$s فصل مفقود + + لعلَّه فاقد بعض الفصول + دوِّر الصفحات العريضة لتلائم الشاشة + اعكس اتِّجاه الصفحات العريضة المدوَّرة \ No newline at end of file diff --git a/i18n/src/main/res/values-b+es+419/strings.xml b/i18n/src/main/res/values-b+es+419/strings.xml index 7b0772abaf..22b7eed94b 100644 --- a/i18n/src/main/res/values-b+es+419/strings.xml +++ b/i18n/src/main/res/values-b+es+419/strings.xml @@ -267,7 +267,6 @@ Cuadrícula compacta Iniciar Reintentar - Detener Borrar cookies Red Se canceló la restauración @@ -315,7 +314,6 @@ No se han encontrado resultados No hay más resultados Pestañas - Insignias Local Actualizando categoría Error desconocido @@ -490,10 +488,8 @@ No se encontró ninguna aplicación de selección de archivos Por favor iniciar sesión con MAL de nuevo Mostrar en la lista de fuentes y extensiones - Registros de accidentes Fecha de lectura terminada Fecha de inicio de lectura - Registros de fallos guardados Guarda los registros de errores en un archivo para compartirlos con los desarrolladores Volcar registros de accidentes Zona de toque @@ -657,13 +653,11 @@ Ninguna fuente encontrada No se encontró ninguna fuente instalada Recuento no leído - No se pudo dividir la imagen descargada Bueno, esto es incomodo Todas las configuraciones del lector se restablecen No se pudo restablecer la configuración del lector Restablecer la configuración del lector por serie Mejora rendimiento del lector al ajustar imágenes altas descargadas. - Ajustar automáticamente el alto de las imagenes Página %d no encontrada durante la división No se encontró la ruta del archivo de la página %d Reinicie el modo de lectura y la orientación de toda serie diff --git a/i18n/src/main/res/values-be/strings.xml b/i18n/src/main/res/values-be/strings.xml index 5176cfe896..d08dd93535 100644 --- a/i18n/src/main/res/values-be/strings.xml +++ b/i18n/src/main/res/values-be/strings.xml @@ -33,7 +33,6 @@ Наступная частка Папярэдняя частка Паўза - Спыніць Прагляд частак Рэдагаваць вокладку Дадаць у катэгорыі diff --git a/i18n/src/main/res/values-bg/strings.xml b/i18n/src/main/res/values-bg/strings.xml index 547f43a016..e2d733803e 100644 --- a/i18n/src/main/res/values-bg/strings.xml +++ b/i18n/src/main/res/values-bg/strings.xml @@ -33,7 +33,6 @@ Преименувай категория Запиши в категории Промени корица - Спри Паузирай Предишна глава Следваща глава @@ -448,7 +447,6 @@ %1$s глави Раздели - Значки Данни Изисква рестартиране, за да влезе в сила Мрежа @@ -525,7 +523,6 @@ Разтовари записите от сривовете Не Пейзаж - Записите са запазени Работа на заден план Изключи инкогнито Обновяване на библиотеката… (%1$d/%2$d) @@ -614,7 +611,6 @@ Не бе намерено приложение за подбор на файлове Първи стъпки Стандартните настройки на главите бяха обновени - Записи от сривове Предишна страница Следваща страница Език @@ -691,7 +687,6 @@ Списък със спрени Предстоящо изтегляне Работи само със заглавия в библиотеката и ако текущата и следващата глава са вече изтеглени - Изтегленото изображение не можа да бъде разделено Виж наскоро обновената манга Не можа да бъде намерен файловият път на страница %d Показвай дубликати на закачените източници @@ -742,7 +737,6 @@ Покажи записи от крашове, оптимизации за батерията Мулти Увеличи широките изображения при натискане - Разделяй високите изображения Подобрява производителността на четеца Не е дадено разрешение за съхранение Приключено издаване diff --git a/i18n/src/main/res/values-bn/strings.xml b/i18n/src/main/res/values-bn/strings.xml index 16fac515ac..811f07ea9b 100644 --- a/i18n/src/main/res/values-bn/strings.xml +++ b/i18n/src/main/res/values-bn/strings.xml @@ -37,7 +37,6 @@ বিভাগের নতুন নামকরণ করুন বিভাগ নির্বাচন করুন মোড়ক সম্পাদনা করুন - থামুন বিরতি দিন পূর্ববর্তী অধ্যায় পরবর্তী আধ্যায় @@ -448,7 +447,6 @@ সর্বশেষ ব্যবহৃত ওয়েবসাইটটি ওয়েবভিউতে পরীক্ষা করুন ট্যাব গুলি - প্রতীক গুলি ডাউনলোড করা অধ্যায় আপনি এখন প্রস্থান করেছেন প্রস্থান @@ -480,13 +478,11 @@ আইটেমের সংখ্যা প্রদর্শন করুন পরের পৃষ্ঠা আগের পৃষ্ঠা - ক্র্যাশ লগগুলো কোনো ফাইল বাছাইকারী অ্যাপ পাওয়া যায় নি উৎস স্থানান্তর গাইড দয়া করে MAL এ আবার লগইন করুন শেষ করার তারিখ শুরু করার তারিখ - ক্র্যাশ লগগুলো সংরক্ষিত হয়েছে বিকাশকারীদের সাথে সেয়ার করার জন্য এরর লগগুলো একটি ফাইলে সংরক্ষণ করে ডান ও বাম কিনার @@ -637,11 +633,9 @@ স্থগিত তালিকা পড়ার সময় সয়ংক্রিয়ভাবে ডাউনলোড শিরোনামসমুহ হালনাগাদ এড়িয়ে যান - ডাউনলোড করা চিত্র বিভক্ত করা যায়নি এড়িয়ে যাওয়া যেগুলো শুরু করা হয়নি ওয়েবভিউ ডাটা মুছা হয়েছে - লম্বা চিত্র বিভক্ত পঠন কর্মক্ষমতা উন্নত করে ওয়েবভিইউ ডাটা মুছুন বহু diff --git a/i18n/src/main/res/values-ca/strings-aniyomi.xml b/i18n/src/main/res/values-ca/strings-aniyomi.xml index 87a9cb5909..ebf74c8b2d 100644 --- a/i18n/src/main/res/values-ca/strings-aniyomi.xml +++ b/i18n/src/main/res/values-ca/strings-aniyomi.xml @@ -50,7 +50,7 @@ Moure Història a la pestanya Més Últim comprovat Anime local - Mostra el botó de continuar mirant/llegint + Botó per a continuar llegint Descarregador extern Episodi anterior Manga diff --git a/i18n/src/main/res/values-ca/strings.xml b/i18n/src/main/res/values-ca/strings.xml index 6af5fcedf6..c940f47c89 100644 --- a/i18n/src/main/res/values-ca/strings.xml +++ b/i18n/src/main/res/values-ca/strings.xml @@ -40,7 +40,6 @@ Canvia el nom de la categoria Defineix les categories Edita la portada - Atura Pausa Capítol anterior Capítol següent @@ -415,7 +414,6 @@ Graella confortable Migra Pestanyes - Icones Mostra les pestanyes de les categories No s\'ha trobat cap pàgina Desactiva-ho tot @@ -480,10 +478,8 @@ No s\'ha trobat cap aplicació de selecció de fitxers Mostra a la llista de fonts i extensions Torneu a iniciar la sessió a MAL - Registres de fallades Data de finalització Data d’inici - S\'han desat els registres de fallades Desa els registres d\'errors en un fitxer perquè el pugueu compartir amb els desenvolupadors Bolca els registres de fallades Zones de toc @@ -646,7 +642,6 @@ No s\'ha trobat cap font instal·lada No s\'ha trobat cap font Nombre de no llegits - Divideix les imatges altes Millora el rendiment del lector No s\'ha trobat la pàgina %d en dividir No s\'ha trobat el camí del fitxer de la pàgina %d @@ -654,7 +649,6 @@ Restableix el mode de lectura i l\'orientació de totes les sèries S\'han restablert tota la configuració del lector No s\'ha pogut restablir la configuració del lector - No s\'ha pogut dividir la imatge baixada Ostres, que estrany... Llengua Versió @@ -761,4 +755,5 @@ Amaga els elements que ja són a la biblioteca Copia al porta-retalls Actualitza la categoria + Divideix les imatges altes \ No newline at end of file diff --git a/i18n/src/main/res/values-ceb/strings.xml b/i18n/src/main/res/values-ceb/strings.xml index 25bc34c912..3caa93e7e9 100644 --- a/i18n/src/main/res/values-ceb/strings.xml +++ b/i18n/src/main/res/values-ceb/strings.xml @@ -10,7 +10,7 @@ Gisubay Wala mabasa Alpabetiko - Kinatibuk sa manga + Hingpit nga mga entry Kinatibuk sa mga kapitulo Katapusan nga pagbasa Wala mabasa nga ihap @@ -65,7 +65,6 @@ Usba ang ngalan sa kategorya Usba ang hapin Tan-awa ang mga kapitulo - Hunong Kuhaa Sa miaging kapitulo Pagsugod @@ -353,7 +352,6 @@ Mga serbisyo nga naghatag dugang nga mga bahin alang sa piho nga mga gigikanan. Awtomatikong gisubay ang Manga kon idugang sa imong librarya. Usa ka paagi nga pag-sync aron ma-update ang pag-uswag sa kapitulo sa mga serbisyo sa pagsubay. I-set up ang tracking para sa indibidwal nga manga entries gikan sa ilang tracking button. Gipalambo nga mga serbisyo - Awtomatikong gibahin ang taas nga mga imahe Nagpauswag sa pasundayag sa magbabasa pinaagi sa pagbahin sa taas nga na-download nga mga imahe. Mga serbisyo Pag-tap sa mga zone @@ -430,7 +428,6 @@ I-reset ang mode sa pagbasa ug oryentasyon sa tanan nga serye Ang tanan nga mga setting sa magbabasa gi-reset Dili ma-reset ang mga setting sa magbabasa - Na-save ang mga crash log Kalihokan sa background Nagtabang sa mga update sa librarya sa background ug pag-backup Verbose logging diff --git a/i18n/src/main/res/values-cs/strings-aniyomi.xml b/i18n/src/main/res/values-cs/strings-aniyomi.xml index 6228e54edd..f87f8fe335 100644 --- a/i18n/src/main/res/values-cs/strings-aniyomi.xml +++ b/i18n/src/main/res/values-cs/strings-aniyomi.xml @@ -115,7 +115,7 @@ Označit jako nezhlédnuté Stažené epizody Lokální anime - Zobrazit tlařítko pro pokračování sledování / čtení + Tlačítko Pokračovat ve čtení Použít stahovač mimo aplikaci Přesunout Mangy do záložky Více Nezhlédnuto diff --git a/i18n/src/main/res/values-cs/strings.xml b/i18n/src/main/res/values-cs/strings.xml index cd22785968..28c80d00ac 100644 --- a/i18n/src/main/res/values-cs/strings.xml +++ b/i18n/src/main/res/values-cs/strings.xml @@ -111,7 +111,7 @@ Vlastní filtr Stránka: %1$d Další kapitola nenalezena - Obrázek nemohl být načten + Obrázek se nepodařilo načíst Dokončeno: Aktuální: Následující: @@ -146,7 +146,6 @@ Aktualizovat knihovnu Přejmenovat kategorii Vybrat kategorie - Stop Pauza Opakovat Pokračovat @@ -350,9 +349,8 @@ Optimalizace je již vypnuta Pomáhá s aktualizacemi knihovny a záloh na pozadí Vypnout optimalizaci baterie - Chybový protokol uložen Uloží chybové protokoly do souboru pro sdílení s vývojáři - Vypsat protokoly o selhání + Sdílet protokoly o selhání Obnovit přebaly v knihovně Data Pro projevení je nutný restart aplikace @@ -425,7 +423,6 @@ Zakázat Zobrazovat počet položek Začít - Záznamy o pádech Aktualizace rozšíření Chyby Dokončeno @@ -465,9 +462,9 @@ Kap. %1$s - %2$s Aktualizuji knihovnu - Přeskakuji %d kapitolu, buď chybí ve zdroji nebo byla vyfiltrována - Přeskakuji %d kapitoly, buď chybí ve zdroji nebo byly vyfiltrovány - Přeskakuji %d kapitol, buď chybí ve zdroji nebo byly vyfiltrovány + Přeskočena %d kapitola, buď chybí ve zdroji nebo byla vyfiltrována + Přeskočeny %d kapitoly, buď chybí ve zdroji nebo byly vyfiltrovány + Přeskočeno %d kapitol, buď chybí ve zdroji nebo byly vyfiltrovány Zdroj nenalezen Žádné stránky nenalezeny @@ -500,7 +497,6 @@ Při otevření čtečky krátce zobrazí aktuální režim Čekajících aktualizací Nebyla nalezena žádná aplikace pro výběr souborů - Odznaky Filtruje všechnu položky ve vaší knihovně Položky ve vynechaných kategoriích nebudou staženy, i kdyby byly také v zahrnutých kategoriích. Automatické stahování @@ -667,13 +663,11 @@ Jazyk Věkové hodnocení Verze - Rozdělit vysoké obrázky Zlepšuje výkon čtečky Nastavení čtečky se nepodařilo resetovat No, tohle je trapné Stránka %d nebyla při rozdělení nalezena Nepodařilo se najít cestu k souboru stránky %d - Stažený obrázek se nepodařilo rozdělit Posunutí širokých snímků při klepnutí Počet nepřečtených Když baterie není vybitá @@ -705,7 +699,7 @@ Funguje pouze na položky v knihovně a pokud je již stažena aktuální kapitola a následující kapitola Jste si jistí\? Chystáte se odstranit \"%s\" ze své knihovny - Multi + Více Rozdělit vysoké obrázky (BETA) Poslední aktualizace knihovny: %s Populární @@ -721,7 +715,7 @@ Zdroje, rozšíření, globální vyhledávání Zámek aplikace, zabezpečená obrazovka Výpis protokolů selhání, optimalizace baterie - %s narazil na neočekávanou chybu. Doporučujeme vám pořídit snímek obrazovky, vypsat protokoly o selhání a poté je sdílet v našem kanálu podpory na Discordu. + %s narazil na neočekávanou chybu. Doporučujeme vám sdílet protokoly o selhání a poté je sdílet v našem kanálu podpory na Discordu. Restartujte aplikaci Ruční a automatické zálohování Došlo k neočekávané chybě @@ -751,4 +745,14 @@ Zkopírovat do schránky Aktualizovat kategorii + Rozdělit vysoké obrázky + Překrytí + Překlopení orientace otočených širokých stránek + Otočení širokých stránek tak, aby se vešly + + Chybí %1$s kapitola + Chybí %1$s kapitoly + Chybí %1$s kapitol + + Mohou chybět kapitoly \ No newline at end of file diff --git a/i18n/src/main/res/values-cv/strings-aniyomi.xml b/i18n/src/main/res/values-cv/strings-aniyomi.xml index 759204e412..771d9a7e59 100644 --- a/i18n/src/main/res/values-cv/strings-aniyomi.xml +++ b/i18n/src/main/res/values-cv/strings-aniyomi.xml @@ -4,7 +4,7 @@ Aniyomi уҫ Юлашки ҫӗнетӗве тӗрӗслени Ҫырава кӑтарт - Вырӑнти ҫӑл куҫ + Вырӑнти ҫӑл куҫран Яланхилле пухмӑш Ку хушмана шанчӑклӑ мар сертификатпа алӑ пуснӑ тата ӑна активламан. \n diff --git a/i18n/src/main/res/values-cv/strings.xml b/i18n/src/main/res/values-cv/strings.xml index 862bbc23c1..ce6200886b 100644 --- a/i18n/src/main/res/values-cv/strings.xml +++ b/i18n/src/main/res/values-cv/strings.xml @@ -1,6 +1,6 @@ - Вулавӑшӑн серилӗхӗсем + Вулавӑшри серилӗхсем Усӑ курнӑ: %1$s Куккисем катертнӗ Кукки тасат @@ -40,35 +40,35 @@ %d пухмӑш Кашнинчех ыйтмалла - Тулать - Ҫӗнетни чарӑвӗсем - Кашни эрне + Петтерей тулнӑ чух + Хатӗр валли хӑй-хальлӗн ҫӗнетӳ чарӑвӗсем + Кашни ерне Кашни 2 кун Кашни кун Кашни 12 сехет Кашни 6 сехет Ҫӗнетни тӑтӑшлӑхӗ - Пӗтӗм ҫӗнетӳ + Пӗтӗмӗшле ҫӗнетӳ Кӑтарт - Сыхлав ыкранӗ + Ыкран сыхлавӗ 1 минут хыҫҫӑн %1$s минут хыҫҫӑн Сыхлав тата вӑрттӑнлӑх - Систерӳсене ӗнерле + Систерӳсене ӗнер Тухнине ҫирӗплет - Вӑхӑт формачӗ + Ҫул-кун хармачӗ Ҫутнӑ Сӳнтернӗ Тата - Тиевсем + Тийевсем Вулавӑш Тӗп Хушӑма кӗме май ҫук Ҫӗнет Малалла - Каялла + Кайалла Пӑрахӑҫла Упра Пайлаш @@ -77,12 +77,12 @@ Пуҫламӑш патне куҫ Чи кивви Чи ҫӗнни - Веҫех пӑрахӑҫла + Пурне те пӑрахӑҫла Пӑрахӑҫла Ҫаклат Сӳнтер - Тиенисен палли - Ят-йыш + Тийене сыпӑксем шучӗ + Йат-йыш Кӑтарт Кӑтарту тытӑмӗ WebView-ра уҫ @@ -91,18 +91,17 @@ Пуҫла Катерт Ҫӗнӗрен - Вӑхӑтлӑха чар - Чар + Чар Сыпӑксем пӑх - Хуплашкине улӑштар + Хуплашка улӑштар Пухмӑша хуш Пухмӑш ячӗне улӑштар Пухмӑшсене тӳрлет Пухмӑша хуш Хуш - Унчченхине вуланӑ пек паллӑ ту - Тухма каялла тепӗр хут пус - Тӳрлет + Умӗнхине вуланӑ пек паллӑ ту + Тухма тепӗр хут пус + Улӑштар Веҫех сӳнтер Веҫех ҫут Вулавӑша ҫӗнет @@ -118,7 +117,7 @@ Юлашки сыпӑкпа Юлашки вуланипе Сыпӑксен шучӗпе - Алфавитпа + Сас паллисен йӗркипе Алана катерт Вуламан Карт @@ -154,27 +153,27 @@ Меллӗ сетке Ҫӑтӑ сетке Тепӗр майлӑ суйла - Алӑ вӗҫҫӗн - Ретри япаласем + Сӳнтернӗ + Серилӗхсен шучӗ Урлӑ Тӑрӑх - Тепӗр хушӑмсем ҫине куҫнӑ чух ку хушӑмӑн мӗн пуррине пытар тата скриншот тума чар - Систерӳсен мӗн ҫырнине пытар + Ыкран сыхлавӗ тепӗр апсем ҫине куҫнӑ чух ку апӑн мӗн пуррине пытарать тата ыкрана сӑн ҫапма чарать + Систерӳсенче мӗн ҫырнине пытарни Нихӑҫан - Яланах + Йаланах Ҫӗнӗрен йӗркеле - Тӑвӑмсен кӗнекине уҫ + Тӑвӑм-пулӑм кӗнекине уҫ Тасат Уйӑр - Тӗксӗм тема - Сӳстемри пекех + Тӗксӗм темӑ + Системри пекех Вулӑш - Сӑнану - Тиени… + Йӗрлев + Тийени… Тавӑр Салт Куҫар - Маллали сыпӑк + Хыҫҫӑнхи сыпӑк Умӗнхи сыпӑк Сӑнану Чи лайӑх пӗрлӗхшӗн WebView-а ҫӗнет @@ -186,8 +185,8 @@ Ҫӗнетӳсем кӗтеҫҫӗ Вулавӑша ҫӗнетнӗ чухне ҫӗнӗ хуплашка тата вак-тӗвек пуррине тӗрӗслени Хӑй-халлӗн метта пӗлӗмсене ҫӗнетмелле - Тухакан манкка ҫеҫ ҫӗнетмелле - Хушӑм ҫинчен + Серилӗх вӗҫленнӗ + Ап ҫинчен Шанчӑклӑ мар Официаллӑ мар Шанчӑклӑ @@ -208,10 +207,10 @@ Вулав тытӑма кӑтарт Тулли экран Шанчӑклӑ мар хушма - Кӗтмелли тытӑмра ҫаклатса лартмалла + Ним туман чух ҫаклатни 10% %1$s-мӗш сыпӑк - Ҫаклатӑва уҫма ыйт + Ҫаклатӑва уҫма пӳрне йӗрӗ ыйтни 25% 20% 15% @@ -385,7 +384,6 @@ Пӗр тупсӑм та тупӑнман Урӑх тупсӑмсем ҫук Кантӑксем - Паллисем Тӗрӗс мар янтӑв файлӗ Вырӑнти Пухмӑша ҫӗнетни @@ -440,7 +438,7 @@ Янтӑв ту v%1$s верссиччен ҫӗнетнӗ Мӗн ҫӗнни - Тема + Темӑ Хушнӑ вӑхӑтпа \"%1$s\" пур ҫӗрте шыра Вулав тытӑмӗ @@ -476,10 +474,10 @@ Маллали сыпӑк Умӗнхи эл Ҫӑл куҫ куҫарассипе пулӑшу - NSFW (18+) шалаш + NSFW (18+) ҫӑл куҫӗсем Файлсене суйламалли хушӑм тупӑнман Тархасшӑн MAL-а ҫӗнӗрен кӗр - Ҫӑл куҫсен ят-йышӗнче кӑтарт + Ҫӑл куҫсен тата хушмасен йат-йышӗнче кӑтартни Вулама вӗҫленӗ вӑхӑчӗ Вулама пуҫланӑ вӑхӑчӗ Хӗрри @@ -488,10 +486,8 @@ Пӗчӗкленнипе Пысӑкланнипе Сыпӑк шучӗпе - Тиесе илни вӑхӑчӗпе - Сӑнавланакан - Тухсан кайни ҫинчен логсем - Тухса кайни ҫинчен логсем упраннӑ + Тийесе илни вӑхӑчӗпе + Йӗрленет Файлсенчи йӑнӑшсен логсене хатерлевҫӗсем патне яма упрать Тухса кайни инчен логсене тиесе яни Куҫӑм палли @@ -499,7 +495,7 @@ Ик эллӗ уйарни вулав майлӑ мар пулсан Ик эллӗ уйарнине ҫавӑр Ик эллӗ уйӑрни - Япаласен шутне кӑтарт + Серилӗхсен шутне кӑтарт Сылтӑм Сулахай Малалли @@ -509,7 +505,7 @@ Кӑлар: %s Ҫут: %s Ҫук - Илни вӑхӑт + Йулашки сыпӑк илнипе Ҫак Android версси урӑх Пайлашу аса ӑтавланаймарӗ Тӑрӑх @@ -520,39 +516,39 @@ Тӑвӑмсем Пусма вырӑнсене вулӑш уҫӑ чухне кӑтартмалла Йӑнӑшсене кӑтарт - Ҫак серилӗх валли веҫ пӑрахӑҫла - Хӑй-хальлӗн тийесе илни, малтан тийени + Ҫак серилӗх валли пурне те пӑрахӑҫла + Хӑй-хальлӗн тийесе илни, малтанах тийени Юлнӑ сыпӑксем Пурӗ ҫырав - Веҫех катерт + Пурне те катерт «%s» пухмӑша катертесшӗнех-и\? - Пухмӑша катерт + Пухмӑш катерт Асӑрхаттару - Ятсӑр сетке - Серие пуҫламӑша куҫар - Улшӑнӑва ҫирӗплетме аутентификацилен + Йатсӑр сетке + Серилӗхе пуҫламӑша куҫар + Улшӑнӑва ҫирӗплетме есӗлӗхе ҫирӗплет Пуҫланӑ Кӑтартусем тата ыйту-хурав Чӗлхе Шыра… Хуп - Тиеве халь пуҫла - InternalError: Хушма пӗлӗме пӑхма тӑвӑмсен кӗнекине тӗрӗсле + Тийеве халь пуҫла + InternalError: Хушма пӗлӗме пӑхма тӑвӑм-пулӑм кӗнекине пӑх Кӑтартӑнни Ҫутнӑ Сӳнтернӗ - Тема, кун тата вӑхӑт хармачӗ + Темӑ, кун тата вӑхӑт хармачӗ Пухмӑшсем, пӗтӗмӗшле ҫӗнетӳ Вулав тытӑмӗ, кӑтартӑнни, куҫӑм - Хушӑм чӗлхи, систерӳсем + Ап чӗлхи, систерӳсем Тийев черечӗ Ҫӗр ҫырли тайккирийӗ Ҫур ҫӗр ӗнтрӗкӗ Пӗр йенлӗ ӳсӗм килӗштерӗвӗ, анлӑлатнӑ килӗштерӳ Wi-Fi урлӑ ҫеҫ «Малалла вула» пускӑч - Хушӑма ҫаклатни, ыкран хӳтӗлевӗ - Петтерей тулли чухне + Апа ҫаклатни, ыкран хӳтӗлевӗ + Петтерей тулли чух Чарусем: %s Вуламан сыпӑк(сем) пур Пуҫланӑ @@ -562,7 +558,7 @@ Вӑхӑт паллисем Чухлавлӑ вӑхӑт палли Вӑрӑм (кӗске+, n кун кайалла) - Хушӑм темми + Ап темми Анчахрах Пайан Лавантӑ @@ -575,13 +571,19 @@ Ӗнер %1$d кун кайалла - Чаравсӑр тетелте ҫеҫ + Чараксӑр тетел урлӑ ҫеҫ Серилӗхе пуҫламан - Таккӑ + Такку Хуп-хура темӑ - Хушӑм чӗлхи + Ап чӗлхи Халь мар Кӗске (пайан, ӗнер) Кашни 3 кун Симӗс кӑвак + Куҫӑмлӑ + Шутлавсем + Пайлашу асне ӑт + Хӑй тӗллӗн тата хӑй-хальлӗн йантӑлав + Пухмӑш ҫӗнет + Йӑнӑш кӗнекине тийесе йани, петтерей лайӑхлатни \ No newline at end of file diff --git a/i18n/src/main/res/values-da/strings.xml b/i18n/src/main/res/values-da/strings.xml index 8e4b10fa79..cb9217e445 100644 --- a/i18n/src/main/res/values-da/strings.xml +++ b/i18n/src/main/res/values-da/strings.xml @@ -79,7 +79,6 @@ Kompakt grid Liste Downloaded kapitler - Stop Sporet Vælg kategorier Vis kategori tabs diff --git a/i18n/src/main/res/values-de/strings-aniyomi.xml b/i18n/src/main/res/values-de/strings-aniyomi.xml index 1e797bed64..05bd8dc63f 100644 --- a/i18n/src/main/res/values-de/strings-aniyomi.xml +++ b/i18n/src/main/res/values-de/strings-aniyomi.xml @@ -47,7 +47,7 @@ Heruntergeladene Folgen Lokale Quelle Lokale Animequelle - Fortsetzen-Knopf anzeigen + Weiterlesen-Button Nach Folgennummer Externen Downloader benutzen Internen Downloader benutzen diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index 949c332afb..2cee83b746 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -34,7 +34,6 @@ Kategorie umbenennen Kategorien festlegen Cover bearbeiten - Stopp Pause Vorheriges Kapitel Nächstes Kapitel @@ -267,7 +266,7 @@ 32-Bit-Farben Gelesene Kapitel überspringen Bei langem Antippen anzeigen - Überlagerung + Overlay Multiplizieren Bildschirm Brennen / Verdunkeln @@ -415,7 +414,6 @@ Migrieren Komfortable Kacheln Registerkarten - Abzeichen Kategorienreiter anzeigen Keine Seiten gefunden Alles deaktivieren @@ -484,12 +482,10 @@ Tippzonen Kindle-Stil L-förmig - Absturzprotokolle Enddatum Startdatum - Absturzprotokolle gespeichert Speichert Fehlerprotokolle in einer Datei, die dann mit den Entwicklern geteilt wird - Absturzprotokolle ausgeben + Absturzprotokolle teilen Absteigend Aufsteigend Nach Kapitelnummer @@ -646,7 +642,6 @@ Keine installierte Quelle gefunden Keine Quelle gefunden Ungelesenenanzahl - Hohe Bilder teilen Verbessert die Leserleistung Seite %d während dem Aufteilen nicht gefunden Dateipfad der Seite %d konnte nicht gefunden werden @@ -654,7 +649,6 @@ Lesereinstellungen für jede Serie zurücksetzen Alle Lesereinstellungen zurückgesetzt Lesereinstellungen konnten nicht zurückgesetzt werden - Heruntergeladenes Bild konnte nicht aufgeteilt werden Tja, das ist jetzt etwas peinlich Version Sprache @@ -710,7 +704,7 @@ Ein unerwarteter Fehler ist aufgetreten Absturzprotokolle ausgeben, Akkuverbrauch-Optimierung Manuelle und automatische Datensicherungen - %s ist auf einen unerwarteten Fehler gestoßen. Wir empfehlen dir, einen Screenshot von dieser Nachricht zu machen, die Absturzprotokolle auszugeben und sie dann in unserem Support-Kanal auf Discord zu teilen. + %s ist auf einen unerwarteten Fehler gestoßen. Wir empfehlen dir, die Absturzprotokolle in unserem Support-Kanal auf Discord zu teilen. App-Sperre, sicherer Bildschirm Unbekannter Titel Ungültiger Speicherort: %s @@ -736,4 +730,13 @@ In die Zwischenablage kopieren Kategorie aktualisieren + Hohe Bilder teilen + Overlay + Breite Seiten drehen, damit sie passen + + %1$s Kapitel fehlt + %1$s Kapitel fehlen + + Es könnten Kapitel fehlen + Ausrichtung gedrehter breiter Seiten spiegeln \ No newline at end of file diff --git a/i18n/src/main/res/values-el/strings.xml b/i18n/src/main/res/values-el/strings.xml index a9901b4ba7..94039bcbfd 100644 --- a/i18n/src/main/res/values-el/strings.xml +++ b/i18n/src/main/res/values-el/strings.xml @@ -40,7 +40,6 @@ Μετονομασία κατηγορίας Ορισμός κατηγοριών Επεξεργασία εξώφυλλου - Σταμάτημα Παύση Προηγούμενο κεφάλαιο Επόμενο κεφάλαιο @@ -415,7 +414,6 @@ Μεταφορά Άνετο πλέγμα Καρτέλες - Σήματα Εμφάνιση καρτελών κατηγοριών Δεν βρέθηκαν σελίδες Απενεργοποίηση όλων @@ -484,12 +482,10 @@ Άκρη Σαν Kindle Σχήματος L - Αρχεία καταγραφής σφαλμάτων Ημερομηνία λήξης Ημερομηνία έναρξης - Τα αρχεία καταγραφής σφαλμάτων αποθηκεύτηκαν Αποθηκεύει αρχεία καταγραφής σφαλμάτων σε ένα αρχείο για κοινή χρήση με τους προγραμματιστές - Άδειασμα αρχείων καταγραφής σφαλμάτων + Κοινή χρήση αρχείων καταγραφής σφαλμάτων Φθίνουσα Αύξουσα Κατά αριθμό κεφαλαίου @@ -646,7 +642,6 @@ Δε βρέθηκε εγκατεστημένη πηγή Αριθμός μη αναγνωσμένων Δε βρέθηκε πηγή - Διαχωρισμός ψηλών εικόνων Βελτιώνει την απόδοση του αναγνώστη Η σελίδα %d δε βρέθηκε κατά τη διάσπαση Δεν ήταν δυνατή η εύρεση της διαδρομής αρχείου της σελίδας %d @@ -654,7 +649,6 @@ Επαναφορά ρυθμίσεων προγράμματος ανάγνωσης ανά σειρά Δεν ήταν δυνατή η επαναφορά των ρυθμίσεων του προγράμματος ανάγνωσης Επαναφορά όλων των ρυθμίσεων προγράμματος ανάγνωσης - Δεν ήταν δυνατή η διαίρεση της εικόνας που έχει ληφθεί Λοιπόν, αυτό είναι άβολο Έκδοση Γλώσσα @@ -711,7 +705,7 @@ Επανεκκίνηση της εφαρμογής Αυτόματη λήψη, λήψη εκ των προτέρων Αρχεία καταγραφής σφαλμάτων, βελτιστοποιήσεις μπαταρίας - Το %s αντιμετώπισε ένα μη αναμενόμενο σφάλμα. Σας προτείνουμε να κάνετε στιγμιότυπο οθόνης αυτού του μηνύματος, να αποθηκεύσετε τα αρχεία καταγραφής σφαλμάτων και, στη συνέχεια να το μοιραστείτε στο κανάλι υποστήριξης μας στο Discord. + Το %s αντιμετώπισε ένα απροσδόκητο σφάλμα. Σας προτείνουμε να μοιραστείτε τα αρχεία καταγραφής σφαλμάτων στο κανάλι υποστήριξης μας στο Discord. Άγνωστος τίτλος Μη έγκυρη τοποθεσία: %s Μη έγκυρη συμβολοσειρά πράκτορα χρήστη @@ -736,4 +730,13 @@ Απόκρυψη καταχωρήσεων που βρίσκονται ήδη στη βιβλιοθήκη Αντιγραφή στο πρόχειρο Ενημέρωση κατηγορίας + Διαχωρισμός ψηλών εικόνων + Επικάλυψη + + Λείπει %1$s κεφάλαιο + Λείπουν %1$s κεφάλαια + + Μπορεί να λείπουν κεφάλαια + Περιστροφή πλατιών σελίδων για να χωρέσουν + Αναστροφή του προσανατολισμού των πλατιών σελίδων που έχουν περιστραφεί \ No newline at end of file diff --git a/i18n/src/main/res/values-eo/strings.xml b/i18n/src/main/res/values-eo/strings.xml index 27dd5436a0..780d855c69 100644 --- a/i18n/src/main/res/values-eo/strings.xml +++ b/i18n/src/main/res/values-eo/strings.xml @@ -18,7 +18,6 @@ Uzantnomo Pasvorto Retpoŝta adreso - Halti Vidi ĉapitrojn Alinomi kategorion Redakti kategoriojn @@ -327,7 +326,6 @@ Neniu rezulto trovita Ne pli rezultoj Langetoj - Ŝildoj Nekonata eraro Vi nun estas elsalutita Elsaluti diff --git a/i18n/src/main/res/values-es/strings.xml b/i18n/src/main/res/values-es/strings.xml index aaa0904b2f..f04ab51bed 100644 --- a/i18n/src/main/res/values-es/strings.xml +++ b/i18n/src/main/res/values-es/strings.xml @@ -27,7 +27,6 @@ Renombrar categoría Establecer categorías Editar la portada - Detener Pausar Capítulo anterior Capítulo siguiente @@ -328,7 +327,7 @@ Según ajustes del sistema Gestionar notificaciones Seguridad y privacidad - Desbloqueo obligatorio + Requiere desbloqueo Bloquear por inactividad Siempre Nunca @@ -452,7 +451,6 @@ Cuadrícula amplia Migrar Pestañas - Insignias Mostrar pestañas de categorías No parece haber ninguna página Deshabilitar todo @@ -497,9 +495,9 @@ 18+ Esto no evita que las extensiones extraoficiales o que estén mal clasificadas muestren contenido para mayores de 18 años en la aplicación. - Se salta el capítulo %d, o bien falta en la fuente o se ha filtrado - Saltándose %d capítulos, o las fuentes que faltan o se han filtrado - Saltándose %d capítulos, o las fuentes que faltan o se han filtrado + Se omite %d capítulo, o bien falta en la fuente o ha sido filtrado + Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados + Se omiten %d capítulos, o bien faltan en la fuente o han sido filtrados No hay capítulos Se han actualizado los ajustes predeterminados de capítulo @@ -521,10 +519,8 @@ No se ha encontrado ninguna aplicación con la que elegir archivos Vuelve a iniciar sesión en MAL Estilo Kindle - Registros de fallos Fecha de finalización Fecha de inicio - Registros de errores guardados Guarda los registros de errores en un archivo para compartirlos con los desarrolladores Volcar registros de fallos Zonas de toque @@ -687,7 +683,6 @@ Todavía no se ha instalado ninguna fuente No se ha encontrado ninguna fuente Capítulos restantes - Dividir imágenes demasiado altas Mejora el rendimiento del lector dividiendo páginas descargadas mucho más altas que anchas No se ha encontrado la página %d al dividir La ruta al archivo de la página %d no se encuentra @@ -695,7 +690,6 @@ Se han restablecido los ajustes del visor Restablecer los ajustes del lector en cada serie No se pudieron restablecer los ajustes del visor - No se pudo dividir la imagen descargada Houston, tenemos un problema Versión Idioma @@ -744,7 +738,7 @@ Sincroniza tu progreso de lectura; unidireccional o mejorada Descargas automáticas y por adelantado Categorías y actualizaciones generales - %s se ha cerrado por un problema inesperado. Te sugerimos que hagas una captura de pantalla de lo que ves, vuelques todos registros de depuración y que nos los envíes a nuestro canal de apoyo en Discord, en inglés. + %s se ha cerrado por un problema inesperado. Te sugerimos que compartas todos tus registros de depuración, enviándolos a nuestro canal de apoyo en Discord, en inglés. Modos de lectura, apariencia y navegación Idioma de la interfaz y notificaciones Temas de colores y formatos de fecha @@ -779,4 +773,14 @@ Saltar elementos que ya estén en la biblioteca Copiar al portapapeles Actualizar categoría + Dividir las imágenes altas + Superposición + + Falta %1$s capítulo + "Faltan %1$s capítulos" + Faltan %1$s capítulos + + Girar las páginas anchas para adaptarlas a la pantalla + Puede que le falte algún capítulo + Girar las páginas anchas en la dirección opuesta \ No newline at end of file diff --git a/i18n/src/main/res/values-eu/strings.xml b/i18n/src/main/res/values-eu/strings.xml index 61e4d680b2..441fda15a4 100644 --- a/i18n/src/main/res/values-eu/strings.xml +++ b/i18n/src/main/res/values-eu/strings.xml @@ -63,7 +63,6 @@ %1$s-n egin da %2$s errorerekin Cookieak garbitu dira - Gorde dira erroreen erregistroak Alde batera utzia Egoera Fidagarria ez den luzapena @@ -198,7 +197,6 @@ Desinstalatu Erakutsi orrialdearen zenbakia Freskatu jarraipena - Bereizgarriak Liburutegian Gehiago %1$s Kapitulua @@ -385,7 +383,6 @@ Kategoriak ezarri Editatu azala Ikusi kapituluak - Gelditu Pausatu Aurreko kapitulua Hurrengo kapitulua @@ -444,7 +441,6 @@ Ez dago azkenaldiko eguneraketarik Zure liburutegia hutsik dago Ez daukazu kategoriarik. Sakatu gehiketa botoia zure liburutegia antolatzeko bat sortzeko. - Erroreen erregistroak Aurreko orrialdea Piztu Berriena diff --git a/i18n/src/main/res/values-fa/strings.xml b/i18n/src/main/res/values-fa/strings.xml index f178700f5c..fe79a3dd5e 100644 --- a/i18n/src/main/res/values-fa/strings.xml +++ b/i18n/src/main/res/values-fa/strings.xml @@ -126,7 +126,6 @@ هیچ نتیجه ای یافت نشد نتیجه بیشتری یافت نشد تب ها - تعداد حداکثر تعداد نسخه‌های پشتیبان زمان پشتیبان گیری پشتیبان گیری خودکار @@ -332,7 +331,6 @@ قسمت بعد قسمت قبل مکث - متوقف کردن مشاهده قسمت ها محلی درحال به روز رسانی دسته بندی ها @@ -594,7 +592,6 @@ شامل: %s بدون: %s ذخیره به عنوان فایل آرشیو CBZ - نصف کردن خودکار عکس های بلند انتشار به پایان رسید قالب فصل نامعتبر است لغو شد @@ -623,4 +620,6 @@ پشتیبانی/بازگردانی ممکن است کار نکند اگر بهینه‌سازی MIUI غیر فعال باشد در دسترس است اما افزونه منبع نصب نشده است: %s شما باید از پشتیبانی ها در جا های دیگر هم کپی داشته باشید. + بروزرسانی دسته بندی + کپی کردن به کلیپ‌برد \ No newline at end of file diff --git a/i18n/src/main/res/values-fi/strings.xml b/i18n/src/main/res/values-fi/strings.xml index 92315e99fe..90ccded5d9 100644 --- a/i18n/src/main/res/values-fi/strings.xml +++ b/i18n/src/main/res/values-fi/strings.xml @@ -85,7 +85,6 @@ Muokkaa kategorioita Nimeä kategoria uudelleen Yritä uudelleen - Lopeta Pysäytä Aiempi luku Seuraava luku @@ -415,7 +414,6 @@ Siirrä Mukava ruudukko Välilehdet - Merkit Näytä kategorioiden välilehdet Sivuja ei löytynyt Poista kaikki käytöstä @@ -485,10 +483,8 @@ Kindle tyylinen L-muotoinen Poista kaatumislokit - Kaatumislokit Lopetuspäivämäärä Aloituspäivämäärä - Kaatumislokit tallennettu Tallentaa virhelokit tiedostoon jaettavaksi kehittäjien kanssa Laskeva Nouseva @@ -617,7 +613,6 @@ Korkea Etukäteen lataus Automaattinen lataus luetessa - Jaa korkeat kuvat Parantaa lukijan suorituskykyä Haluatko poistaa kategorian \"%s\"\? Toimii vain kirjastossa oleville sarjoille joissa nykyinen ja seuraava luku on jo ladattu @@ -674,7 +669,6 @@ Ohitettu, koska sarja ei vaadi päivityksiä Olet poistamassa \"%s\" kirjastostasi Uusi versio on saatavilla viralliselta alustalta. Napauta saadaksesi tietää, miten siirtyä pois epävirallisesta F-Droid versiosta. - Ladattua kuvaa ei voitu jakaa Kirjoittaa yksityiskohtaiset lokit järjestelmälokiin (heikentää sovelluksen suorituskykyä) Ohitettu Varmuuskopioita kannattaa säilyttää myös muissa paikoissa. diff --git a/i18n/src/main/res/values-fil/strings.xml b/i18n/src/main/res/values-fil/strings.xml index 2535ca4e3f..a2527b8554 100644 --- a/i18n/src/main/res/values-fil/strings.xml +++ b/i18n/src/main/res/values-fil/strings.xml @@ -38,7 +38,6 @@ Susunod na kabanata Nakaraang kabanata Ihinto - Itigil Tingnan ang mga kabanata Palitan ang cover Ikategorya @@ -76,7 +75,7 @@ Nakaraan Tina-track Mga Kabanata - Manga + Mga entry ng aklatan Wala ka pang kategorya. Pindutin ang plus button para makagawa ka ng isa para maayos mo ang Aklatan mo. Bakante ang Aklatan mo Walang binasa kamakailan @@ -101,7 +100,7 @@ Hindi Palagi Isara kung nakatambay - I-lock gamit ang biometrics + Nangangailangang i-unlock Pamahalaan ang mga abiso Seguridad at privacy Kumpirmahing aalis @@ -182,7 +181,7 @@ Pinili kong filter ng kulay Pinili kong liwanag Gupitin ang gilid - Nababawasan ang pagkakaroon ng linya, pero dagdag-pasanin ito sa app + Binabawasan ang banding, ngunit maaaring makaapekto ito sa performance Totoong kulay (32-bit) Mabilis na ipakita ang kasalukuyang ginagamit kapag nakabukas ang reader Ipakita ang paraan ng pagbasa @@ -388,7 +387,6 @@ Walang nakitang resulta Wala na\'ng resulta Mga Tab - Mga Panukoy Lokal Ina-update ang kategorya Di matukoy na error @@ -458,8 +456,8 @@ Posibleng may NSFW (18+) content ang mga source mula sa extension na ito 18+ - Nilaktawan ang %d na kabanata, maaaring ito ay wala sa pinagmula o na-filter ang mga ito - Nilaktawan ang %d na (mga) kabanata, maaaring wala ang pinagmulan nila o na-filter ang mga ito + Nilaktawan ang %d na kabanata, maaaring ito ay wala sa source o na-filter ang mga ito + Nilaktawan ang %d na (mga) kabanata, maaaring wala sa source o na-filter ang mga Walang nakitang kabanata Gusto mo bang i-save at ipagpaubaya ang pagsasaayos na ito\? @@ -467,7 +465,7 @@ Ini-update na ang Ipagpaubaya %1$s: %2$s, pahina %3$d Pagsasaayos ng Kabanata - Mga naka-download na kabanata + Bilang ng na-download Maghanap Nakatago Linisin ang nakaraan @@ -480,11 +478,9 @@ Ipakita sa mga source at extension Mga source na NSFW (18+) Mag-login muli po sa MAL - Crash log Natapos basahin Sinimulang basahin - Itambak ang mga crash log - Nai-save na ang crash log + Magbahagi ng mga crash log Sine-save ang mga error log sa isang file para maibahagi sa mga developer Mga tap zone Sulok @@ -569,7 +565,7 @@ Mababa Mataas Pinakamataas - Sensitivity ng pagtago sa menu pagka-scroll + Sensitivity para sa pagtatago ng menu sa scroll Baligtad Mahaba (Maiksi+, n (na) araw ang nakalipas) Maiksi (Ngayon, Kahapon) @@ -620,10 +616,10 @@ 5% Nasimulan Pabalat lang - Nilaktawan dahil tapos na ang serye + Nilaktawan dahil kumpleto na ang serye Hindi pa nasisimulan - Nilaktawan dahil may di pa nababasang kabanata - Nilaktawan dahil wala pang nababasang kabanata + Nilaktawan dahil may di pa nabasang (mga) kabanata + Nilaktawan dahil wala pang nabasang (mga) kabanata Mag-zoom sa pahigang larawan I-pan ang malapad na larawan kapag nag-tap Matuto pa @@ -646,11 +642,9 @@ Walang nakitang naka-install na source Walang nakitang source Dami ng di pa nabasa - Hatiin ang mga matatangkad na larawan Pinapahusay ang performance ng reader Hindi nakita ang pahina %d habang naghahati Di makita ang file path ng pahina %d - Di mahati ang na-download na larawan Paano ba \'to Di ma-reset ang pagsasaayos sa reader Na-reset na ang lahat ng pagsasaayos sa reader @@ -685,7 +679,7 @@ Daluyong I-download agad Kusang mag-download habang nagbabasa - Gagana lang sa mga entry sa aklatan at kung naka-download na ang kasalukuyang kabanata pati ang susunod + Gumagana lamang sa mga entry sa aklatan at kung ang kasalukuyang kabanata at ang susunod na kabanata ay na-download na Sigurado ka ba\? Susunod ang hindi pa nababasa na chapter @@ -693,8 +687,8 @@ Marami Tatanggalin mo na ang \"%s\" mula sa iyong aklatan - Huling update ng Library: %s - Hatiin ang mga matatangkad na larawan (BETA) + Huling update sa aklatan: %s + Hatiin ang mga matatangkad na (mga) larawan (BETA) Sikat Hindi binigay ang mga permiso sa storage Nilaktawan dahil hindi kailangan ang pag-update sa serye @@ -708,7 +702,7 @@ Itambak ang mga crash log, pag-o-optimisa sa baterya Mga kategorya, panlahatang update Mga source, extension, panlahatang paghanap - Nagkaroon ang %s ng di-inaasahang error. Paki-screenshot ang error, itambak ang mga crash log, at ibahagi sa support channel sa Discord. + Nagkaroon ng hindi inaasahang error ang %s. Iminumungkahi naming ibahagi mo ang mga crash log sa aming support channel sa Discord. Ay! Wika ng App, mga abiso Buksan muli ang app @@ -742,4 +736,13 @@ Susunod na %d (mga) kabanata I-update ang kategorya + + Nawawalang %1$s na kabanata + Nawawalang %1$s mga kabanata + + Baka nawawala ang ilang kabanata + I-rotate ang malalawak na (mga) pahina para magkasya + I-flip ang oryentasyon ng mga pinaikot na malalawak na (mga) pahina + Nakapatong (Overlay) + Hatiin ang mga matatangkad na (mga) larawan \ No newline at end of file diff --git a/i18n/src/main/res/values-fr/strings-aniyomi.xml b/i18n/src/main/res/values-fr/strings-aniyomi.xml index 3f4dd10b55..f1fb35b8d3 100644 --- a/i18n/src/main/res/values-fr/strings-aniyomi.xml +++ b/i18n/src/main/res/values-fr/strings-aniyomi.xml @@ -41,12 +41,12 @@ Épisode suivant Mode d\'ajustement de l\'écran Mode image incrustée - Afficher l\'entrée + Afficher le titre Afficher l\'animé Épisodes téléchargés Source locale Animé local - Afficher le bouton Continuer à regarder/lire + Bouton \"Reprendre\" Par numéro d\'épisode Utiliser un téléchargeur externe Utiliser un téléchargeur interne @@ -86,9 +86,9 @@ Préférence pour les lecteurs externes %1$s - %2$s %1$d% % - Supprimer des chapitres - Après avoir été marqué manuellement comme lu - Après la lecture, supprimer automatiquement + Suppression des chapitres + Après avoir été marqué comme lu + Suppression automatique après lecture Permettre la suppression des chapitres marqués d\'un marque-page Catégories exclues Catégories d\'animé exclues @@ -147,7 +147,7 @@ Revisionnage Ceci supprimera la date de visionnage de cet épisode. Êtes-vous sûr·e \? Réinitialiser tous les épisodes de cet animé - Réinitialiser tous les chapitres de cette entrée + Réinitialiser tous les chapitres de ce titre %1$s : %2$s, %3$s Progression : %1$s/%2$s Progression : %1$s diff --git a/i18n/src/main/res/values-fr/strings.xml b/i18n/src/main/res/values-fr/strings.xml index b43bfd938a..63d55523be 100644 --- a/i18n/src/main/res/values-fr/strings.xml +++ b/i18n/src/main/res/values-fr/strings.xml @@ -33,7 +33,6 @@ Renommer la catégorie Déplacer vers une catégorie Changer l\'image de couverture - Arrêter Pause Chapitre précédent Chapitre suivant @@ -116,7 +115,7 @@ Désactivé Dernier chapitre lu Avant-dernier chapitre lu - Troisième chapitre avant le denier lu + Troisième chapitre avant le dernier lu Quatrième chapitre avant le dernier lu Télécharger les nouveaux chapitres @@ -132,7 +131,7 @@ Version Envoyer des rapports de plantage - Aide à corriger les erreurs. Aucune donnée sensible ne sera envoyée + Aident à corriger les bugs. Aucune donnée sensible ne sera envoyée Connexion à %1$s Nom d\'utilisateur @@ -186,7 +185,7 @@ Des nouveaux chapitres ont été trouvés La mise à jour de la couverture a échoué - Veuillez ajouter l\'entrée à votre bibliothèque avant de faire ceci + Veuillez ajouter ce titre à votre bibliothèque avant de faire cela Sélectionner l\'image de couverture Sélectionner fichier de sauvegarde @@ -329,7 +328,7 @@ Par défaut du système Notifications Sécurité et confidentialité - Imposer un déverrouillage à l\'ouverture + Nécessite un déverrouillage Verrouiller en cas d\'inactivité Toujours Jamais @@ -454,7 +453,6 @@ Migrer Grille espacée Onglets - Badges Afficher les onglets des catégories Aucune page trouvée Tout désactiver @@ -522,13 +520,11 @@ Veuillez vous reconnecter à MAL L’application de sélection de fichier est introuvable Afficher dans les listes de sources et d\'extensions - Registres d\'incident Date de fin Date de début Style Kindle - Registre d\'incident sauvegardé - Archivage des registres d\'incident - Enregistre les traces d\'incident dans un fichier pour les partager avec les développeurs + Partager les rapports de plantage + Enregistre les rapports de plantage dans un fichier pour les partager avec les développeurs Zones tactiles Bord En forme de L @@ -554,7 +550,7 @@ Afficher les zones tactiles (superposition) Exclure : %s Inclure : %s - Aucun + Aucun(e) Date de récupération du chapitre Les entrées des catégories exclues ne seront pas mis à jour même si elles appartiennent aussi à des catégories incluses. Téléchargement automatique @@ -586,7 +582,7 @@ Couverture enregistrée Couverture Manuel de suivi - Désactivé + Désactivé(e) Activé Rendre les réglages de tri et d\'affichage propres à chaque catégorie Vous n\'avez pas encore de catégories. @@ -607,10 +603,10 @@ Thème de l\'appli Certains fabricants ont mis en place des restrictions supplémentaires sur les applications qui tuent les services d\'arrière-plan. Ce site Web contient plus d\'informations sur la manière de résoudre ce problème. Activité en arrière-plan - La plus basse + Minimale Basse Élevée - La plus élevée + Maximale Sensibilité pour masquer le menu lors du défilement Inversé Long (Court+, il y a n jours) @@ -639,8 +635,8 @@ Installateur Installation de l\'extension… Total des entrées - Journalisation détaillée - Imprimer les journaux détaillés dans le journal du système (réduit les performances de l\'application) + Rapports détaillés + Inclut des rapports détaillés dans les traces systèmes (réduit les performances de l\'application) Langue Attention Les mises à jour importantes nuisent aux sources et peuvent entraîner un ralentissement des mises à jour ainsi qu\'une augmentation de l\'utilisation de la batterie. Appuyez pour en savoir plus. @@ -689,7 +685,6 @@ Aucune source installée trouvée Nombre de non-lus Aucune source trouvée - Fractionner les images trop grandes Améliore les performances du lecteur Page %d introuvable lors du fractionnement Impossible de trouver le chemin du fichier de la page %d @@ -697,7 +692,6 @@ Réinitialise le mode de lecture et l\'orientation de toutes les séries Paramètres du lecteur réinitialisés Impossible de réinitialiser les paramètres du lecteur - Impossible de diviser l\'image téléchargée Eh bien, c\'est gênant Version Langue @@ -716,7 +710,7 @@ Lavande Effacer catégorie Souhaitez-vous supprimer la catégorie « %s » \? - ErreurInterne : Vérifiez votre journal de plante pour plus d\'information + ErreurInterne : Consultez vos rapports de plantage pour plus d\'informations Réinitialiser la liste d\'agents utilisateurs Liste d\'agents utilisateurs par défaut Tout retirer @@ -726,8 +720,8 @@ La liste d\'agents utilisateurs ne peut être vide Une mise à jour est déjà en cours Raz-de-marée - Télécharger à l\'avance - Téléchargement automatique pendant la lecture + Téléchargement anticipé + Téléchargement anticipé pendant la lecture Chapitre suivant non lu Les %d suivants non lus @@ -748,14 +742,14 @@ Téléchargement automatique, téléchargement anticipé Synchronisation unidirectionnelle de la progression, synchronisation améliorée Sauvegardes manuelles et automatiques - Dump crash logs, optimisations de la batterie + Rapports de plantage, optimisations de la batterie Redémarrer l\'application Langage d\'application, notifications Sources, extensions, recherche globale Catégories, mise à jour globale Mode de lecture, affichage, navigation Verrouillage des applications, écran sécurisé - %s a rencontré une erreur inattendue. Nous vous suggérons de faire une capture d\'écran de ce message, d\'extraire les dumb crash logs et de les partager dans notre canal d\'assistance sur Discord. + %s a rencontré une erreur inattendue. Nous vous suggérons de nous partager les rapports de plantage dans notre salon d\'assistance sur Discord. Emplacement invalide : %s Chaîne d\'agent utilisateur invalide Titre inconnu @@ -763,9 +757,9 @@ File de téléchargement Copié dans le presse-papier Ignorer les chapitres en double - Vous avez une entrée dans votre bibliothèque avec le même nom. + Un titre de votre bibliothèque porte le même nom. \n -\nVoulez-vous toujours continuer \? +\nVoulez-vous vraiment continuer \? %1$s erreur : %2$s Disponible mais la source n\'est pas installée : %s *obligatoire @@ -778,4 +772,14 @@ Copier dans le presse-papier Mettre à jour la catégorie + Diviser les grandes images + Superposition + + %1$s chapitre manquant + %1$s chapitres manquants + %1$s chapitres manquants + + Il pourrait manquer des chapitres + Inverser l\'orientation des pages larges retournées + Tourner les pages larges pour qu\'elles rentrent \ No newline at end of file diff --git a/i18n/src/main/res/values-gl/strings.xml b/i18n/src/main/res/values-gl/strings.xml index 9f3b054111..b3dabd43f7 100644 --- a/i18n/src/main/res/values-gl/strings.xml +++ b/i18n/src/main/res/values-gl/strings.xml @@ -120,7 +120,7 @@ Amosar Isto non evita que as extensións non oficiais ou mal clasificadas mostren contido NSFW (+18) dentro da aplicación. Esconde os contidos das notificacións - Esconde os contidos da aplicación ao cambiar de aplicación e bloquea as capturas de pantalla + O modo discreto esconde os contidos da app ao cambiar de app e bloquea as capturas de pantalla Modo discreto Despois de 1 minuto @@ -135,7 +135,7 @@ Require reiniciar a aplicación para que surxa efecto Amosar na lista de fontes e extensións Fontes NSFW (+18) - Seguridade + Seguridade e privacidade Xestionar notificacións Confirmar a saída Formato da data @@ -191,7 +191,6 @@ Capítulo seguinte Capítulo anterior Pausar - Parar Ver capítulos Editar portada Establecer categorías @@ -221,7 +220,7 @@ Capítulos totais Alfabeticamente Quitar filtro - Sen ler + Non lidos Favoritos Filtro Menú @@ -246,7 +245,7 @@ Completado Título Licenciado - Sen ler + Non lidos En curso Descoñecido Non se atoparon capítulos @@ -355,10 +354,9 @@ Actualizar todo A sincronización destos servizos é unidireccional. Configura o seguemento para os elementos individuais dende o seus botóns de seguemento. Seguir - Só incluír as fontes fixadas + Só buscar as fontes fixadas na búsqueda global Hai %1$d elementos na base de datos que non están na biblioteca Versión - Outros Mostrar brevemente ao abrir o lector Por defecto Estatísticas @@ -415,7 +413,6 @@ Crear unha copia de seguridade Non se puido facer a copia de seguridade Actualizar as portadas da biblioteca - Gardáronse os rexistros de erros O arquivo da copia de seguridade non é válido A copia de seguriade non contén ningún elemento da biblioteca. DNS por HTTPS (DoH) @@ -441,7 +438,6 @@ Fonte local Quitar todo Recentemente - Distintivos Lavanda Inverter as zonas de toque Dereita @@ -467,7 +463,6 @@ Actualizar os servizos de seguemento ao actualizar a biblioteca Incluír: %s Non se puido baixar a listaxe de extensións - Separar imaxes altas Borrar a base de datos Anterior Só con Wi-Fi @@ -515,7 +510,7 @@ Pechar Tema, formatos de data e hora Aparencia - Mostrar o botón de seguir lendo + Botón de seguir lendo Mostrar o número de elementos Cancelar todo para esta serie Por data de subida @@ -687,7 +682,6 @@ Páxina: %1$d Produciuse un erro ao gardar a imaxe Actualizacións erradas: %1$d - Rexistros de erros Baixar Actualizacións da aplicación Preme para saber máis @@ -711,7 +705,6 @@ %1$s: %2$s, páxina %3$d Por data de subida %dmin - Non se puido separar a imaxe baixada Engadir seguimento Eliminar o historial Eliminouse o historial @@ -777,4 +770,20 @@ Nova versión dispoñible! Erros Procurar \"%1$s\" globalmente + Dividir as imaxes altas + Actualizar categoría + Ocultar elementos que xa estén na biblioteca + Copiar ao portapapeis + + O seguinte capítulo + Os seguintes %d capítulos + + + Falta %1$s capítulo + Faltan %1$s capítulos + + Pode que falte algún capítulo + Xirar as páxinas anchas para adaptalas á pantalla + Voltear a orientación das páxinas anchas xiradas + Superposición \ No newline at end of file diff --git a/i18n/src/main/res/values-he/strings.xml b/i18n/src/main/res/values-he/strings.xml index 0a2b71f446..1175a32e85 100644 --- a/i18n/src/main/res/values-he/strings.xml +++ b/i18n/src/main/res/values-he/strings.xml @@ -205,7 +205,6 @@ פרק הבא פרק קודם הפסק - עצור הצג פרקים ערוך את הכריכה סדר קטגוריות @@ -481,7 +480,6 @@ כלול רק מקורות נעוצים מעקב יש %1$d מנגה שנמצאות במסד הנתונים אבל לא בספרייה - תגים לשוניות חפש את \"%1$s\" גלובלית מצב לא ידוע @@ -623,7 +621,6 @@ כמו ספר אלקטרוני גרסה כיסוי - פיצול תמונות ארוכות אוטומטי הגבלת גיל שפה פילטר צבע משתלב diff --git a/i18n/src/main/res/values-hi/strings-aniyomi.xml b/i18n/src/main/res/values-hi/strings-aniyomi.xml index 964b252936..97350ccb3c 100644 --- a/i18n/src/main/res/values-hi/strings-aniyomi.xml +++ b/i18n/src/main/res/values-hi/strings-aniyomi.xml @@ -24,18 +24,16 @@ ऐप बंद करते समय चैप्टर कैशे साफ़ करें डेटाबेस साफ़ करें मैंगा जो आपकी लाइब्रेरी में सहेजे नहीं हैं उनका इतिहास हटाएं - क्या आपको यकीन है\? अध्यायों और गैर-पुस्तकालय मंगा की प्रगति को खो दिया जाएगा अपडेट की स्थिति, स्कोर और अंतिम अध्याय ट्रैकिंग सेवाओं से पढ़ें इतिहास पढ़ने से रोक देता है - पुस्तकालय से मंगा लाइब्रेरी में मंगा जोड़ें\? त्रुटि रोके गए मेरी लाइब्रेरी में सभी मंगा पर भी लागू करें - इस मंगा के लिए सभी अध्यायों को रीसेट करें + इस आइटम के लिए सभी अध्यायों को रीसेट करें कम संग्रहण स्थान के कारण अध्याय डाउनलोड नहीं कर सके चेतावनी: बड़े बल्क डाउनलोड के कारण स्रोत धीमे हो सकते हैं। और/या ताचियोमी को ब्लॉक कर सकते हैं। अधिक जानने के लिए यह टैप करें । - ताचियोमी के लिए वेबव्हिव आवश्यक है + ताचियोमी के लिए WebView आवश्यक है डाउनलोड रोक दिया है अध्याय अद्यतन diff --git a/i18n/src/main/res/values-hi/strings.xml b/i18n/src/main/res/values-hi/strings.xml index 18553a51d9..709f2b466f 100644 --- a/i18n/src/main/res/values-hi/strings.xml +++ b/i18n/src/main/res/values-hi/strings.xml @@ -6,9 +6,9 @@ पदचिह्न इतिहास सेटिंग्स - संग्रह + पुस्तकालय इतिहास - नए अपडेट्स + नए अपडेट बैकअप और पुनर्स्थापना सेटिंग फिल्टर @@ -29,7 +29,7 @@ बुकमार्क अध्याय अध्याय बुकमार्क हटाये हटाये - अद्यतन पुस्तकालय + पुस्तकालय अपडेट करें संपादित करें जोड़े श्रेणियाँ जोड़े @@ -37,7 +37,6 @@ श्रेणियाँ का पुन:नामकरण श्रेणियां निर्धारित करें कवर संपादित करें - रोके ठहराव पिछला अध्याय अगला अध्याय @@ -148,6 +147,8 @@ साफ़ करने के दौरान त्रुटि हुई कुकीज़ को साफ़ करें कुकीज़ को साफ़ किया हुआ + उन आइटम का इतिहास हटाएं जो आपकी पुस्तकालय में सहेजी नहीं गई हैं + क्या आपको यकीन है\? आपके द्वारा पढ़े गए अध्याय और गैर-पुस्तकालय आइटम की प्रगति खो जाएगी प्रविष्टियां हटाई गईं रिफ्रेश ट्रैकिंग संस्करण @@ -204,7 +205,7 @@ अध्याय डाउनलोड नहीं कर सका। आप डाउनलोड अनुभाग में फिर से कोशिश कर सकते हैं नए अध्याय पाए गए कवर को अपडेट करने में विफल - ऐसा करने से पहले कृपया अपनी लाइब्रेरी में मंगा को जोड़ें + ऐसा करने से पहले कृपया अपनी पुस्तकालय में आइटम जोड़ें कवर छवि का चयन करें बैकअप फ़ाइल का चयन करें डाउनलोड @@ -264,7 +265,7 @@ पेज लोड हो रहे है … पृष्ठों को लोड करने में विफल है: %1$s संवाद के लिए लंबी प्रेस - वेब दृश्य में खोलें + WebView में खोलें 32 बिट रंग पढ़े हुए अध्यायों को छोड़ें रंग फिल्टर मिश्रण मोड @@ -297,7 +298,7 @@ चालू करे सिस्टम का पालन करें सूचनाओं का प्रबंधन - सुरक्षा + सुरक्षा और गोपनीयता अनलॉक की आवश्यकता है निष्क्रिय होने पर लॉक करें हमेशा @@ -332,8 +333,8 @@ ईमेल पता हमेशा अध्याय संक्रमण दिखाएं - %d शीर्षक के लिए - %d शीर्षकों के लिए + %d आइटम के लिए + %d आइटम के लिए मेन्यू पुनःक्रमित @@ -346,7 +347,7 @@ %d एक्सटेंशन अपडेट उपलब्ध एक्सटेंशन अपडेट - वेबव्यू में वेबसाइट देखें + WebView में वेबसाइट देखें अद्यतन पुस्तकालय पठन फ़िल्टर किए गए अध्यायों को छोड़ें @@ -377,12 +378,12 @@ बैकअप फेल पुनर्स्थापना रद्द की गई पुनर्स्थापना पहले से ही प्रगति पर है - बैकप पहले से ही प्रगति में है + बैकअप पहले से ही प्रगति में है आखरी इस्त्तमाल किया गया अद्यतन के लिए जाँच स्थानीय स्रोत गाइड %02d मिनट,%02d सेकंड - अपने पुस्तकालय में सभी मंगा फ़िल्टर करे + आपकी पुस्तकालय में सभी आइटम को फ़िल्टर करता है %1$s बचा हुआ %1$s बचे हुए @@ -401,20 +402,19 @@ %1$s में %2$s त्रुटि के साथ किया गया %1$s में %2$s त्रुटियों के साथ किया गया - ट्रैकिंग सेवाओं में अध्याय की प्रगति को अद्यतन करने के लिए एकतरफा सिंक। अपने ट्रैकिंग बटन से व्यक्तिगत मंगा प्रविष्टियों के लिए ट्रैकिंग सेट अप करें। + ट्रैकिंग सेवाओं में अध्याय की प्रगति को अद्यतन करने के लिए एकतरफा सिंक। अपने ट्रैकिंग बटन से व्यक्तिगत प्रविष्टियों के लिए ट्रैकिंग सेट अप करें। रिफ्रेश पुस्तकालय मंगा कवर यह एक्सटेंशन आधिकारिक ताचियोमी एक्सटेंशन सूची से नहीं है। अनौपचारिक अपलोड तिथि द्वारा डेटा अनुपलब्ध स्रोत: - बैकअप में कोई मंगा नहीं होता है। + बैकअप में कोई पुस्तकालय आइटम नहीं हैं। अमान्य बैकअप फ़ाइल लाइब्रेरी को अपडेट करते समय नए कवर और विवरण की जांच करें मेटाडेटा को स्वचालित रूप से ताज़ा करें प्रवास टैब - बैज श्रेणी टैब दिखाएं आरामदायक ग्रिड कोई पृष्ठ नहीं मिला @@ -458,8 +458,8 @@ 18+ यह अनौपचारिक या संभावित रूप से फ़्लैग किए गए एक्सटेंशन को ऐप के भीतर NSFW (18+) सामग्री के सामने आने से नहीं रोकता है। - %d अध्याय को छोड़कर, या तो स्रोत में यह अनुपलब्ध है या इसे फ़िल्टर कर दिया गया है - %d अध्यायों को छोड़कर, या तो स्रोत में वे नहीं हैं या उन्हें फ़िल्टर कर दिया गया है + %d अध्याय को छोड़ा जा रहा है, या तो स्रोत में यह नहीं है या इसे फ़िल्टर कर दिया गया है + %d अध्यायों को छोड़ा जा रहा है, या तो स्रोत उन्हें याद कर रहा है या उन्हें फ़िल्टर कर दिया गया है अपडेट किए गए डिफ़ॉल्ट अध्याय सेटिंग्स कोई अध्याय नहीं मिला @@ -478,12 +478,10 @@ स्रोत माइग्रेशन गाइड स्रोतों और एक्सटेंशन सूचियों में दिखाएं NSFW (18+) स्रोत - क्रैश लॉग कोई फ़ाइल पिकर ऐप नहीं मिला कृपया फिर से MAL पर लॉगिन करें समाप्ति की तिथि आरंभ करने की तिथि - क्रैश लॉग सहेजे गए डेवलपर्स के साथ साझा करने के लिए फ़ाइल में त्रुटि लॉग सहेजता है डंप क्रैश लॉग टैप ज़ोन @@ -500,8 +498,8 @@ बैकअप फ़ाइल से डेटा बहाल किया जाएगा। \n \nआपको किसी भी लापता एक्सटेंशन को इंस्टॉल करना होगा और बाद में सेवाओं को ट्रैक करने के लिए लॉग इन करना होगा ताकि उनका उपयोग किया जा सके। - यदि दोहरे पृष्ठ विभाजन की नियुक्ति पढ़ने की दिशा से मेल नहीं खाती है - दोहरे पृष्ठ विभाजन प्लेसमेंट को पलटना + यदि स्प्लिट वाइड पेजों का प्लेसमेंट पढ़ने की दिशा से मेल नहीं खाता है + स्प्लिट पेज प्लेसमेंट को उल्टा करें चौड़े पृष्ठ को दो भागों में बांट दे अभी डाउनलोड करना शुरू करें विवरण देखने के लिए टैप करें @@ -557,7 +555,7 @@ शिज़ुकु नहीं चल रहा है उल्टी काला और सफेद - पुस्तक के शीर्षक के अनुसार फोल्डर बनाता है + आइटम के शीर्षक अनुसार फोल्डर बनाता है ट्रैकिंग गाइड उन्नत सेवाएं MIUI ऑप्टिमाइज़ेशन अक्षम होने पर बैकअप/पुनर्स्थापना ठीक से काम नहीं कर सकता है। @@ -584,9 +582,9 @@ दिनांक कवर पृष्ठ पृष्ठों को अलग-अलग फ़ोल्डरों में सहेजें - बहिष्कृत श्रेणियों की पुस्तकें डाउनलोड नहीं की जाएंगी, भले ही वे शामिल श्रेणियों में भी हों। + बहिष्कृत श्रेणियों की आइटम डाउनलोड नहीं की जाएंगी, भले ही वे शामिल श्रेणियों में भी हों। सिस्टम लॉग में वर्बोज़ लॉग प्रिंट करें (ऐप प्रदर्शन को कम करता है) - ऐसी सेवाएँ जो विशिष्ट स्रोतों के लिए उन्नत सुविधाएँ प्रदान करती हैं। आपकी पुस्तकालय में जोड़े जाने पर पुस्तकें स्वचालित रूप से ट्रैक की जाती हैं। + ऐसी सेवाएँ जो विशिष्ट स्रोतों के लिए उन्नत सुविधाएँ प्रदान करती हैं। आपकी पुस्तकालय में जोड़े जाने पर आइटम को स्वचालित रूप से ट्रैक किया जाता है। HTTPS उपर DNS (DoH) वर्बोज़ लॉगिंग गुप्त मोड अक्षम करें @@ -601,13 +599,13 @@ सभी अद्यतन करें चेतावनी भाषा - स्वचालित बैकअप की अत्यधिक अनुशंसा की जाती है। आपको अन्य जगहों पर भी प्रतियां रखनी चाहिए। + आपको अन्य स्थानों पर भी बैकअप की प्रतियाँ रखनी चाहिए। बड़े अपडेट स्रोतों को नुकसान पहुंचाते हैं और इससे धीमे अपडेट हो सकते हैं, और बैटरी का उपयोग भी बढ़ सकता है। अधिक जानने के लिए टैप करें । ऐप अपडेट हर 3 दिन केवल वाई-फ़ाई पर - डेटाबेस में %1$d मंगा हैं जो पुस्तकालय में नहीं है - डेटाबेस साफ़ करें + डेटाबेस में %1$d गैर-पुस्तकालय आइटम + साफ़ करने के लिए कुछ नहीं है शीर्षक अद्यतन न करें CBZ आर्कैव के रूप में सहेजें गोपनीयता नीति @@ -630,26 +628,25 @@ रिवर्स पोर्ट्रेट %1$d अपडेट विफल %1$d अपडेट छोड़े गए - पैन पर नेविगेट करें + टैप करते समय छवियों को पैन करें ज़ूम लैंडस्केप इमेज श्रृंखला को शीर्ष पर ले जाएं कोई स्रोत नहीं मिला कोई स्थापित स्रोत नहीं मिला अपठित गिनती जब बैटरी कम नहीं - वेब्यु डेटा साफ हो गया + WebView डेटा साफ हो गया गिटहब में खोलें चित्र सहेजने में त्रुटि लंबी डाउनलोड किए गए चित्रों के हिस्से करके पाठमाला के प्रदर्शन में सुधार लाता है पेज %d हिस्से करते वक्त नहीं मिला बंद करे - वेबव्यू डाटा साफ करें + WebView डाटा साफ करें एक जैसे पिन किए गए स्रोत दिखाएँ उनके संबंधित भाषा समूहों में पिन किए गए स्रोतों को फिर से दिखाएं चित्र %d का फ़ाइल पथ नहीं खोजा जा सका बैकअप करने के लिए कोई पुस्तकालय प्रविष्टि नहीं एक नया संस्करण आधिकारिक रिलीज के द्वारा उपलब्ध है। अनौपचारिक F-Droid रिलीज से माइग्रेट करने का तरीका जानने के लिए यह टैप करें । - लंबे चित्रों की ऑटो हिस्से संस्करण भाषा आयु रेटिंग @@ -660,7 +657,6 @@ श्रेणी हटाएँ कोई विवरण नहीं क्या आप %s कैटेगरी को हटाना चाहते हैं \? - डाउनलोड किया गया चित्र विभाजित नहीं हो सका प्रति श्रृंखला रीडर सेटिंग्स को रीसेट करें सभी श्रृंखलाओं के रीडिंग मोड और ओरिएंटेशन को रीसेट करता है सभी रीडर सेटिंग्स रीसेट करें @@ -672,7 +668,7 @@ आंतरिक त्रुटि : अधिक जानकारी के लिए क्रैश लॉग की जाँच करें केवल अनमीटर्ड कनेक्शन पर RARv5 प्रारूप समर्थित नहीं है - पुस्तकालय पिछली बार अपडेट किया गया: %1$s + पुस्तकालय पिछली बार अपडेट किया गया: %s अपना हाल ही में अपडेट किया गया मांगा देखें केवल लाइब्रेरी में प्रविष्टियों पर काम करता है। और यदि वर्तमान अध्याय और अगला अध्याय पहले ही डाउनलोड हो चुका है कस्टम कवर @@ -691,7 +687,7 @@ मल्टी क्या आपको यकीन है \? ऐप लॉक लागू होने पर विजेट उपलब्ध नहीं होता - आप इस मांगा को अपनी लाइब्रेरी से निकालने वाले हैं + आप अपनी पुस्तकालय से \"%s\" को निकालने जा रहे हैं एक अपडेट पहले से चल रहा है अंतिम पढ़ा गया अध्याय खोलने में असमर्थ यूजर एजेंट स्ट्रिंग(User agent string) डिफॉल्ट रिसेट करें diff --git a/i18n/src/main/res/values-hr/strings.xml b/i18n/src/main/res/values-hr/strings.xml index 3c50d21014..0157e68ebb 100644 --- a/i18n/src/main/res/values-hr/strings.xml +++ b/i18n/src/main/res/values-hr/strings.xml @@ -117,7 +117,6 @@ Sljedeće poglavlje Prethodno poglavlje Zaustavi - Prekini Pogledaj poglavlja Uredi naslovnicu Postavi kategorije @@ -424,7 +423,6 @@ Deaktiviraj sve Aktiviraj sve Nema stranica - Značke Kartice Nakratko prikaži trenutačni modus kad se čitač otvori Prikaži modus čitanja @@ -491,12 +489,10 @@ Nije pronađen nijedan program za biranje datoteka Ponovo se prijavi na MAL Prikaži u popisu izvora i proširenja - Zapisi rušenja programa Datum kraja Datum početka - Zapisi rušenja programa su spremljeni Sprema zapise grešaka u datoteku za obavještavanje programera - Spremi zapise rušenja programa + Dijeli zapise prekida programa Područja dodira Rub Kao Kindle @@ -511,9 +507,9 @@ Podaci datoteke sigurnosne kopije će se obnoviti. \n \nZa upotrebu podataka, morat ćeš instalirati nedostajuća proširenja i nakon toga se prijaviti na usluge praćenja. - Ako se dvostranična podjela ne podudara sa smjerom čitanja - Preokreni smještaj dvostranične podjele - Dvostranična podjela + Ako se položaj rastavljenih širokih stranica ne podudara sa smjerom čitanja + Obrni položaj rastavljene stranice + Rastavi široke stranice Kratko prikaži kada je čitač otvoren Desno Lijevo @@ -625,7 +621,7 @@ Najniža Objavljivanje završeno Prekinuto - Prekid + Zaustavljeno Za pomoć o tome kako popraviti greške aktualiziranja biblioteke, pogledaj %1$s Spremi kao CBZ arhivu ČPP i vodiči @@ -648,15 +644,13 @@ Nema unosa u biblioteci za spremanje u sigurnosnu kopiju Poboljšava performanse čitača Potpun popis - Nije bilo moguće podijeliti preuzetu sliku - Rastavi visoke slike WebView podaci su izbrisani Izbriši WebView podatke Popis čitanja Popis želja Na popisu čekanja Popis nedovršenih - Stranica %d nije pronađena tijekom razdvajanja + Stranica %d nije pronađena tijekom rastavljanja RARv5 format nije podržan Radi samo na unosima u biblioteci i ako je trenutačno poglavlje plus sljedeće već preuzeto @@ -699,4 +693,14 @@ Sljedećih %d poglavlja Aktualiziraj katogoriju + Rastavi visoke slike + Oznake + + Nedostaje %1$s poglavlje + Nedostaju %1$s poglavlja + Nedostaje %1$s poglavlja + + Možda nedostaje poglavlje + Prilagodi prikaz širokih stranica okretanjem + Preokreni položaj širokih stranica \ No newline at end of file diff --git a/i18n/src/main/res/values-hu/strings.xml b/i18n/src/main/res/values-hu/strings.xml index 4528a66234..14c32c490c 100644 --- a/i18n/src/main/res/values-hu/strings.xml +++ b/i18n/src/main/res/values-hu/strings.xml @@ -86,7 +86,6 @@ Kategória átnevezése Kategóriák beállítása Borító szerkesztése - Megállítás Szüneteltetés Előző fejezet Következő fejezet @@ -236,7 +235,6 @@ Frissítések keresése Nyílt forráskódú licenc Lapok - Jelvények Előző %1$s. fejezet @@ -266,7 +264,6 @@ Másolás Áttelepítés %1$s. fejezet - Összeomlási naplók %1$s. fejezetek A(z) %1$s. fejezet és %2$d egyéb fejezetek %1$s:%2$s, %3$d. oldal @@ -591,7 +588,6 @@ Hát ez kínos... A nagy frissítések kárt okoznak a forrásoknak, és lassabb frissítésekhez, valamint megnövekedett akkumulátorhasználathoz vezethetnek. Koppintson további információért. %1$d frissítés sikertelen - Nagy képek automatikus elválasztása Növeli a teljesítményt a nagy képek elválasztásával. Nem bejelentkezett trackerek: Inkognitó mód @@ -647,7 +643,6 @@ WebView adatok törölve Helyreállítja az olvasó módot és orientációt minden elemnél Nem lehetett visszaállítani az olvasói beállításokat - Hibaüzenetek elmentve Akkumulátor optimalizálás kikapcsolása Segít háttérbeli könyvtár frissítésben és a biztonsági mentésekben Akkumulátor optimalizálás már ki van kapcsolva @@ -667,7 +662,6 @@ Letöltés befejeződött %d oldal nem található felosztás közben Nem található az útvonal a %d-ik oldalhoz - Nem sikerült a letöltött képet felosztani Ez a kép legyen a fedlap\? %1$s fejezet és még 1 diff --git a/i18n/src/main/res/values-in/strings.xml b/i18n/src/main/res/values-in/strings.xml index 98b55655a3..9451920ff2 100644 --- a/i18n/src/main/res/values-in/strings.xml +++ b/i18n/src/main/res/values-in/strings.xml @@ -35,7 +35,6 @@ Ubah nama kategori Masukkan ke kategori Ubah gambar sampul - Berhenti Hentikan sementara Bab sebelumnya Bab selanjutnya @@ -298,7 +297,7 @@ Pindahkan ke bawah Lainnya Pembaruan - Kunci dengan biometrik + Memerlukan buka kunci Kunci saat diam Selalu Tidak pernah @@ -405,7 +404,6 @@ Menurut tanggal pengunggahan Grid nyaman Tab - Penanda Tampilkan tab kategori Halaman tidak ditemukan Nonaktifkan semua @@ -446,7 +444,7 @@ 18+ Hal ini tidak mencegah ekstensi yang tidak resmi atau berpotensi salah ditandai untuk menampilkan konten NSFW (18+) di dalam aplikasi. - Melewati %d bab, entah sumber kehilangan mereka atau karena sudah difilter + Melewati %d bab, entah sumbernya hilang atau telah difilter Tidak ada bab yang ditemukan Pengaturan bab bawaan diperbarui @@ -474,11 +472,9 @@ Menurut nomor bab Menurut tanggal unggahan Dilacak - Buat log kerusakan - Log kerusakan + Bagikan log kerusakan Tanggal selesai Tanggal mulai - Log kerusakan disimpan Simpan log kerusakan ke sebuah berkas untuk dibagikan dengan pengembang aplikasi Zona ketuk Kanan dan Kiri @@ -634,14 +630,12 @@ Sumber yang diinstal tidak ditemukan Tidak ada sumber yang ditemukan Jumlah belum dibaca - Membagi gambar panjang Meningkatkan kinerja pembaca Halaman %d tidak ditemukan saat dipisah Tidak dapat menemukan jalur file halaman %d Atur ulang semua pengaturan pengguna Muat ulang pengaturan per-pengguna Atur ulang mode dan orientasi dari semua seri - Tidak bisa memisah gambar yang telah diunduh Yah, ini aneh Tidak bisa mengatur ulang setelan pembaca Versi @@ -689,7 +683,7 @@ Sumber, ekstensi, pencarian global Mode membaca, tampilan, navigasi Sinkronisasi kemajuan satu arah, sinkronisasi yang ditingkatkan - %s mengalami kesalahan yang tak terduga. Kami menyarankan Anda mengambil tangkapan layar pesan ini, membuat log kerusakan, dan kemudian membagikannya di saluran dukungan kami di Discord. + %s mengalami kesalahan tak terduga. Kami menyarankan Anda membagi log kerusakan di saluran dukungan kami di Discord. Unduh otomatis, unduh di depan Kunci aplikasi, layar aman Bahasa aplikasi, notifikasi @@ -716,4 +710,12 @@ Salin ke papan klip Perbarui kategori + Potong gambar tinggi + Lapisan awal + Memutar halaman lebar agar pas + Orientasi balik halaman lebar yang diputar + + Bab %1$s yang hilang + + Mungkin ada bab yang terlewat \ No newline at end of file diff --git a/i18n/src/main/res/values-it/strings.xml b/i18n/src/main/res/values-it/strings.xml index 666996f593..cdc79715c5 100644 --- a/i18n/src/main/res/values-it/strings.xml +++ b/i18n/src/main/res/values-it/strings.xml @@ -32,7 +32,6 @@ Rinomina categoria Assegna categorie Modifica copertina - Ferma Pausa Capitolo precedente Capitolo successivo @@ -446,7 +445,6 @@ Nessuna pagina trovata Per data di aggiunta Schede - Etichette Dati Fonti mancanti: Il backup non contiene alcuna voce di libreria. @@ -527,12 +525,10 @@ Bordo Stile Kindle A forma di L - Registro dei crash Data fine Data inizio - Registro dei crash salvato Salva un registro degli errori su un file per condividerlo con gli sviluppatori - Salva un registro dei crash + Condividi il registro dei crash Decrescente Crescente Per numero di capitolo @@ -691,14 +687,12 @@ Nessuna fonte trovata Conteggio non letti Migliora le prestazioni del lettore - Dividi le immagini troppo alte Pagina %d non trovata durante la divisione Impossibile trovare il percorso della pagina %d Reimposta la modalità di lettura e l\'orientamento per tutte le serie Reimposta le opzioni del lettore per ogni serie Ripristinate tutte le opzioni del lettore Impossibile ripristinare le opzioni del lettore - Impossibile separare le immagini scaricate Beh, questo è imbarazzante Classificazione per età Versione @@ -746,7 +740,7 @@ Ricerca… Lingua dell\'app, notifiche Tema, formato data e ora - %s ha riscontrato un errore imprevisto. Ti suggeriamo di fare uno screenshot di questo messaggio, scaricare i registri degli arresti anomali e condividerli nel nostro canale di supporto su Discord. + %s ha riscontrato un errore imprevisto. Ti suggeriamo di condividere il registro degli arresti anomali nel nostro canale di supporto su Discord. Categorie, aggiornamenti globali Download automatico, download anticipato Fonti, estensioni, ricerca globale @@ -782,4 +776,14 @@ Nascondi le voci già in libreria Copia negli appunti Aggiorna categoria + Dividi immagini alte + Sovrimpressione + Ruota le pagine larghe per adattareaallo schermo + Capovolgi l\'orientamento delle pagine larghe ruotate + + Manca %1$s capitolo + Mancano %1$s capitoli + Mancano %1$s capitoli + + Potrebbero mancare alcuni capitoli \ No newline at end of file diff --git a/i18n/src/main/res/values-ja/strings.xml b/i18n/src/main/res/values-ja/strings.xml index 73c5057eb1..bc7c1182a8 100644 --- a/i18n/src/main/res/values-ja/strings.xml +++ b/i18n/src/main/res/values-ja/strings.xml @@ -147,7 +147,6 @@ 追加 カテゴリー名を編集 カテゴリーを設定 - 停止 削除 インストール 取り消し @@ -310,7 +309,6 @@ ローカルソース WebViewでサイトを開く タブ - 情報バッジ メールアドレス 残り%1$s件 @@ -470,16 +468,14 @@ ファイルを選択できるアプリが見つかりません もう一度MALにログインしてください アイテム数を表示する - クラッシュログ 読み終わった日付 読み始めた日付 - クラッシュログが保存されました 章の番号順 アップロードされた日付順 登録済み 開発者に渡すよう、エラー ログを保存します - クラッシュ ログをダンプ + クラッシュ ログを共有 タップ可能なゾーン 右と左 Kindleスタイル @@ -634,7 +630,6 @@ ソースが見つかりません インストール済みのソースが見つかりません 未読の章数 - 長い画像を分割 ビューアのパフォーマンスを改善します 分割時にページ%dが見つかりませんでした シリーズ別のビューア設定をリセット @@ -642,7 +637,6 @@ ページ%dのファイルパスが見つかりませんでした ビューア設定を全てリセットしました ビューア設定をリセットできませんでした - ダウンロードした画像を分割できませんでした えっと、こりゃまずいですね バージョン 言語 @@ -686,7 +680,7 @@ ストレージ権限を持っていません シリーズは更新を必要としないため、スキップされました 検索… - %sでは予期せぬエラーが発生しました。お手数ですが、このメッセージのスクリーンショットを作成し、クラッシュ ログをダンプして、Discord のサポート チャネルで共有するようお願い致します。 + %sでは予期せぬエラーが発生しました。お手数ですが、クラッシュ ログを Discord のサポート チャネルで共有するようお願い致します。 無効な場所: %s 不明なタイトル ユーザー エージェント文字列が無効です @@ -722,4 +716,12 @@ 次の%d章 カテゴリを更新 + 長い画像を分割 + オーバーレイ + 画面に合わせるように幅広いページを回転 + 回転した幅広いページの向きを反転 + + %1$s章が欠けています + + 一部の章が足りない可能性あり \ No newline at end of file diff --git a/i18n/src/main/res/values-jv/strings.xml b/i18n/src/main/res/values-jv/strings.xml index 0ad9c74380..0b56c6bc5a 100644 --- a/i18n/src/main/res/values-jv/strings.xml +++ b/i18n/src/main/res/values-jv/strings.xml @@ -98,7 +98,6 @@ Setelan Liyane Jeneng - Mandheg Manga Donlotan rampung Donloder diff --git a/i18n/src/main/res/values-ka-rGE/strings-aniyomi.xml b/i18n/src/main/res/values-ka-rGE/strings-aniyomi.xml index 807bbb2520..b580d7306a 100644 --- a/i18n/src/main/res/values-ka-rGE/strings-aniyomi.xml +++ b/i18n/src/main/res/values-ka-rGE/strings-aniyomi.xml @@ -16,12 +16,12 @@ თავის ქეშის გასუფთავება მონაცემთა ბაზის გასუფთავება ბიბლიოთეკაში შეუნახავი მანგების ისტორიის წაშლა - დარწმუნებული ხარ\? წაკითხული თავები და ბიბლიოთეკაში არ არსებული მანგების პროგრესი დაიკარგება + დარწმუნებული ბრძანდებით\? წაკითხული თავები და ბიბლიოთეკაში არ არსებული ჩანაწერების პროგრესი დაიკარგება ანახლებს სტატუსს, შეფასებას და ბოლო თავს წაკითხულს თვალყურის სადევნებელ სერვისებიდან დავამატო მანგა ბიბლიოთეკაში\? შეცდომა დაპაუზებულია - ამ მანგას ყველა თავის წაკითხვა + ამ ჩანაწერის ყველა თავის თავიდან წაკითხვა შეუძლებელია თავების გადმოწერა დისკზე ადგილის უკმარისობის გამო WebView არის აუცილებელი Tachiyomi-ს სამუშაოდ გადმოწერა დაპაუზებულია diff --git a/i18n/src/main/res/values-ka-rGE/strings.xml b/i18n/src/main/res/values-ka-rGE/strings.xml index 44a5fd8078..a4cc9d9034 100644 --- a/i18n/src/main/res/values-ka-rGE/strings.xml +++ b/i18n/src/main/res/values-ka-rGE/strings.xml @@ -1,7 +1,7 @@ სახელი - მანგა + ბიბლიოთეკის ჩანაწერები ისტორია მეტი პარამეტრები @@ -36,7 +36,6 @@ კატეგორიის რედაქტირება კატეგორიის სახელის ვცლილება თავების ხილვა - გაჩერება პაუზა წინა თავი შემდეგი თავი @@ -44,7 +43,7 @@ წაშლა გაგრძელება ბრაუზერში გახსნა - აპლიკაციაში გახსნა + WebView-ში გახსნა ჩვენების რეჟიმი ჩვენება კომპაქტური ბადე @@ -86,8 +85,8 @@ თარიღის ფორმატი გამოსვლის დადასტურება შეტყობინებების მართვა - უსაფრთხოება - თითის ანაბეჭდით ბლოკი + უსაფრთხოება და კონფიდენციალობა + განბლოკვის მოთხოვნა ბლოკირება უმოქმედობის დროს ყოველთვის არასდროს @@ -198,11 +197,11 @@ მაქსიმალური რეზერვი რეზერვი შექმნილია არასწორი სარეზერვო ფაილი - რეზერვი არ შეიცავს არცერთ მანგას. + მარქაფი ბიბლიოთეკის ჩანაწერებს არ შეიცავს. დაკარგული წყაროები: აღდგენა შესრულებულია %02d წუთი, %02d წამი - რეზერვის შემქნა პროგრესშია + მარქაფი უკვე მიმდინარეობს რას გსურს რომ შეუქმნა რეზერვი\? რეზერვის შექმნა რეზერვის შექმნა ვერ მოხერხდა @@ -227,7 +226,7 @@ ქრეშის რეპორტის გაგზავნა ეხმარება შეცდომების გამოსწორებაში. სენსიტიური მონაცემები არ იქნება გაგზავნილი მხოლოდ გადმოწერილები - ფილტრავს ყველა მანგას შენს ბიბლიოთეკაში + თქვენს ბიბლიოთეკაში ყველა ჩანაწერის გაფილტვრა შესვლა %1$s-ში მომხმარებლის სახელი ელ. ფოსტა @@ -239,11 +238,10 @@ გამოსვლა წარმატებით მოხერხდა დაფიქსირდა უცნობი შეცდომა კატეგორია ახლდება - ნიშნაკები ჩანართები შედეგების სიის დასასრული შედეგი ვერ მოიძებნა - ვებსაიტის ნახვა აპლიკაციაში + ვებსაიტის ნახვა WebView-ში ლოცალური წყარო სხვა ბოლოს გამოყენებული @@ -294,7 +292,7 @@ სურათი შენახულია გვერდი: %1$d შემდეგი თავი ვერ მოიძებნა - სურათის გადმოტვირთვა ვერ მოხერხდა + გამოსახულებსი ჩატვირთვის შეცდომა ამ სერიებისთვის კითხვის რეჟიმი დამთავრებულია: @@ -319,19 +317,19 @@ თავი %1$s და %2$d მეტი თავები %1$s ვერ მოხერხდა ყდის განახლება - გთხოვთ ჯერ დაამატოთ მანგა თქვენს ბიბლიოთეკაში + გთხოვთ ჯერ დაამატოთ ჩანაწერი თქვენს ბიბლიოთეკაში ამოირჩიე ყდის სურათი ამოირჩიე რეზერვის ფაილი გადმოწერა განახლება ვერ მოიძებნა განახლების ძებნა… გადმოწერა… - გადმოწერა დასრულებულია + განახლების დასაყენებლად დაატყაპუნეთ გადმოწერის შეცდომა ხელმისაწვდომია ახალი ვერსია! გადმოწერები არ არის განახლებები არ არის - შენი ბიბლიოთეკა ცარიელია, დაამატე სერიები შენს ბიბლიოთეკაში \"დაათვალიერე\"-დან. + თქვენი ბიბლიოთეკა ცარიელია Wi-Fi კავშირი არ არის ხელმისაწვდომი ინტერნეტთან კავშირი არ არის ხელმისაწვდომი საერთო @@ -355,7 +353,7 @@ ნდობა არასანდო არასანდო დამატება - დამატება აღარ არის ხელმისაწვდომი. + დამატება ხელმისაწვდომი აღარაა. მან შეიძლება არასწორად იმუშაოს და აპლიკაციას პრობლემები შეუქმნას. გირჩევთ, წაშალოთ ის. დამატება არ არის Tachiyomi-ს ოფიციალური დამატებების სიიდან. ორჯერ დაჭერისას ანიმაციის სისწრაფე აუმჯობესებს ხარისხს, თუმცა ამცირებს წარმადობას @@ -365,7 +363,7 @@ დიალოგი ხანგრძლივი დაჭერისას ვებ კომიქსი დანომრილი - შეიყვანე მხოლოდ მიმაგრებული წყაროები + გლობალურ ძებნაში მხოლოდ მიჭიკარტებულ წყაროებში ძებნა გამორთე ბატარეის ოპტიმიზაცია ეხმარება უკანა ფონში მიმდინარე ბიბლიოთეკის განახლებებსდა რეზერვს ღია კოდის ლიცენზია @@ -384,7 +382,7 @@ ჩართე ყველა გამორთე ყველა აპლიკაციებს შორის გადართვისას შემადგენლობის დამალვა და სკრინშოტების დაბლოკვა - ცალმხრივი სინქრონიზაცია თვალყურის სადევნებელ სერვისებში თავების პროგრესის განსაახლებლად. მიადევნე თვალყური ინდივიდუალურ მანგებს მათი ჩანართებიდან + ცალმხრივი სინქრონიზაცია თვალყურის სადევნებელ სერვისებში თავების პროგრესის განსაახლებლად. მიადევნე თვალყური ინდივიდუალურ ჩანაწერებს მათი ჩანართებიდან. გადაფარება ვერ მოხერხდა CloudFlare-ს შემოვლა ორივე @@ -405,8 +403,8 @@ ნაპოვნია %1$d ახალი თავი - ნაპოვნია ახალი თავები ნაწარმოებისთვის - ნაპოვნია ახალი თავები %d ნაწარმოებისთვის + %d ჩანაწერისთვის + %d ჩანაწერისთვის წყარო ვერ მოიძებნა @@ -425,8 +423,8 @@ ასამოქმედებლად საჭიროა აპლიკაციის გადატვირთვა ქსელი - გაკეთებულია %1$s %2$s შეცდომით - გაკეთებულია %1$s %2$s შეცდომებით + დასრულების დრო %1$s. %2$s შეცდომა + დასრულების დრო %1$s. %2$s შეცდომა კითხვის რეჟიმი @@ -444,7 +442,7 @@ შეიძლება შეიჩაცდეს უცენზურო(18+) კონტენტს 18+ მარჯვენა და მარცხენა - გვერდების ორმაგი გაყოფა (ალფა) + განიერი გვერდების გაყოფა ეს არ აღმოფხვრავს პროგრამაში NSFW (18+) შიგთავსის არაოფიციალურ, ან შესაძლო არასწორად მონიშნული გაფართოებებოიდან გამოჩენას. საგნების რაოდენობის ჩვენება პარამეტრების ძიება @@ -459,4 +457,5 @@ ნაგულისხმევი დააჭირე დეტალების სანახავად გადმოწერის რიგი + წაუკითხავი \ No newline at end of file diff --git a/i18n/src/main/res/values-kk/strings.xml b/i18n/src/main/res/values-kk/strings.xml index c153d4992b..a278804e45 100644 --- a/i18n/src/main/res/values-kk/strings.xml +++ b/i18n/src/main/res/values-kk/strings.xml @@ -127,7 +127,6 @@ Санаттарды орнату Мұқабасын өзгерту Тарауларды қарау - Доғару Тоқтату Алдыңғы тарау Келесі тарау @@ -330,7 +329,6 @@ %1$s дегеннен кейін %2$s қателікпен орындалды %1$s дегеннен кейін %2$s қателікпен орындалды - Ұзын суреттерді авто бөлу Тараулардың ауысуын әрдайым көрсету Құлыпталған портрет Жоғары @@ -476,7 +474,6 @@ User agent бос бола алмайды Оқыма баптауларын әрбір туынды үшін қалпына келтіру Барлық туындылар үшін оқу режимі мен бағдарын қалпына келтіреді - Қателіктер тіркеуі сақталды Аялық белсенділік Батареяның оңтайландыруын өшіру Құрылғы баптаулары ашылмады @@ -489,7 +486,6 @@ Сіз шығып кеттіңіз Санатты жаңарту Локал - Белгілер Нәтиже жоқ Локал дереккөз Ақырғы қолданылған @@ -596,7 +592,6 @@ Жүктеп алу Ортақ Прогресс - Қателіктер тіркеуі Тарау баптаулары Қателік Тоқтатылды @@ -663,7 +658,6 @@ Жүктеу қателігі Орнату үшін басыңыз Қателіктер - Жүктелген бет бөлінбеді Аяқталмағандар Тізімі Бағалау Атауы diff --git a/i18n/src/main/res/values-km/strings.xml b/i18n/src/main/res/values-km/strings.xml index 816e696adb..5ecc799317 100644 --- a/i18n/src/main/res/values-km/strings.xml +++ b/i18n/src/main/res/values-km/strings.xml @@ -37,7 +37,6 @@ ទទួលយកថ្នាក់ ផ្លាស់ប្ដូរក្របម៊េងហ្គា មើលភាគ - ឈប់ ឈប់បណ្ណោះអាសន្ន ម្ដងទៀត លុប diff --git a/i18n/src/main/res/values-kn/strings.xml b/i18n/src/main/res/values-kn/strings.xml index c1b55dfadd..8878f5d749 100644 --- a/i18n/src/main/res/values-kn/strings.xml +++ b/i18n/src/main/res/values-kn/strings.xml @@ -65,7 +65,6 @@ ಮುಂದಿನ ಅಧ್ಯಾಯ ಹಿಂದಿನ ಅಧ್ಯಾಯ ವಿರಾಮ - ನಿಲ್ಲಿಸಿ ಅಧ್ಯಾಯಗಳನ್ನು ವೀಕ್ಷಿಸಿ ಕವರ್ ತಿದ್ದಿ ವರ್ಗಗಳನ್ನು ಹೊಂದಿಸಿ @@ -412,7 +411,6 @@ ಮೂಲ ಸ್ಥಳಾಂತರ ಯಾವುದೇ ಪುಟಗಳು ಸಿಕ್ಕಿಲ್ಲ ಟ್ಯಾಬ್‌ಗಳು - ಬ್ಯಾಡ್ಜ್‌ಗಳು ರೀಡರ್ ತೆರೆದಾಗ ಪ್ರಸ್ತುತ ಮೋಡ್ ಅನ್ನು ಸಂಕ್ಷಿಪ್ತವಾಗಿ ತೋರಿಸಿ ಓದುವ ರೀತಿ ತೋರಿಸಿ ವರ್ಗ ಟ್ಯಾಬ್‌ಗಳನ್ನು ತೋರಿಸಿ @@ -483,10 +481,8 @@ ಅಪ್ಲೋಡ್ ದಿನಾಂಕ ದಂತೆ ವಸ್ತುಗಳ ಸಂಖ್ಯೆಯನ್ನು ತೋರಿಸಿ ಸಿಕ್ಕ ಮಾಹಿತಿ - ಕ್ರ್ಯಾಶ್ ಲಾಗ್ ಗಳು ಮುಕ್ತಾಯ ದಿನಾಂಕ ಆರಂಭದ ದಿನ - ಕ್ರ್ಯಾಶ್ ಲಾಗ್‌ಗಳನ್ನು ಉಳಿಸಲಾಗಿದೆ ಡೆವಲಪರ್‌ಗಳೊಂದಿಗೆ ಹಂಚಿಕೊಳ್ಳಲು ದೋಷದೆ ಲಾಗ್‌ಗಳನ್ನು ಫೈಲ್‌ಗೆ ಸೇರಿಸಿ ಕ್ರ್ಯಾಶ್ ಲಾಗ್‌ಗಳನ್ನು ಡಂಪ್ ಮಾಡಿ HTTPS ಮೇಲೆ DNS ಬಳಸಿ diff --git a/i18n/src/main/res/values-ko/strings.xml b/i18n/src/main/res/values-ko/strings.xml index 154ae6eff2..48702b1336 100644 --- a/i18n/src/main/res/values-ko/strings.xml +++ b/i18n/src/main/res/values-ko/strings.xml @@ -30,7 +30,6 @@ 카테고리 이름 바꾸기 카테고리 지정 표지 편집 - 정지 제거 계속 브라우저에서 열기 @@ -398,7 +397,7 @@ 데이터 이전 %1$s화 - %2$s 화면 보안 - 잠금 해제 요청 + 잠금 해제 필요 단어 사용 모양 오늘 @@ -454,7 +453,6 @@ 전부 업데이트 백업에 항목이 포함되어 있지 않습니다. 개발자와 공유할 수 있는 오류 로그 파일을 생성합니다 - 오류 로그가 저장되었습니다 개인정보 보호 정책 %1$s: %2$s, %3$d페이지 계속하시겠습니까\? 모든 기록이 삭제됩니다. @@ -511,10 +509,9 @@ 10% 트래킹 서재 표지 새로고침 - 오류 로그 덤프 + 오류 로그 공유 회차를 찾을 수 없습니다 결과가 없습니다 - 오류 로그 이 확장기능의 소스는 19금 콘텐츠가 포함될 수 있습니다 트래커 사용 시작 @@ -576,7 +573,6 @@ 백그라운드 서재 업데이트와 라이브러리 업데이트를 도울 수 있습니다 자세한 로그 자세한 로그를 시스템 로그에 기록 (성능이 하락할 수 있습니다) - 배지 날짜 정렬 읽기 시작한 날짜 @@ -635,7 +631,6 @@ 배터리가 부족하지 않을 때만 WebView 데이터 지우기 WebView 데이터 삭제됨 - 긴 이미지 분할 리더 성능 향상 버전 언어 @@ -665,7 +660,6 @@ 마지막 회차를 열 수 없습니다 최근에 업데이트된 항목 보기 보류 목록 - 다운로드한 사진을 나눌 수 없습니다 나누어 짐 중에 %d페이지 안 찾습니다 RARv5 포맷은 지원되지 않습니다 앱 잠금 사용 중에 위젯 이용할 없습니다 @@ -699,7 +693,7 @@ 자동 다운로드, 미리 다운로드 예기치 않은 오류가 발생했습니다 애플리케이션 재시작 - %s에 예기치 않은 오류가 발생했습니다. 이 메시지를 스크린샷으로 찍고, 충돌 로그를 덤프 한 다음 공식 디스코드 채널에 공유하는 것을 추천합니다. + %s에 예기치 않은 오류가 발생했습니다. 충돌 로그를 공식 디스코드 채널에 공유하는 것을 추천합니다. 알 수 없는 제목 잘못된 위치: %s 잘못된 사용자 에이전트 문자열 @@ -723,4 +717,12 @@ 다음 %d화 카테고리 업데이트 + 긴 이미지 분할 + 오버레이 + 화면에 맞게 넓은 페이지 회전 + 회전된 넓은 페이지의 회전 방향 + 챕터가 누락되었을 수 있습니다 + + 누락된 회차 %1$s개 + \ No newline at end of file diff --git a/i18n/src/main/res/values-lt/strings.xml b/i18n/src/main/res/values-lt/strings.xml index a7eb047ef0..37c2bc7f65 100644 --- a/i18n/src/main/res/values-lt/strings.xml +++ b/i18n/src/main/res/values-lt/strings.xml @@ -148,7 +148,6 @@ Sekantis skyrius Ankstesnis skyrius Pristabdyti - Stop Rodyti skyrius Keisti viršelį Perkelti kategorijas @@ -466,7 +465,6 @@ Bakstelėdami slinkti plačius vaizdus Priartinti horizontalų vaizdą Versija - Automatiškai padalinti aukšti vaizdai Vienpusė sinchronizacija, skirta skaitymo progresui stebėjimo paslaugose atnaujinti. Nustatykite atskirą įrašų sekimą iš jų sekimo mygtuko. Galima naudoti dabartinei bibliotekai atkurti Šoninis spaudinėjimas @@ -488,7 +486,6 @@ Atidaryti „GitHub“ svetainėje Programėlių atnaujinimai Plėtinių atnaujinimai - Strigčių žurnalai Ankstesnis puslapis Atsarginės kopijos kūrimas / atkūrimas gali veikti netinkamai, jei \"MIUI Optimization\" yra išjungtas. Atšauktas atkūrimas @@ -526,7 +523,6 @@ %d plėtinių atnaujinimai pasiekiami %d plėtinių atnaujinimai pasiekiami - Nepavyko padalyti atsisiųsto vaizdo Atnaujinti numatytus skyrių nustatymus Parsisiuntėjas Klaida @@ -559,7 +555,6 @@ Iš naujo nustati visų serijų skaitymo režimą ir orientaciją Visų skaitytuvo nustatymų nustatymas iš naujo Nepavyko atstatyti skaitytuvo nustatymų - Išsaugoti strigčių žurnalai Fono veikla Padeda su fono bibliotekos naujinimais ir atsarginėmis kopijomis Akumuliatoriaus optimizavimas jau išjungtas @@ -601,7 +596,6 @@ Viršelis Prisegtas El. pašto adresas - Žymės Patikrinkite svetainę \"WebView\" aplinkoje Pasaulinė paieška… Naujausia diff --git a/i18n/src/main/res/values-lv/strings-aniyomi.xml b/i18n/src/main/res/values-lv/strings-aniyomi.xml index ac6802f40a..835754739c 100644 --- a/i18n/src/main/res/values-lv/strings-aniyomi.xml +++ b/i18n/src/main/res/values-lv/strings-aniyomi.xml @@ -2,11 +2,10 @@ Kategorijas Atslēgt Anijomi - Pēdējais manga atjauninājums - Rādīt manga - Lokālā manga + Pēdējā atjauninājuma pārbaude + Rādīt ierakstu + Lokālais avots Ar nelasītu(ām) nodaļu(ām) - Rādīt nelasīto skaitu uz Atjauninājumi ikonas Noklusētā kategorija Izslēgto kategoriju manga netiks atjaunināta, pat ja tās ir arī iekļautajās kategorijās. Šis paplašinājums tika parakstīts ar neuzticamu sertifikātu, un tas netika aktivizēts. @@ -24,13 +23,13 @@ Notīriet nodaļu kešatmiņu, aizverot lietotni Notīrīt datu bāzi Dzēst vēsturi preikš manga, kas nav saglabāta bibliotēkā - Vai esi pārliecināts\? Lasītās nodaļas un progress priekš manga, kuras nav bibliotēkā, būs zudis + Vai esi pārliecināts\? Lasītās nodaļas un progress priekš ieraksta, kuras nav bibliotēkā, būs zudis Atjaunina statusu, vērtējumu un pēdējo izlasīto nodaļu no izsekošanas servisa Pauzē lasīšanas vēsturi - Manga no bibliotēkas + No bibliotēkas Pievienot manga bibliotēkai\? - Attiecas arī uz visām manga manā bibliotēkā - Atiestatīt visas šīs mangas nodaļas + Attiecas arī uz visiem ierakstiem manā bibliotēkā + Atiestatīt visas šī ieraksta nodaļas Nevarēja lejupielādēt nodaļas, jo trūkst krātuves vietas Brīdinājums: liela apjoma lejupielāde var izraisīt to, ka avoti kļūst lēnāki un/vai bloķē Tachiyomi. Pieskarieties, lai uzzinātu vairāk. Tachiyomi ir nepieciešams WebView diff --git a/i18n/src/main/res/values-lv/strings.xml b/i18n/src/main/res/values-lv/strings.xml index 7c8bffad37..8056b9dabe 100644 --- a/i18n/src/main/res/values-lv/strings.xml +++ b/i18n/src/main/res/values-lv/strings.xml @@ -34,7 +34,6 @@ Pārdēvēt kategoriju Noteikt kategorijas Rediģēt vāku - Beigt Pārtraukt Iepriekšēja nodaļa Nākama nodaļa @@ -63,7 +62,7 @@ Izsekošana Papildu iestatījumi Par programmu - Common + Biežs Tīkla savienojums nav pieejams Nav pieejams Wi-Fi savienojums Nevarēja lejupielādēt nodaļu neparedzētas kļūdas dēļ @@ -85,7 +84,7 @@ %02d min, %02d sec Atjaunošana pabeigta Trūkstošie avoti: - Dublējumā nav nevienas manga. + Dublējumā nav neviena bibliotēkas ieraksta. Paplašinājumu informācija Paplašinājumi Abi @@ -142,7 +141,7 @@ Vienmēr Bloķēt, ja dīkstāvē Vajadzīgs atbloķēt - Drošība + Drošība un privātums Pārvaldiet paziņojumus Apstipriniet izeju Datuma formāts @@ -206,7 +205,7 @@ Instalētājs Shizuku nedarbojas Dinamisks - Kopējā manga + Visi ieraksti Atcelt visu šai sērijai Pēc augšupielādes datuma NSFW (18+) avoti @@ -224,7 +223,7 @@ Vakar Pirms %1$d dienām - Izlaist sēriju atjaunināšanu + Izlaist ierakstu atjaunināšanu Biežāk uzdotie jautājumi un rokasgrāmatas Aizvērt Laikspiedols @@ -248,7 +247,7 @@ Instalējiet un startējiet Shizuku, lai izmantotu Shizuku kā paplašinājuma instalētāju. Pilnekrāns Rādīt skāriena zonu pārklājumu - Ja dubultlappušu sadalījuma izvietojums neatbilst lasīšanas virzienam + Ja sadalīto plato lapu izvietojums neatbilst lasīšanas virzienam Animēt lappušu pārejas Rādīt lappušu numuru Rādīt lasīšanas režīmu @@ -286,10 +285,10 @@ Uzticams Neoficiāls Neuzticams - Šis paplašinājums vairs nav pieejams. + Šis paplašinājums vairs nav pieejams. Tas var nedarboties pareizi un var radīt problēmas ar lietotni. Ieteicams to atinstalēt. Šis paplašinājums nav no oficiālo Tachiyomi paplašinājumu saraksta. - Divu lappušu sadalīšana - Apvērst divu lappušu sadalījuma izvietojumu + Sadalīt platas lapas + Apvērst dalītās lapas izvietojumu Rādīt saturu izgriezuma apgabalā Dubultpieskāriena animācijas ātrums Apgriezt apmales @@ -360,7 +359,7 @@ Lasīšanas režīms No labās puses uz kreiso Vertikāls - Manga + Bibliotēkas ieraksti Ātrs Noklusējuma rotācijas tips Augsts @@ -375,7 +374,7 @@ Navigācija Invertēt skaļuma regulēšanas taustiņus Rādīt ar ilgu pieskārienu - Izveido mapes atbilstoši manga nosaukumam + Izveido mapes atbilstoši ieraksta nosaukumam Fona krāsa Mēroga tips Ietilpt ekrānā @@ -383,38 +382,37 @@ Tālummaiņas sākuma pozīcija Zems Atspējots - Izslēgto kategoriju manga netiks lejupielādēta pat tad, ja tās ir arī iekļautajās kategorijās. - Automātiski sadalīt garus attēlus + Izslēgto kategorijas ieraksti netiks lejupielādēti pat tad, ja tie ir iekļautajās kategorijās. Skaļuma regulēšanas taustiņi Konfidencialitātes politika Var izmantot, lai atjaunotu pašreizējo bibliotēku - Uzlabo lasītāja veiktspēju, sadalot garus lejupielādētus attēlus. - Pakalpojumi, kas nodrošina uzlabotus līdzekļus konkrētiem avotiem. Pievienojot bibliotēkai, manga tiks automātiski izsekota. + Uzlabo lasītāja veiktspēju, sadalot garus lejupielādētus attēlus + Pakalpojumi, kas nodrošina uzlabotus līdzekļus konkrētiem avotiem. Pievienojot bibliotēkai, ieraksts tiks automātiski izsekots. Izveidot dublējumu Dublējuma atrašanās vieta Nederīgs dublējuma fails Automātiskā dublēšana Dublējumu biežums - Automātiskā dublēšana ir ļoti ieteicama. Kopijas vajadzētu glabāt arī citās vietās. + Kopijas vajadzētu glabāt arī citās vietās. Dažiem ražotājiem ir papildu lietojumprogrammu ierobežojumi, kas iznīcina fona pakalpojumus. Šajā vietnē ir vairāk informācijas par to, kā to izlabot. - Vienvirziena sinhronizācija, lai atjauninātu izsekošanas pakalpojumu nodaļas progresu. Iestatiet izsekošanu atsevišķiem manga ierakstiem, izmantojot izsekošanas pogu. + Vienvirziena sinhronizācija, lai atjauninātu izsekošanas pakalpojumu nodaļas progresu. Iestatiet izsekošanu atsevišķiem ierakstiem, izmantojot izsekošanas pogu. Palīdz ar fona bibliotēku atjauninājumiem un dublējumiem Akumulatora optimizācija jau ir atspējota Drukāt verbose žurnālus sistēmas žurnālā (samazina programmas veiktspēju) Izsekošanas rokasgrāmata Saglabā avārijas žurnālu Saglabā kļūdu žurnālus failā priekš koplietošanas ar izstrādātājiem - Iekļaut tikai piespraustos avotus + Globālajā meklēšanā izmantot tikai piespraustos avotus Atsvaidzināt bibliotēkas vākus Ieraksti izdzēsti Nav bibliotēkas ierakstu, ko dublēt Atjaunošana atcelta Dublēšana/atjaunošana var nedarboties pareizi, ja ir atspējota MIUI Optimization. - %1$d grāmatas, kas nav bibliotēkas, ir datu bāzē + %1$d ierakstu, kas nav bibliotēkas, ir datu bāzē Tīkls Dati Izmantots: %1$s - Datu bāze tīra + Nav ko tīrīt Atspējot akumulatora optimizāciju Atjaunot bibliotēku no dublējuma faila Maksimālais dublējumu skaits @@ -434,7 +432,6 @@ Pārbaudīt, vai nav atjauninājumu Notīrīt WebView datus WebView dati notīrīti - Avārijas žurnāli saglabāti Fona darbība Nevarēja atvērt ierīces iestatījumus Serviss @@ -456,8 +453,7 @@ Pieteikumvārds Inkognito režīms Rezultāti nav atrasti - Nozīmītes - Filtrē visu manga bibliotēkā + Filtrēt visus ierakstus bibliotēkā Vairāk rezultātu nav Pieteicies Izrakstīties no %1$s\? @@ -542,7 +538,6 @@ Atvērt vietnē GitHub Šī Android versija vairs netiek atbalstīta Lejupielādē… - Nevarēja sadalīt lejupielādēto attēlu Progress Izlaists Attēlu nevarēja ielādēt @@ -550,14 +545,13 @@ Priekš šīs sērijas Pamests - Priekš %d - Priekš %d - Priekš %d + Priekš %d ierakstiem + Priekš %d ieraksta + Priekš %d ierakstiem Izlaists, jo neviena nodaļa nav izlasīta Atlasiet vāka attēlu Atlasiet dublējuma failu - Avāriju žurnāli Iepriekšējā lapa Kopēt Jaunā versija ir pieejama no oficiālajām versijām. Pieskarieties, lai uzzinātu, kā migrēt no neoficiālajām F-Droid versijām. @@ -604,7 +598,7 @@ Atrastas jaunas nodaļas Nodaļa %1$s un vēl %2$d Nav atrasta failu atlases programma - Lūdzu, pievienojiet manga savai bibliotēkai, pirms to darāt + Lūdzu, pievienojiet ierakstu savai bibliotēkai, pirms to darāt %1$d jaunu nodaļu %1$d jauna nodaļa @@ -620,7 +614,7 @@ Atjaunināti noklusējuma nodaļu iestatījumi Palielināt ainavas attēlu Nākošā lapa - Pārslēgt lapu, kamēr pietuvināts + Pieskaroties izplest platus attēlus Nevar atvērt pēdējo lasīto nodaļu Pārlasīšana Vai dzēst lejupielādētās nodaļas\? @@ -646,7 +640,7 @@ Nodaļa %1$s un vēl 1 Nodaļa %1$s un vēl %2$d - Pieskarieties, lai instalētu + Pieskarieties, lai instalētu atjauninājumu Pielāgots vāks Nepabeigto saraksts Sākuma datums @@ -678,4 +672,111 @@ Darba sākšanas rokasgrāmata Jums vēl nav nevienas kategorijas. Lejupieladēšanas rinda + Nederīga lietotāja agent virkne + Izlaist vienādas nodaļas + Tādējādi no %s tiks noņemts iepriekš atlasītais beigu datums + Dzēst kategoriju + Lasīšanas režīmi, displejs, navigācija + Avārijas žurnāli, akumulatora optimizācija + Manuālā un automātiskā dublēšana + Sadalīt augstus attēlus (BETA) + Paisuma vilnis + Vairāku + Lejupielādēt uz priekšu + Darbojas tikai ar ierakstiem bibliotēkā un tad, ja pašreizējā un nākamā nodaļa jau ir lejupielādēta + Jūsu bibliotēkā ir ieraksts ar tādu pašu nosaukumu. +\n +\nVai joprojām vēlaties turpināt\? + Nederīgs lejupielāžu indekss + Pārskats + Statistika + Sākts + Lejupielādēts + Lokālais + Nederīga atrašanās vieta: %s + Nezināms nosaukums + Izsekotāji + Lasīts + Izsekotie ieraksti + Vidējais vērtējums + Izmantots + Nav piemērojams + %dd + %dm + %ds + Populārs + Vai tu esi pārliecināts\? + Tikko + Atjaunināt kategoriju + Atvērt nejaušu ierakstu + Noņemt visu + Turpināt lasīt poga + Automātiska lejupielāde lasīšanas laikā + Pieejams, bet avots nav instalēts: %s + Krātuves atļaujas nav piešķirtas + Kopēts starpliktuvē + Bibliotēka pēdējo reizi atjaunināta: %s + Izlaists, jo sērijai nav nepieciešami atjauninājumi + Tādējādi no %s tiks noņemts iepriekš atlasītais sākuma datums + Pārklājums + Aplikācijas slēdzis, ekrāna aizsargāšana + Aplikācijas valoda, paziņojumi + Kopēt starpliktuvē + Automātiskā lejupielāde, lejupielādes rinda + Vienvirziena progresa sinhronizācija, uzlabota sinhronizācija + Avoti, paplašinājumi, globālā meklēšana + Tēma, datuma un laika formāti + Kategorijas, globāli atjauninājumi + Rādīt nelasīto skaitu uz atjauninājumu ikonas + Logrīks nav pieejams, ja ir iespējota lietotņu bloķēšana + RARv5 formāts netiek atbalstīts + %dh + InternalError: Par plašāku informāciju skatiet avārijas žurnālu + Vai vēlaties dzēst kategoriju \"%s\"\? + + Trūkst %1$s nodaļu + Trūkst %1$s nodaļa + Trūkst %1$s nodaļas + + Iespējams, trūkst nodaļu + + Nākošā %d nodaļa + Nākošā %d nodaļa + Nākošās %d nodaļās + + %1$s kļūda: %2$s + Atjauninājums jau darbojas + %s radās neparedzēta kļūda. Mēs iesakām izveidot šo ziņojumu ekrānuzņēmumu, atrast avārijas žurnālu un pēc tam kopīgot to mūsu atbalsta kanālā Discord lietotnē. + Pagrieziet platas lapas, lai tās ietilptu + Apvērst orientācija pagrieztām platām lapām + Sadalīt augstus attēlus + Slēpt ierakstus, kas jau ir bibliotēkā + Vai noņemt datumu\? + Kategorija ir tukša + Skatiet savus nesen atjauninātos bibliotēkas ierakstus + Jūs gatavojaties noņemt \"%s\" no savas bibliotēkas + + Nākamā nelasītā nodaļa + Nākamā nelasītā nodaļa + Nākamās %d nelasītas nodaļas + + Worker info + Ne tagad + Meklē… + F-Droid versijas netiek oficiāli atbalstītas. +\nPieskarieties, lai uzzinātu vairāk. + Pārbauda lejupielādes + Lietotāja agent virknes lauks nedrīkst būt tukšs + Šajā kategorijā nav atrasts neviens ieraksts + Noklusējuma lietotāja agent virkne + Atiestatīt noklusējuma lietotāja agent virkni + Piespiediet lietotni atkārtoti pārbaudīt lejupielādētās nodaļas + Ak nē! + Restartējiet lietotni + Pabeigtie ieraksti + Lasīšanas ilgums + Ieraksti + Globālajā atjauninājumā + Kopā + *obligāti \ No newline at end of file diff --git a/i18n/src/main/res/values-mr/strings.xml b/i18n/src/main/res/values-mr/strings.xml index 191322ba8f..823e2ef3c1 100644 --- a/i18n/src/main/res/values-mr/strings.xml +++ b/i18n/src/main/res/values-mr/strings.xml @@ -71,7 +71,6 @@ पुढील अध्याय मागील अध्याय विराम द्या - थांबवा अध्याय बघा कव्हर बदलवा श्रेणी सेट करा diff --git a/i18n/src/main/res/values-ms/strings.xml b/i18n/src/main/res/values-ms/strings.xml index ddffa767b6..e62a28c716 100644 --- a/i18n/src/main/res/values-ms/strings.xml +++ b/i18n/src/main/res/values-ms/strings.xml @@ -37,7 +37,6 @@ Ubah nama kategori Tetapkan kategori Ubah muka hadapan - Henti Henti sebentar Bab sebelumnya Bab seterusnya @@ -406,7 +405,6 @@ Segar semula metadata secara automatik Pindah sumber Grid selesa - Penanda Tab Tunjuk tab kategori Tiada muka surat ditemui @@ -477,10 +475,8 @@ Menaik Mengikut nombor bab Mengikut tarikh muat naik - Log kerosakan - Log kerosakan disimpan Simpan log ralat untuk dikongsi dengan pembangunan aplikasi - Kumpulan log kerosakan + Kongsi log kerosakan Tarikh mula Tarikh selesai Dijejaki @@ -634,14 +630,12 @@ Tiada sumber dipasang ditemui Tiada sumber ditemui Kiraan belum dibaca - Memisah imej yang tinggi Meningkatkan prestasi pembaca Muka surat %d tidak dijumpai ketika memisah Lokasi fail muka surat %d tidak ditemui Menetapkan semula mod bacaan dan orientasi semua siri Tetapan pembaca diset semula Set semula tetapan pembaca setiap siri - Tidak dapat memisahkan imej yang dimuat turun Hmm, ini agak menjanggalkan Tidak dapat set semula tetapan pembaca Versi @@ -698,7 +692,7 @@ Ralat Tidak Dijangka Berlaku Mulakan semula aplikasi Sumber, sambungan, carian keseluruhan - %s mengalami ralat tidak dijangka. Kami mencadangkan anda untuk tangkapan skrin mesej ini, mengumpulkan log kerosakan, dan kemudian mengongsi ia pada Discord kami di saluran bantuan. + %s mengalami ralat tidak dijangka. Kami mencadangkan anda untuk kongsi log kerosakan pada Discord kami di saluran bantuan. Tajuk tidak diketahui Lokasi tidak sah: %s Rentetan ejen pengguna tidak sah @@ -724,4 +718,12 @@ Sembunyikan entri yang sudah ada di dalam pustaka Salin ke papan keratan Kemas kini kategori + Memisah imej yang tinggi + Tindanan + + Hilang %1$s bab + + Berkemungkinan ada bab yang hilang + Terbalikkan orientasi muka surat lebar yang diputarkan + Putar muka surat lebar agar muat \ No newline at end of file diff --git a/i18n/src/main/res/values-nb-rNO/strings.xml b/i18n/src/main/res/values-nb-rNO/strings.xml index eba3cfdb89..011976d8cb 100644 --- a/i18n/src/main/res/values-nb-rNO/strings.xml +++ b/i18n/src/main/res/values-nb-rNO/strings.xml @@ -38,7 +38,6 @@ Gi kategori nytt navn Sett kategorier Rediger omslag - Stopp Pause Forrige kapittel Neste kapittel @@ -114,7 +113,7 @@ Venstre til høyre Høyre til venstre Loddrett - Webtoon + Nett-tegneserie Skalatype Tilpass skjerm Strekk @@ -383,7 +382,6 @@ Ukjent forfatter Lokal kildeguide Sjekk nettsted i WebView - Merker %1$s gjenstående %1$s gjenstående @@ -470,7 +468,6 @@ Sidepolstring Neste side Forrige side - Feillogg Ingen app for filvelging funnet Veiledning for kilde-flytting Er du sikker\? All historikk vil gå tapt. @@ -481,8 +478,7 @@ Startdato Nedlastede kapitler Inkognitomodus - Feillogger lagret - Dump feillogg + Del krasjlogger Lagrer feillogger i en fil som kan sendes til utviklerne Navigasjonsoppsett Kant @@ -691,10 +687,8 @@ Slett kategori InternalError: Sjekk krasjlogger for mer informasjon En uventet feil oppstod - %s fikk en uventet feil. Vi foreslår at du tar et skjermbilde av denne meldingen, dumper krasjloggene og derette deler dette i vår støttekanal på Discord. - Kunne ikke dele det nedlastede bildet + %s fikk en uventet feil. Vi foreslår at du deler krasjloggene i vår støttekanal på Discord. Appspråk - Del høye bilder Ingen beskrivelse Søk… Fjern alt @@ -736,4 +730,13 @@ Skjul oppføringer som allerede er i biblioteket Kopier til utklippstavle Oppdater kategori + Del opp høye bilder + Elementer + + Mangler %1$s kapittel + Mangler %1$s kapitler + + Kan mangle kapitler + Vend sideretning for roterte brede sider + Roter brede sider slik at de passer \ No newline at end of file diff --git a/i18n/src/main/res/values-ne/strings-aniyomi.xml b/i18n/src/main/res/values-ne/strings-aniyomi.xml index 240b4adf5e..98ffae7afa 100644 --- a/i18n/src/main/res/values-ne/strings-aniyomi.xml +++ b/i18n/src/main/res/values-ne/strings-aniyomi.xml @@ -1,7 +1,7 @@ वर्गहरू - अनलक ताचियोमी + ताचियोमी अनलक गर्नुहोस् पछिल्लो अपडेट चेक इन्ट्री देखाउनुहोस् लोकल माङ्गा diff --git a/i18n/src/main/res/values-ne/strings.xml b/i18n/src/main/res/values-ne/strings.xml index 28ab47bb45..b420036240 100644 --- a/i18n/src/main/res/values-ne/strings.xml +++ b/i18n/src/main/res/values-ne/strings.xml @@ -20,7 +20,7 @@ लग खोल्नुहोस् पूर्ववत गर्नुहोस् रिसेट गर्नुहोस् - सेभ गर्नुहोस् + बचत गर्नुहोस् साझा गर्नुहोस् स्थापना गर्नुहोस् तल सार्नुहोस् @@ -53,7 +53,6 @@ अर्को अध्याय अघिल्लो अध्याय रोक्नुहोस् - रोक्नुहोस् अध्यायहरू हेर्नुहोस् आवरण सम्पादन गर्नुहोस् वर्गहरू सेट गर्नुहोस् @@ -99,7 +98,7 @@ कुनै डाउनलोडहरू छैन मद्दत एक्सटेनशनको जानकारी - एक्सटेनशनहरु + एक्सटेनशन स्थानान्तरण ब्याकअप र पुनर्स्थापना स्रोतहरू @@ -322,15 +321,14 @@ पिन गरिएका स्रोतहरू मात्र समावेश गर्नुहोस् पुनर्स्थापना सम्पन्न भयो - %2$s एररको साथ %1$s मा सम्पन्न भयो - %2$s एररहरूसँग %1$s मा सम्पन्न भयो + %2$s त्रुटिको साथ %1$s मा सम्पन्न भयो + %2$s त्रुटिहरूसँग %1$s मा सम्पन्न भयो कुकीहरू खाली गर्नुहोस् तपाई के ब्याकअप गर्न चाहनुहुन्छ\? ब्याकअप असफल भयो HTTPS (DoH) माथि DNS प्रभाव पार्न एप पुन: सुरु गर्न आवश्यक छ - ब्याजहरू आवरण अपडेट गर्न असफल भयो यो गर्नु अघि कृपया आफ्नो पुस्तकालयमा यस इन्ट्रीलाई राख्नुहोस् डाउनलोडर @@ -352,7 +350,7 @@ नेटवर्क ब्याकअप पुनर्स्थापना असफल भयो तपाईंले ब्याकअपको प्रतिलिपिहरू अन्य ठाउँहरूमा पनि राख्नु पर्छ। - नयाँ अध्यायहरु पायो + नयाँ अध्यायहरु फेला पर्यो %1$d नयाँ अध्याय %1$d नयाँ अध्यायहरू @@ -361,7 +359,7 @@ असक्षम परिष्कृत सेवाहरू पुनर्स्थापना रद्द गरियो - एरर + त्रुटि तन्काउनुहोस् स्मार्ट फिट स्वचालित @@ -387,7 +385,7 @@ कुनै नेटवर्क जडान उपलब्ध छैन ट्र्याकिङ सेवाहरूमा अध्याय प्रगति अपडेट गर्न एकतर्फी सिङ्क। तिनीहरूको ट्र्याकिङ बटनबाट व्यक्तिगत इन्ट्रीहरूको लागि ट्र्याकिङ सेट अप गर्नुहोस्। ट्र्याकरहरूमा लगइन छैनन्: - हराएको स्रोतहरु: + छुटेको स्रोतहरु: पाँचौं अन्तिम अध्याय \"बहिष्कृत\" वर्गहरूमा इन्ट्री \"समावेश गरिएका\" वर्गहरूमा भए पनि डाउनलोड गरिने छैन। @@ -398,12 +396,12 @@ हालको पुस्तकालय पुनर्स्थापना गर्न प्रयोग गर्न सकिन्छ कुकीहरू खाली गरियो डाटा - क्यास खाली गर्दा एरर भयो + खाली गर्दा त्रुटि भयो स्थिति नयाँ अध्यायहरूको लागि जाँच गर्दै डाउनलोड गर्दै… अपडेट स्थापना गर्न ट्याप गर्नुहोस् - अप्रत्याशित एररका कारण अध्याय डाउनलोड गर्न सकिएन + अप्रत्याशित त्रुटिका कारण अध्याय डाउनलोड गर्न सकिएन अध्याय %1$s मूल आकार जुम सुरु स्थिति @@ -421,7 +419,7 @@ ब्याकअप रिस्टोर गर्नुहोस् ब्याकअप फाइलबाट पुस्तकालय पुनर्स्थापना गर्नुहोस् स्वचालित ब्याकअपहरू - ब्याकअपले कुनै पनि पुस्तकालयका इन्ट्री समावेश गर्दैन। + ब्याकअपमा कुनै पनि पुस्तकालयका इन्ट्री समावेश छैन। ब्याकअप स्थान ब्याकअप फ्रिक्वेन्सी अधिकतम ब्याकअपहरु @@ -432,9 +430,9 @@ क्यास खाली गरियो। %1$d फाइलहरू हटाएका छन् अध्याय %1$s र %2$d अरु - डाउनलोड एरर + डाउनलोड त्रुटि एक्सटेनशन अपडेटहरु - विकासकर्ताहरूसँग साझेदारी गर्नको लागि फाइलमा एरर लगहरू बचत गर्दछ + विकासकर्ताहरूसँग साझेदारी गर्नको लागि फाइलमा त्रुटि लगहरू बचत गर्दछ ब्याकग्राउण्ड गतिविधि नयाँ के छ डेटाबेसमा %1$d गैर-पुस्तकालय इन्ट्रीहरु @@ -455,14 +453,13 @@ %1$s मा लग इन गर्नुहोस् पुस्तकालयका आवरणहरु ताजा गर्नुहोस् गुप्त मोड असक्षम गर्नुहोस् - अज्ञात एरर + अज्ञात त्रुटि डाउनलोड गरिएका अध्यायहरू अध्याय फेला परेन तपाईंसँग पिन गरिएको स्रोतहरू छैनन् अवैध अध्याय ढाँचा तपाईंको पुस्तकालयमा सबै इन्ट्री फिल्टर गर्नेछ - क्र्यास लगहरू डम्प गर्नुहोस् - क्र्यास लगहरू सुरक्षित गरियो + क्र्यास लगहरू साझा गर्नुहोस् ब्याट्री अप्टिमाइजेसन असक्षम पार्नुहोस् अज्ञात कुनै परिणाम फेला परेन @@ -500,7 +497,7 @@ प्रकाशन सकियो रद्द गरेको अन्तरालमा छ - पुस्तकालयमा राख्नुहोस + पुस्तकालयमा राख्नुहोस् पुस्तकालयमा राखियो पुस्तकालयमा छ समाप्त @@ -529,7 +526,7 @@ अपलोड मिति द्वारा %d अध्याय छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ - %d अध्यायहरु छोड्दै, या त स्रोत सँग छैन वा तिनिहरू फिल्टर गरिएका छन् + %d अध्यायहरु छोड्दै, या त स्रोत सँग छैन वा यसलाई फिल्टर गरिएको छ स्रोत स्थानान्तरण गाइड स्थानान्तरण गर्नको लागि स्रोत चयन गर्नुहोस् @@ -540,7 +537,7 @@ के तपाईँले चयन गर्नुभएको अध्यायहरू हटाउन चाहनुहुन्छ\? अध्याय सेटिङ पूर्वनिर्धारित रूपमा सेट गर्नुहोस् - यो छवि आवरण कलाको रूपमा राख्न चाहनुहुन्छ\? + यो छवि आवरण को रूपमा राख्न चाहनुहुन्छ\? नपढिएको आवरण बचत भयो यो श्रृंखलाको लागि @@ -628,7 +625,6 @@ यो एन्ड्रोइड संस्करण अब समर्थित छैन सुरु गर्ने गाइड ट्याप गर्दा चौडा छविहरू प्यान गर्नुहोस् - क्र्यास लगहरू जूम ल्यान्डस्केप छवि %1$d अपडेट(हरू) असफल भयो %1$d अपडेट(हरू) छोडियो @@ -653,9 +649,9 @@ अग्लो छविहरू विभाजित गर्नुहोस् (BETA) थुप्रै डुप्लिकेट अध्यायहरू छोड्नुहोस् - \"जारी राख्नुहोस्\" बटन देखाउनुहोस् + जारी राख्नुहोस् बटन तथ्याङ्क - सुरु गरेको + सुरु भएको लोकल डाउनलोड गरेको मापन नगरिएको नेटवर्कमा मात्र @@ -666,7 +662,6 @@ संस्करण भाषा उमेर मूल्याङ्कन - अग्लो छविहरू विभाजित गर्नुहोस् अहिले हैन अनियमित इन्ट्री खोल्नुहोस् तपाईको पुस्तकालयमा एउटै नामको इन्ट्री छ। @@ -674,7 +669,7 @@ \nके तपाईं अझै जारी राख्न चाहनुहुन्छ\? पढ्ने सूची पुस्तकालय पछिल्लो पटक अपडेट गरिएको: %s - %s एक अप्रत्याशित त्रुटिमा पर्यो। हामी तपाईंलाई यो सन्देशको स्क्रिनसट, क्र्यास लगहरू डम्प गर्ने, र त्यसपछि Discord मा रहेको हाम्रो support च्यानलमा साझेदारी गर्न सुझाव दिन्छौं। + %s एक अप्रत्याशित त्रुटिमा पर्यो। समर्थन को लागी हामी तपाईंलाई हाम्रो Discord को #support च्यानलमा क्र्यास लगहरू साझेदारी गर्न सुझाव दिन्छौं। GitHub मा खोल्नुहोस् डाउनलोड गरिएका अध्यायहरू पुन: जाँच गर्न एपलाई फोर्स गर्नुहोस् एकतर्फी प्रगति सिंक, परिष्कृत सिंक @@ -691,7 +686,6 @@ औसत स्कोर तपाईंको भर्खरै अपडेट गरिएको पुस्तकालय इन्ट्रीहरू हेर्नुहोस् उपलब्ध छ तर स्रोत स्थापना गरिएको छैन: %s - अन्य क्लिपबोर्डमा प्रतिलिपि गरियो यस वर्गमा कुनै इन्ट्रीहरु फेला परेनन् अज्ञात शीर्षक @@ -713,7 +707,6 @@ अधूरो सूची कुनै स्थापित स्रोत फेला परेन पृष्ठ %d को फाइल मार्ग फेला पार्न सकेन - डाउनलोड गरिएको छवि विभाजित गर्न सकिएन कस्टम आवरण इच्छा सूची लोकप्रिय @@ -723,7 +716,7 @@ यसले %s बाट तपाइँको पहिले चयन गरिएको सुरु मिति हटाउनेछ यसले %s बाट तपाइँको पहिले चयन गरिएको समाप्त मिति हटाउनेछ एप को भाषा, सूचनाहरू - एउटा अपडेट पहिले नै चलिरहेको छ + एउटा अपडेट पहिले नै जारी छ ल, यो के भयो स्थापना गरिएको छैन पढ्ने मोड र सबै शृङ्खलाहरूको अभिमुखीकरण रिसेट गर्दछ @@ -765,4 +758,13 @@ %dदि %dघ वर्ग अपडेट गर्नुहोस् + अग्लो छविहरू विभाजित गर्नुहोस् + आवरण ओभरले + फिट गर्न चौडा पृष्ठहरू घुमाउनुहोस् + घुमाइएका चौडा पृष्ठहरूको अभिमुखीकरण फ्लिप गर्नुहोस् + अध्यायहरू छुटेको हुन सक्छ + + %1$s अध्याय छुटेको छ + %1$s अध्यायहरू छुटेका छन् + \ No newline at end of file diff --git a/i18n/src/main/res/values-nl/strings.xml b/i18n/src/main/res/values-nl/strings.xml index f9ad1d96ad..c902bb6e1e 100644 --- a/i18n/src/main/res/values-nl/strings.xml +++ b/i18n/src/main/res/values-nl/strings.xml @@ -32,7 +32,6 @@ Categorie hernoemen Zet categorieën Omslag wijzigen - Stop Pauzeren Vorige hoofdstuk Volgende hoofdstuk @@ -410,7 +409,6 @@ Geen pagina\'s gevonden Op uploaddatum Tabbladen - Badges Data Missende bronnen: Back-up bevat geen items. @@ -484,10 +482,8 @@ Rand Kindle-achtig L-vormig - Crashlogboeken Einddatum Begindatum - Crashlogboeken opgeslagen Slaat foutmeldingslogboeken op in een bestand om te delen met de ontwikkelaars Crashlogboeken opslaan Aflopend @@ -632,14 +628,12 @@ Breng serie naar boven Geen gelezen hoofdstukken Overgeslagen want de serie is af - Split tall afbeeldingen Verbetert de prestaties van de lezer Volledige lijst Onvoltooide lijst Download vooruit WebView-gegevens gewist WebView-gegevens wissen - Kan gedownloade afbeelding niet splitsen RARv5-indeling wordt niet ondersteund Pagina %d niet gevonden tijdens het splitsen InternalError: Bekijk de crash logs voor meer informatie @@ -714,7 +708,6 @@ %1$s-fout: %2$s User agent terugzetten naar standaardwaarde Datum verwijderen\? - Anders Verlanglijst Worker-informatie Aangepaste omslag diff --git a/i18n/src/main/res/values-nn/strings.xml b/i18n/src/main/res/values-nn/strings.xml index 16af6657b1..89561f2265 100644 --- a/i18n/src/main/res/values-nn/strings.xml +++ b/i18n/src/main/res/values-nn/strings.xml @@ -37,7 +37,6 @@ Rediger kategoriar Rediger omslag Vis kapittel - Stopp Pause Neste kapittel Prøv igjen diff --git a/i18n/src/main/res/values-pl/strings.xml b/i18n/src/main/res/values-pl/strings.xml index 3a47513448..177c882c59 100644 --- a/i18n/src/main/res/values-pl/strings.xml +++ b/i18n/src/main/res/values-pl/strings.xml @@ -33,7 +33,6 @@ Zmień kategorie Edytuj okładkę Ilość rozdziałów - Zatrzymaj Wstrzymaj Poprzedni rozdział Następny rozdział @@ -432,7 +431,6 @@ Migracja Nie znaleziono żadnych stron Karty - Plakietki Pokaż karty kategorii Wyłącz wszystkie Włącz wszystkie @@ -502,10 +500,8 @@ Nie znaleziono aplikacji do przeglądania plików Wymagane ponowne zalogowanie do MAL Pokazuj na liście źródeł i rozszerzeń - Logi crashy Data zakończenia Data rozpoczęcia - Logi crasha zapisane Zapisuje logi błędów do pliku do udostępnienia programistom Zapisz logi crasha Strefy kliknięcia @@ -656,7 +652,6 @@ Zamknij Przenieś serię na górę Ilość nieprzeczytanych - Rozdzielaj wysokie obrazy Kliknij, by dowiedzieć się więcej Otwórz na GitHubie Reset wszystkich ustawień czytnika @@ -671,7 +666,6 @@ Błąd podczas zapisywania obrazka Wyczyść dane WebView Dane WebView wyczyszczone - Nie można rozdzielić pobranego obrazka Poprawia wydajność czytnika Brak opisu Lista życzeń diff --git a/i18n/src/main/res/values-pt-rBR/strings.xml b/i18n/src/main/res/values-pt-rBR/strings.xml index 97657d8745..84a8217b27 100644 --- a/i18n/src/main/res/values-pt-rBR/strings.xml +++ b/i18n/src/main/res/values-pt-rBR/strings.xml @@ -36,7 +36,6 @@ Renomear categoria Definir categorias Editar a capa - Parar Pausar Capítulo anterior Próximo capítulo @@ -422,7 +421,6 @@ Atualizar os metadados automaticamente Migrar Grade confortável - Selos Mostrar as abas de categoria Abas Nenhuma página encontrada @@ -495,12 +493,10 @@ Kindle Em forma de L Zonas de toque - Registros de travamentos Data de término Data de início - Registros de travamento salvos Salva os registros de erro em um arquivo para o compartilhamento com os desenvolvedores - Exportar os registros de travamentos + Compartilhar os registros de travamentos Decrescente Crescente Pelo número do capítulo @@ -658,7 +654,6 @@ Nenhuma fonte instalada foi encontrada Nenhuma fonte encontrada Contagem de não lidos - Dividir imagens compridas Melhora o desempenho do leitor Página %d não encontrada durante a divisão Não foi possível encontrar o caminho do arquivo da página %d @@ -666,7 +661,6 @@ Todas as configurações do leitor foram redefinidas Não foi possível redefinir as configurações do leitor Redefinir as configurações do leitor em cada série - Não foi possível dividir a imagem transferida Bem, isso é esquisito Versão Idioma @@ -724,7 +718,7 @@ Exportar registros de travamento, otimizações de bateria Ocorreu um erro inesperado Reiniciar o aplicativo - %s teve um erro inesperado. Nós sugerimos que você capture a tela com essa mensagem, exporte os registros de travamento e então compartilhá-los no nosso canal de suporte no Discord. + %s teve um erro inesperado. Nós sugerimos que você compartilhe os registros de travamento em nosso canal de suporte no Discord. Título desconhecido Local inválido: %s Valor de user agent inválido @@ -750,4 +744,14 @@ Ocultar títulos que já estão na biblioteca Copiar para a área de transferência Atualizar categoria + Dividir imagens compridas + Sobreposição + Rotacionar páginas largas para caber + Inverter a orientação das páginas largas rotacionadas + Pode ter capítulos faltando + + Faltando %1$s capítulo + Faltando %1$s capítulos + Faltando %1$s capítulos + \ No newline at end of file diff --git a/i18n/src/main/res/values-pt/strings.xml b/i18n/src/main/res/values-pt/strings.xml index dc085fa375..d03c1fac0a 100644 --- a/i18n/src/main/res/values-pt/strings.xml +++ b/i18n/src/main/res/values-pt/strings.xml @@ -27,7 +27,6 @@ Renomear categoria Definir categorias Alterar capa - Parar Pausar Capítulo anterior Capítulo seguinte @@ -444,7 +443,6 @@ Nenhuma página encontrada Por data de envio Abas - Distintivos Dados Fontes em falta: O backup não possui nenhum item da biblioteca. @@ -528,10 +526,8 @@ Por data de envio Monitorizado Borda - Registos de falhas Data de conclusão Data de início - Registos de falhas guardados Guardar registos de falhas Guarda registos de erros num ficheiro para partilhar com os desenvolvedores Mostrar número de itens @@ -684,9 +680,7 @@ Mostrar fontes fixadas duplicadas Repita as fontes fixadas nos seus respetivos grupos de idiomas Contagem de não lidos - Dividir imagens altas Melhora o desempenho do leitor - Não foi possível dividir a imagem transferida Bem, isto é estranho Não foi possível repor as definições do leitor Todas as definições do leitor reiniciadas @@ -770,7 +764,6 @@ Local inválido: %s Erro em %1$s: %2$s Itens - Outros Duração de leitura Bloqueio da app, ecrã seguro Exportar registos de travamento, otimizações de bateria @@ -800,4 +793,14 @@ Próximos %d capítulos Atualizar categoria + Dividir Imagens altas + Sobreposição + Girar páginas largas para caber + Virar a orientação de páginas largas giradas + + Falta %1$s capítulo + Faltam %1$s capítulos + Faltam %1$s capítulos + + Talvez esteja a faltar capítulos \ No newline at end of file diff --git a/i18n/src/main/res/values-ro/strings.xml b/i18n/src/main/res/values-ro/strings.xml index 6134f0575b..281f12dfee 100644 --- a/i18n/src/main/res/values-ro/strings.xml +++ b/i18n/src/main/res/values-ro/strings.xml @@ -40,7 +40,6 @@ Redenumește categoria Setați categoriile Modifică coperta - Stop Pauză Capitolul anterior Capitolul următor @@ -424,7 +423,6 @@ Neoficial Nu s-au găsit pagini Tab-uri - Insigne Afișează filele categoriei Dezactivați toate Activează toate @@ -505,10 +503,8 @@ Afișare număr de elemente Nici unul DNS prin HTTPS (DoH) - Jurnale de erori fatale Data de terminare Data începerii - Jurnalele de erori fatale salvate Aruncați jurnalele de erori fatale Salvează jurnalele de erori într-un fișier pentru partajarea cu dezvoltatorii Zone de atingere @@ -586,7 +582,6 @@ Mod întunecat negru pur Servicii care oferă caracteristici îmbunătățite pentru anumite surse. Manga sunt urmărite în mod automat atunci când sunt adăugate la biblioteca dumneavoastră. Ar trebui să păstrați copii ale backupurilor și în alte locuri. - Împărțiți imagini înalte Îmbunătățește performanța cititorului Închide Când bateria nu este scăzută @@ -610,7 +605,6 @@ Scăzut Ștergeți datele WebView Lista în așteptare - Nu s-a putut diviza imaginea descărcată Cel mai scăzut Lista neterminată Scurt (Astăzi, Ieri) diff --git a/i18n/src/main/res/values-ru/strings.xml b/i18n/src/main/res/values-ru/strings.xml index 64e3439e34..596898cfcf 100644 --- a/i18n/src/main/res/values-ru/strings.xml +++ b/i18n/src/main/res/values-ru/strings.xml @@ -41,7 +41,6 @@ Сортировка Алфавит Последнее прочитанное - Остановить Обновить библиотеку Все Чёрный @@ -431,7 +430,6 @@ Удобная сетка Перенести Вкладки - Значки Вкладки категорий Страницы не найдены Включить всё @@ -506,12 +504,10 @@ По краям Kindle-подобная L-образная - Журнал с ошибками Дата окончания Дата начала - Журнал с ошибками сохранён Сохраняет журнал с ошибками в файл для отправки разработчикам - Выгрузить журнал с ошибками + Поделиться журналом с ошибками По убыванию По возрастанию Номер главы @@ -670,7 +666,6 @@ Не найдено установленных источников Не найдено источников Оставшиеся главы - Разделять длинные изображения Страница %d не найдена, при разделении Улучшает производительность читалки Путь к файлам страницы %d не найден @@ -678,7 +673,6 @@ Сбрасывает режим чтения и ориентацию для всех серий Все настройки читалки сброшены Не удалось сбросить настройки читалки - Не удалось разделить загруженное изображение Упс, ошибочка вышла Возрастное ограничение Версия @@ -737,7 +731,7 @@ Блокировка приложения, защита экрана Выгрузка журнала с ошибками, оптимизация батареи Возникла непредвиденная ошибка - %s столкнулось с непредвиденной ошибкой. Мы рекомендуем сделать скриншот этого сообщения, выгрузить журнал с ошибками и после поделиться этим журналом/скиншотом в нашем Discord сервере в канале поддержки. + %s столкнулось с непредвиденной ошибкой. Мы рекомендуем поделиться журналом с ошибками в нашем Discord сервере в ветке support. Неизвестное название Недопустимое расположение: %s Недопустимый параметр user agent @@ -764,4 +758,15 @@ Скрывать серии, уже находящиеся в библиотеке Копировать в буфер обмена Обновить категорию + Разделять длинные изображения + Наложение + Развернуть широкие страницы под размер экрана + Могут отсутствовать главы + + Отсутствует %1$s глава + Отсутствуют %1$s главы + Отсутствуют %1$s глав + Отсутствуют %1$s глав + + Отразить ориентацию развёрнутых широких страниц \ No newline at end of file diff --git a/i18n/src/main/res/values-sa/strings.xml b/i18n/src/main/res/values-sa/strings.xml index 6c4409abc5..defaa45bbd 100644 --- a/i18n/src/main/res/values-sa/strings.xml +++ b/i18n/src/main/res/values-sa/strings.xml @@ -42,7 +42,6 @@ वर्गस्य नाम परिणमतु वर्गानि स्थापयतु आवरणं सम्पादयतु - निरामयतु विरामयतु पूर्वः अध्यायः पुनः यतताम् @@ -397,7 +396,6 @@ यद्यपि अन्तर्भूतवर्गेषु स्थिताः सन्ति तथापि वर्जितवर्गेषु स्थिताः माङ्गाः अवारोपिताः न भविष्यन्ति। अनुप्रज्ञानसेवासु अध्यायप्रगतिं नवीकर्तुं एकमार्गसमाकलनम्। तदीयात् अनुप्रज्ञानगण्डात् व्यक्तिगतेभ्यः माङ्गानिवेशेभ्यः अनुप्रज्ञानं स्थापयतु॥ दत्तांशः - पदकानि प्लवनखण्डाः अन्यम् तिथिः @@ -434,7 +432,6 @@ अग्रिमपुटः दत्तनिधिः मार्जितः पृष्ठभूमिकार्यक्रमम् - ध्वंसेतिवृत्तानि रक्षितानि विद्युत्कोषवृद्धिकरणम् अशक्तं करोतु नूतनं किम् टाबलेट् यू॰ऐ @@ -504,7 +501,6 @@ पूर्णम् अनुप्रयोगनवीकरणानि विस्तारनवीकरणानि - ध्वंसेतिवृत्तानि १ शेषम् २ शेषे diff --git a/i18n/src/main/res/values-sah/strings.xml b/i18n/src/main/res/values-sah/strings.xml index 3bcef45066..271a6cec17 100644 --- a/i18n/src/main/res/values-sah/strings.xml +++ b/i18n/src/main/res/values-sah/strings.xml @@ -136,7 +136,6 @@ Аныгыскы түһүмэх Ааспыттанны түһүмэх Тохтобул - Тохтоо Түһүмэхтэри көр Таһын уларытыы Бөлө(ххө/хтөргө) киллэр @@ -296,7 +295,6 @@ Түмүк суох Эбии түмүк суох Кыбытыктар - Бэлиэлэр Бөлөх саҥардыыта Биллибэт сыыһыы Эн таҕыстын diff --git a/i18n/src/main/res/values-sc/strings.xml b/i18n/src/main/res/values-sc/strings.xml index bd27dad53c..15017139d0 100644 --- a/i18n/src/main/res/values-sc/strings.xml +++ b/i18n/src/main/res/values-sc/strings.xml @@ -40,7 +40,6 @@ Cambia su nùmene de sa categoria Imposta sas categorias Muda sa cobertedda - Firma Pone in pàusa Capìtulu antepostu Capìtulu imbeniente @@ -416,7 +415,6 @@ Grìllia discansosa Peruna pàgina agatada Ischedas - Distintivos Ammustra sas ischedas de sas categorias Disabìlita totu Abìlita totu @@ -484,12 +482,10 @@ Zonas de tocu A tipu Kindle A forma de L - Registros de sos arrestos anòmalos Data de fine Data de incumintzu - Registros de sos arrestos anòmalos sarvados Sarva sos registros de sos errores in unu documentu pro lu cumpartzire cun sos isvilupadores - Iscàrriga sos registros de sos arrestos anòmalos + Cumpartzi sos registros de sos arrestos anòmalos In achirrada Creschente Pro nùmeru de capìtulu @@ -646,7 +642,6 @@ Contu de non lèghidos Peruna fonte agatada Peruna fonte installada agatada - Partzi sas immàgines artas Megiorat su rendimentu de su leghidore Pàgina %d no agatada partzende Oja, custu est impitzosu @@ -655,7 +650,6 @@ Resetat sa modalidade de leghidura e s\'orientamentu de totu sas sèries Totu sas impostatziones de su leghidore resetadas No at fatu a resetare sas impostatziones de su leghidore - No at fatu a partzire s\'immàgine iscarrigada Classificatzione pro edade Limba Versione @@ -699,7 +693,7 @@ Permissu de archiviatzione non cuntzessu Brincadu ca sa sèrie non tenet bisòngiu de agiornamentos Chirca… - %s at tentu un\'errore non prevìdidu. Ti cussigiamus de sarvare un\'ischermada de custu messàgiu, iscarrigare sos registros de sas serraduras anòmalas e de cumpartzire totu custu in su canale de suportu nostru de Discord. + %s at tentu un\'errore non prevìdidu. Ti cussigiamus de cumpartzire sos registros de sas serraduras anòmalas in su canale de suportu nostru de Discord. Torra a allùghere s\'aplicatzione Limba de s\'aplicatzione, notìficas Tema, data e formadu de s\'ora @@ -734,4 +728,13 @@ Cua sos elementos giai in sa biblioteca Còpia in punta de billete Agiorna sa categoria + Partzi sas immàgines artas + Istratu superiore + Diant pòdere mancare capìtulos + Gira sas pàginas largas pro las adatare + Fùrria s\'orientamentu de sas pàginas largas giradas + + Mancat %1$s capìtulu + Mancant %1$s capìtulos + \ No newline at end of file diff --git a/i18n/src/main/res/values-sdh/strings.xml b/i18n/src/main/res/values-sdh/strings.xml index 265a919f01..d5d525f227 100644 --- a/i18n/src/main/res/values-sdh/strings.xml +++ b/i18n/src/main/res/values-sdh/strings.xml @@ -61,7 +61,6 @@ نیشان نەکردنی چاپتەر دەستکاری کردن دەربارە - وەستان ڕاگرتنی کاتی لابردن دەست پێکردن diff --git a/i18n/src/main/res/values-sk/strings.xml b/i18n/src/main/res/values-sk/strings.xml index def218b7dc..299c1a4e2b 100644 --- a/i18n/src/main/res/values-sk/strings.xml +++ b/i18n/src/main/res/values-sk/strings.xml @@ -41,7 +41,6 @@ Premenovať kategóriu Určiť kategórie Upraviť obal - Zastaviť Pauza Predošlá kapitola Nasledujúca kapitola @@ -417,7 +416,6 @@ Ďalší Obnovenie zálohy zlyhalo Obnovenie bolo zrušené - Odznaky Karty Reťazec User-Agent nemôže byť prázdny Obnovenie nastavení čítačky pre jednotlivé série @@ -527,7 +525,6 @@ Automatické sťahovanie Opakovať pripnuté zdroje v ich príslušných jazykových skupinách Nenašiel sa žiadny zdroj - Stiahnutý obrázok sa nepodarilo rozdeliť Priebeh Vymazať históriu Chyba pri ukladaní obalu @@ -554,7 +551,6 @@ Kap. %1$s - %2$s Strana %d sa pri rozdeľovaní nenašla - Logy zlyhaní Publikovanie ukončené Nemáte žiadne pripnuté zdroje Táto verzia systému Android už nie je podporovaná @@ -616,7 +612,6 @@ Otvoriť na GitHub-e Nová verzia je k dispozícii z oficiálnych vydaní. Klepnutím sa dozviete, ako migrovať z neoficiálnych vydaní F-Droid. Príručka Začíname - Uložené logy o zlyhaní Zakázať režim inkognito Menej 5% @@ -674,7 +669,6 @@ Levanduľa 20% Mangy vo vylúčených kategóriách nebudú stiahnuté, aj keď sú tiež v zahrnutých kategóriách. - Rozdelenie vysokých obrázkov Čistenie databázy Vymazanie údajov WebView Údaje WebView boli vymazané diff --git a/i18n/src/main/res/values-sq/strings.xml b/i18n/src/main/res/values-sq/strings.xml index 6fcc91446c..d77e1c630a 100644 --- a/i18n/src/main/res/values-sq/strings.xml +++ b/i18n/src/main/res/values-sq/strings.xml @@ -108,7 +108,6 @@ Përditësimet e kapitullit U anashkalua Shkarkimet e indeksimit - Imazhi i shkarkuar nuk mund të ndahej Faqja %d nuk u gjet gjatë ndarjes Shkarkues Përditësoni aplikacionin WebView për përputhshmëri më të mirë @@ -249,7 +248,6 @@ Fshi kategorinë Redakto kopertinën Shiko kapitujt - Ndalo Ndalo Kapitulli i mëparshëm Kapitulli tjetër @@ -383,7 +381,6 @@ Shkarkoni përpara Shkarkim automatik gjatë leximit Ruaje si arkiv CBZ - Ndani imazhet e gjata Udhëzues gjurmimi Shërbime të përmirësuara @@ -593,7 +590,6 @@ Aktiviteti në sfond Ndihmon me përditësimet dhe kopjet rezervë të bibliotekës në sfond Ruan regjistrat e gabimeve në një skedar për t\'i ndarë me zhvilluesit - Regjistrat e aksidenteve u ruajtën Optimizimi i baterisë është tashmë i çaktivizuar Cilësimet e pajisjes nuk mund të hapeshin Kontrolloni për përditësime @@ -612,9 +608,7 @@ Kategoria po përditësohet Nga biblioteka Kapitujt e shkarkuar - Distinktivët Skedat - Tjetër Nuk ka më rezultate Kontrolloni faqen e internetit në WebView Burimi lokal @@ -746,7 +740,6 @@ Kjo do të heqë datën e leximit të këtij kapitulli. A je i sigurt\? Kjo do të heqë datën e fillimit të zgjedhur më parë nga %s Ky version Android nuk mbështetet më - Regjistrat e përplasjeve Faqja e internetit Version Cfare ka te re diff --git a/i18n/src/main/res/values-sr/strings.xml b/i18n/src/main/res/values-sr/strings.xml index bd1dfaca0f..b7e44b36b7 100644 --- a/i18n/src/main/res/values-sr/strings.xml +++ b/i18n/src/main/res/values-sr/strings.xml @@ -40,7 +40,6 @@ Преименуј категорију Изабери категорије Промени омот - Заустави Паузирај Претходно поглавље Наредно поглавље @@ -528,7 +527,6 @@ Нема доступне интернет везе Преузимање је завршено Ажурирања додатака - Логови крешовања Претходна страница Следећа страница Потамни (Burn) @@ -565,7 +563,6 @@ %1$s преосталих Грешке - Логови крешовања су сачувани Подешавања поглавља Скоро Данас @@ -610,7 +607,6 @@ Једносмерна синхронизација за ажурирање броја прочитаних поглавља у трекерима. За појединачно праћење наслова додирни дугме за њихово праћење. Подаци Инкогнито мод - Беџеви Омот је ажуриран %1$s: %2$s, страница %3$d За овај наслов @@ -666,7 +662,6 @@ Извори, екстензије, глобална претрага Ручне и аутоматске резервне копије Преузми аутоматски током читања - Раздели високе слике Копирано Сва подешавања читача су ресетована Присили апликацију да поново провери преузета поглавља @@ -675,7 +670,6 @@ Widget није доступан када је омогућено закључавање апликације Листа жеља Доступно, али извор није преузет: %s - Остало F-Droid верзије нису званично подржане. \nДодирните да сазнате више. Уклони датум\? @@ -740,7 +734,6 @@ Уклони све Упс! Поново покрени апликацију - Није могуће разделити преузету слику Није могуће отворити последње прочитано поглавље Претражи… Преузми унапред diff --git a/i18n/src/main/res/values-sv/strings-aniyomi.xml b/i18n/src/main/res/values-sv/strings-aniyomi.xml index a1fda1b4fa..715f95df31 100644 --- a/i18n/src/main/res/values-sv/strings-aniyomi.xml +++ b/i18n/src/main/res/values-sv/strings-aniyomi.xml @@ -2,8 +2,8 @@ Kategorier Lås upp Aniyomi - Sista manga-uppdateringen - Visa manga + Senaste uppdateringskontroll + Visa inlägg Lokal manga Med olästa kapitel Visa antalet olästa på ikonen Uppdateringar @@ -24,15 +24,15 @@ Rensa kapitelcache när appen stängs Rensa databas Ta bort historik för manga som inte finns i ditt bibliotek - Är du säker\? Lästa kapitel och framsteg av icke-bibliotek manga kommer att gå förlorade + Är du säker\? Lästa kapitel och framsteg för poster som inte är biblioteksinlägg kommer att gå förlorade Uppdaterar status, betyg och senaste kapitel läst från spårningstjänsterna Pausar läshistoriken - Manga från biblioteket + Från biblioteket Lägg till manga i biblioteket\? Fel Pausad - Gäller även all manga i biblioteket - Återställ alla kapitel för denna manga + Gäller även alla inlägg i biblioteket + Återställ alla kapitel för denna post Det gick inte att ladda ner kapitelt på grund av lågt lagringsutrymme Varning: stora massnerladdningar kan leda till att källor blir långsamma och/eller blockerar Tachiyomi. Tryck för att få veta mer. WebView krävs för Tachiyomi diff --git a/i18n/src/main/res/values-sv/strings.xml b/i18n/src/main/res/values-sv/strings.xml index 1c1124fa6a..def5884120 100644 --- a/i18n/src/main/res/values-sv/strings.xml +++ b/i18n/src/main/res/values-sv/strings.xml @@ -1,7 +1,7 @@ Namn - Manga + Biblioteksposter Kapitel Spårning Historik @@ -40,7 +40,6 @@ Byt namn på kategori Välj kategorier Redigera omslaget - Stop Paus Föregående kapitel Nästa kapitel @@ -180,7 +179,7 @@ Uppdaterar kategori Lokal Inga mer resultat - Lokal manga + Lokal källa Andra Global sökning… Senaste @@ -242,14 +241,14 @@ Kunde inte ladda ned kapitlen. Du kan försöka igen i nedladdningssektionen Nya kapitel hittades Misslyckades att uppdatera omslag - Vänligen lägg till mangan i ditt bibliotek innan du gör detta + Vänligen lägg till post i ditt bibliotek innan du gör detta Välj omslagsbild Välj säkerhetskopia Ladda ner Inga nya uppdateringar tillgängliga Söker efter uppdateringar… Laddar ner… - Tryck på för att installera + Tryck för att installera uppdateringen Nedladdningsfel Ny version tillgänglig! Inga nedladdningar @@ -281,7 +280,7 @@ Uppdatera Bibliotek Obsolet - Detta tillägg är inte längre tillgänglig. + Detta tillägg är inte längre tillgängligt. Det kanske inte fungerar korrekt och kan orsaka problem med appen. Vi rekommenderar att du avinstallerar det. Datumformat Global uppdatering Logga ut från %1$s\? @@ -297,8 +296,8 @@ Följ systemet Hantera aviseringar - Säkerhet - Kräv upplåsning + Säkerhet och integritet + Kräver upplåsning Lås vid inaktivitet Alltid Aldrig @@ -332,8 +331,8 @@ E-postadress Visa alltid kapitelövergång - För %d titel - För %d titlar + För %d post + För %d poster Meny Ändra ordning @@ -382,12 +381,12 @@ Säkerhetskopiering pågår redan %02d minuter, %02d sekunder Endast nedladdat - Filtrerar all manga i ditt bibliotek + Filtrerar alla poster i ditt bibliotek %1$s återstående %1$s återstående - Inkludera endast fästa källor + Sök endast i pinnade källor i global sökning Läsläge För denna serie Grå @@ -402,11 +401,11 @@ Det gick inte att öppna enhetsinställningarna Uppdatera bibliotekets omslag - Envägssynkronisering för att uppdatera kapitelförloppet i spårningstjänster. Ställ in spårning för enskilda manga från spårningsknappen under manga info. + Envägssynkronisering för att uppdatera kapitlets framsteg i spårningstjänsterna. Ställ in spårning för enskilda inlägg från deras spårningsknapp. Efter uppladdningsdatum Data Saknade källor: - Säkerhetskopian innehåller ingen manga. + Säkerhetskopian innehåller inga biblioteksposter. Ogiltig säkerhetskopia Detta tillägg kommer inte från den officiella Tachiyomi-tilläggslistan. Inofficiell @@ -416,7 +415,6 @@ Inaktivera alla Hittade inga sidor Flikar - Emblem Visa nuvarande läge när läsaren öppnas Visa läsläge Visa kategoriflikar @@ -480,10 +478,8 @@ Visa i källor och tilläggslistor Ingen filväljare-app hittades Vänligen logga in på MAL igen - Kraschloggar - Kraschloggar sparade Sparar felloggar till en fil för delning med utvecklarna - Dumpa kraschloggar + Dela kraschloggar Fallande Efter kapitelnummer Efter uppladdningsdatum @@ -514,7 +510,7 @@ Inkludera: %s Ingen Datum för hämtning av kapitel - Manga i uteslutna kategorier laddas inte ner även om de också ingår i inkluderade kategorier. + Inlägg i uteslutna kategorier laddas inte ner även om de också ingår i inkluderade kategorier. Ladda ned automatiskt Tryck för att se detaljer Denna Android-version stöds inte längre @@ -522,7 +518,7 @@ Liggande Porträtt Rotationstyp - Skapar mappar enligt mangatitel + Skapa mappar enligt posternas titel Spara sidor i separata mappar Åtgärder Gråskala @@ -552,7 +548,7 @@ Uppdaterar biblioteket ... (%1$d / %2$d) Denna tracker är endast kompatibel med Komga-källan. Säkerhetskopiering/återställning kanske inte fungerar korrekt om MIUI-optimering är inaktiverat. - Tjänster som erbjuder förbättrade funktioner för specifika källor. Manga spåras automatiskt när de läggs till i ditt bibliotek. + Tjänster som erbjuder förbättrade funktioner för specifika källor. Inlägg spåras automatiskt när de läggs till i ditt bibliotek. Förbättrade tjänster Rent svart mörkt läge Yotsuba @@ -594,7 +590,7 @@ Äldre Installatör Installerar tillägg… - Total manga + Totalt antal inlägg Installera och starta Shizuku för att använda Shizuku som tilläggsinstallationsprogram. Skriv ut utförliga loggar till systemloggen (minskar appprestanda) Du bör också förvara säkerhetskopiorna på andra ställen. @@ -606,10 +602,10 @@ Var 3:e dag Uppdatera alla Appuppdateringar - %1$d manga i databasen som inte är biblioteksmanga + %1$d poster i databasen som inte är biblioteksposter Inget att rensa För hjälp med att åtgärda fel i biblioteksuppdateringar, se %1$s - Hoppa över att uppdatera titlar + Hoppa över uppdatering av poster Avbruten På uppehåll Publiceringen avslutad @@ -645,7 +641,6 @@ Upprepa källor som är upptagna i sina respektive språkgrupper Ingen installerad källa hittades Antal olästa - Dela upp höga bilder Förbättrar läsarens prestanda Sida %d hittades inte under delning Ingen källa hittad @@ -662,7 +657,6 @@ Önskelista Klart lista Åldersklassificering - Kunde inte dela den nedladdade bilden Version Språk Det går inte att öppna det senast lästa kapitlet @@ -708,12 +702,12 @@ Ogiltig plats: %s Lagrings rättigheter inte tillagda Hoppat över för att serien inte behöver uppdateras - %s stötte på ett oväntat fel. Vi förslår att du tar en skärmdump av det här meddelandet, dumpar fel loggarna, och delar det i vår support kanal på Discord. + %s stötte på ett oväntat fel. Vi föreslår att du delar med dig av kraschloggen i vår supportkanal på Discord. Appspråk, notifikationer Tema, datum och tids format Källor, tillägg, global sökning Starta om applikationen - Ett oväntat fel inträffade + Hoppsan! Dumpa krasch logg, batterioptimering Applikations lås, säkerhetsskärm Nedladdningskö @@ -723,9 +717,26 @@ Nedladdade Tillgänglig men källan är ej installerad: %s Hoppa över duplicerade kapitel - Visa knappen för att återuppta läsning + Fortsätt läsa knapp Inte nu Öppna en slumpmässig manga Gör nedladdningsindexet ogiltigt Tvinga appen att läsa in nedladdade kapitel på nytt + F-Droid-versioner stöds inte officiellt. +\nTryck för att få veta mer. + Dela höga bilder + Uppdatera kategori + Kopiera till urklipp + Dölja poster som redan finns i biblioteket + Kontrollerar nedladdningar + Överlägg + + Saknar %1$s kapitel + Saknar %1$s kapitel + + Rotera breda sidor så att de passar + Vänd orientering av roterade breda sidor + Det kan saknas kapitel + Information om arbetare + *krävs \ No newline at end of file diff --git a/i18n/src/main/res/values-te/strings.xml b/i18n/src/main/res/values-te/strings.xml index 0ff46bd1f7..4a7ab17a45 100644 --- a/i18n/src/main/res/values-te/strings.xml +++ b/i18n/src/main/res/values-te/strings.xml @@ -58,7 +58,6 @@ తదుపరి అధ్యాయము మునుపటి అధ్యాయము తాత్కాలికముగా నిలిపి వేయుము - ఆపుము అధ్యాయాములను చూడుము ముఖచిత్రమును సవరించుము విభాగములను క్రమీకరించుము diff --git a/i18n/src/main/res/values-th/strings.xml b/i18n/src/main/res/values-th/strings.xml index fd005e495f..fa6ebdfea7 100644 --- a/i18n/src/main/res/values-th/strings.xml +++ b/i18n/src/main/res/values-th/strings.xml @@ -74,7 +74,6 @@ บุ๊คมาร์คตอน ยกเลิกบุ๊คมาร์คตอน ตั้งหมวดหมู่ - หยุด หยุดชั่วคราว นำออก ดำเนินการต่อ @@ -236,7 +235,7 @@ อัปเดตปกแล้ว หน้า: %1$d ไม่พบตอนถัดไป - ไม่สามารถโหลดรูปภาพได้ + ไม่สามารถโหลดภาพนี้ได้ ใช้ภาพนี้เป็นรูปปก\? อ่านจบแล้ว: ปัจจุบัน: @@ -439,7 +438,6 @@ ใช้ล่าสุด ตรวจสอบเว็บไซต์ใน WebView แท็บ - เครื่องหมาย ตอนที่ดาวน์โหลดแล้ว ออกจากระบบแล้ว ออกจากระบบ @@ -461,9 +459,8 @@ การเพิ่มประสิทธิภาพแบตเตอรี่ถูกปิดใช้งานอยู่แล้ว ช่วยในการอัปเดตและสำรองข้อมูลคลังในพื้นหลัง ปิดการใช้งานการเพิ่มประสิทธิภาพแบตเตอรี่ - บันทึกบันทึกข้อขัดข้องแล้ว บันทึกบันทึกข้อผิดพลาดลงในแฟ้มเพื่อแบ่งปันกับผู้พัฒนา - ดัมพ์บันทึกข้อขัดข้อง + แบ่งปันบันทึกข้อขัดข้อง โหลดปกในคลังใหม่ ข้อมูล ต้องรีสตาร์ทแอปจึงจะมีผล @@ -505,7 +502,6 @@ ของเรื่อง %d หน้าก่อน - บันทึกข้อขัดข้อง การอัปเดตส่วนขยาย ข้อผิดพลาด เสร็จสมบูรณ์ @@ -634,7 +630,6 @@ ไม่พบแหล่งที่มาที่ติดตั้งแล้ว ไม่พบแหล่งที่มาใด ๆ จำนวนตอนที่ยังไม่ได้อ่าน - แยกรูปภาพสูงยาว ไม่พบหน้า %d ขณะกำลังแยกหน้า เพิ่มประสิทธิภาพตัวอ่าน ไม่พบตำแหน่งไฟล์ของหน้า %d @@ -642,7 +637,6 @@ รีเซ็ตโหมดการอ่านและการจัดวางของทุกเรื่อง รีเซ็ตค่ากำหนดตัวอ่านทั้งหมดแล้ว ไม่สามารถรีเซ็ตค่ากำหนดตัวอ่าน - ไม่สามารถแยกภาพที่ดาวน์โหลดไว้ได้ เอ่อ นี้มันงี่เง่าชะมัด เวอร์ชัน ภาษา @@ -697,7 +691,7 @@ การล็อกแอป, หน้าจอความปลอดภัย ดัมพ์บันทึกข้อขัดข้อง, การเพิ่มประสิทธิภาพแบตเตอรี่ เกิดความผิดพลาดอย่างไม่ได้คาดคิด - %s เกิดข้อผิดพลาดอย่างไม่คาดคิด ขอแนะนำให้คุณบันทึกภาพหน้าจอข้อความนี้ จากนั้นดัมพ์บันทึกข้อขัดข้อง แล้วส่งลงไปในช่องสนับสนุนของเราใน Discord + %s เกิดข้อผิดพลาดอย่างไม่คาดคิด ขอแนะนำให้คุณแบ่งปันบันทึกข้อขัดข้อง แล้วส่งลงไปในช่องสนับสนุนของเราใน Discord ตำแหน่งไม่ถูกต้อง: %s ไม่ทราบชื่อเรื่อง เริ่มแอปพลิเคชันใหม่ @@ -717,4 +711,12 @@ ซ่อนรายการที่มีอยู่ในคลังอยู่แล้ว คัดลอกไปยังคลิปบอร์ด อัปเดตหมวดหมู่ + อาจเป็นตอนที่ไม่พบ + แยกภาพสูงยาว + โอเวอร์เลย์ + + ไม่พบ %1$s ตอน + + หมุนหน้ากว้างให้พอดี + พลิกการวางแนวของหน้ากว้างที่หมุน \ No newline at end of file diff --git a/i18n/src/main/res/values-tr/strings.xml b/i18n/src/main/res/values-tr/strings.xml index 7aef115d65..6347bd6973 100644 --- a/i18n/src/main/res/values-tr/strings.xml +++ b/i18n/src/main/res/values-tr/strings.xml @@ -2,7 +2,7 @@ Ad Kategoriler - Manga + Kütüphane girdileri Bölümler İzleme Geçmiş @@ -38,10 +38,9 @@ Ekle Kategori ekle Kategorileri düzenle - Kategoriyi yeniden adlandır + Kategorileri yeniden adlandır Kategorileri ayarla Kapağı düzenle - Durdur Duraklat Önceki bölüm Sonraki bölüm @@ -416,7 +415,6 @@ Taşın Rahat ızgara Sekmeler - Rozetler Kategori sekmelerini göster Sayfa bulunamadı Tümünü devre dışı bırak @@ -485,12 +483,10 @@ Kenar Kindle gibi L şeklinde - Çökme günlükleri Bitirme tarihi Başlama tarihi - Çökme günlükleri kaydedildi Geliştiricilerle paylaşmak için hata günlüklerini bir dosyaya kaydeder - Çökme günlüklerini kaydet + Çökme günlüklerini paylaş Azalan Artan Bölüm numarasına göre @@ -647,7 +643,6 @@ Kurulu kaynak bulunamadı Kaynak bulunamadı Okunmayan sayısı - Uzun görselleri böl Okuyucu performansını iyileştirir Bölünürken sayfa %d bulunamadı Sayfa %d dosya yolu bulunamadı @@ -655,7 +650,6 @@ Tüm serilerin okuma kipini ve yönünü sıfırlar Tüm okuyucu ayarları sıfırlandı Okuyucu ayarları sıfırlanamadı - İndirilen görsel bölünemedi Peki, bu garip Sürüm Dil @@ -673,7 +667,7 @@ Açıklama yok Lavanta Kategoriyi sil - \"%s\" kategorisini silmek istiyor musunuz\? + \"%s\" kategorilerini silmek istiyor musunuz\? Dahili hata: Daha fazla bilgi için çökme günlüklerine bakın Öntanımlı kullanıcı aracısı dizgesi Öntanımlı kullanıcı aracısı dizgesini sıfırla @@ -704,7 +698,7 @@ Uygulama kilidi, güvenli ekran Çökme günlükleri dökümü, pil iyileştirmeleri Uygulama dili, bildirimler - Kategoriler, genel güncelleme + Kategorileri, genel güncelle Kaynaklar, uzantılar, genel arama Otomatik indir, önceden indir Okuma kipi, görüntüleme, gezinme @@ -712,7 +706,7 @@ Tek yönlü ilerleme eşitlemesi, gelişmiş eşitleme Uygulamayı yeniden başlat Beklenmeyen Bir Hata Oluştu - %s beklenmeyen bir hatayla karşılaştı. Bu mesajın ekran görüntüsünü ve çökme günlüklerinin dökümünü almanızı, ardından Discord\'daki destek kanalımızda paylaşmanızı öneririz. + %s beklenmeyen bir hatayla karşılaştı. Çökme günlüklerini Discord\'daki destek kanalımızda paylaşmanızı öneririz. Bilinmeyen başlık Geçersiz konum: %s Geçersiz kullanıcı aracısı dizgesi @@ -737,4 +731,13 @@ Panoya kopyala Kategoriyi güncelle + Uzun görselleri otomatik böl + Kaplama + Eksik bölümler olabilir + + %1$s bölüm eksik + %1$s bölüm eksik + + Geniş sayfaları sığdırmak için döndür + Döndürülen geniş sayfaların yönünü çevir \ No newline at end of file diff --git a/i18n/src/main/res/values-uk/strings.xml b/i18n/src/main/res/values-uk/strings.xml index b6f9952462..9ad9fd817b 100644 --- a/i18n/src/main/res/values-uk/strings.xml +++ b/i18n/src/main/res/values-uk/strings.xml @@ -40,7 +40,6 @@ Перейменувати категорію Помістити в категорію Змінити обкладинку - Стоп Пауза Попередній розділ Наступний розділ @@ -228,7 +227,7 @@ Обкладинку оновлено Сторінка %1$d Наступний розділ не знайдено - Зображення не може бути завантажене + Зображення не вдалося завантажити Ви бажаєте встановити цю картинку як обкладинку\? Завершено: Поточна: @@ -399,7 +398,6 @@ Посібник місцевого джерела Останній використаний Вкладки - Значки %1$s залишилось %1$s залишилось @@ -502,12 +500,10 @@ Будь ласка, увійдіть в MAL знову Показувати в списку джерел та розширень NSFW (18+) джерела - Логи падінь Дата закінчення Дата початку - Логи падінь збережено Зберігає логи помилок до файлу для надсилання розробникам - Дамп логів про падіння + Поділіться журналами збоїв Зони тицяння Межа Kindle-подібна @@ -655,8 +651,6 @@ Пропущено, оскільки серія не почата Немає елементів у бібліотеці для резервування Сторінку %d не знайдено, при розділенні - Неможливо розділити завантажене зображення - Розділяти довгі зображення Покращує продуктивність читалки Дані WebView очищені Очистити дані WebView @@ -723,7 +717,7 @@ Рядок User agent не може бути пустим Популярне Автоматичне завантаження під час читання - %s зіткнулось з непередбачуваною помилкою. Ми пропонуємо вам, зробити скріншот цього повідомлення, завантажити журнал помилок та поділитись ними з нами на нашому каналі підтримки в Discord. + %s зіткнулися з неочікуваною помилкою. Ми пропонуємо вам поділитися журналами збоїв у нашому каналі підтримки на Discord. Мова застосунку, сповіщення Тема, формат дати та часу Категорії, глобальне оновлення @@ -759,4 +753,15 @@ Наступних %d розділів Оновити категорію + Розділяти довгі зображення + Оверлей + Можливо, відсутні розділи + Повернути широкі сторінки за розміром + Перевернути орієнтацію обернутих широких сторінок + + Відсутній %1$s розділ + Відсутні %1$s розділи + Відсутні %1$s розділів + Відсутні %1$s розділів + \ No newline at end of file diff --git a/i18n/src/main/res/values-uz/strings.xml b/i18n/src/main/res/values-uz/strings.xml index 8e62d40a39..d5aa8ed6a5 100644 --- a/i18n/src/main/res/values-uz/strings.xml +++ b/i18n/src/main/res/values-uz/strings.xml @@ -1,6 +1,5 @@ - Tõxatish Bõlimlarni kõrish Muqovani õzgartirish Kategoriyalar õrnatish diff --git a/i18n/src/main/res/values-vi/strings-aniyomi.xml b/i18n/src/main/res/values-vi/strings-aniyomi.xml index 499e4970e5..eb565bae42 100644 --- a/i18n/src/main/res/values-vi/strings-aniyomi.xml +++ b/i18n/src/main/res/values-vi/strings-aniyomi.xml @@ -27,7 +27,7 @@ Bạn có chắc không\? Các chương đã đọc và tiến độ đọc các truyện không nằm trong thư viện sẽ bị mất Cập nhật trạng thái, điểm số và chương cuối đã đọc từ dịch vụ theo dõi Dừng lưu giữ lịch sử đọc - Truyện từ thư viện + Từ thư viện Thêm truyện vào thư viện\? Lỗi Đã tạm dừng diff --git a/i18n/src/main/res/values-vi/strings.xml b/i18n/src/main/res/values-vi/strings.xml index 5780147b31..d8f0187c4a 100644 --- a/i18n/src/main/res/values-vi/strings.xml +++ b/i18n/src/main/res/values-vi/strings.xml @@ -33,7 +33,6 @@ Đổi tên danh mục Đặt danh mục Sửa ảnh bìa - Dừng lại Tạm dừng Chương trước Chương kế @@ -186,7 +185,7 @@ Đã cập nhật ảnh bìa Trang: %1$d Không tìm thấy chương kế tiếp - Loạt ảnh hiện không thể được tải lên + Hình ảnh không thể hiển thị được Bạn có chắc muốn đặt ảnh này làm ảnh bìa? @@ -274,7 +273,7 @@ Đã thêm vào thư viện Đã xóa khỏi thư viện Muốn xóa các chương đã tải\? - Đã sao chép từ bộ nhớ đệm: + Đã sao chép vào bảng tạm: \n%1$s Nguồn truyện không thể cài đặt: %1$s Đọc lại @@ -315,7 +314,7 @@ %1$d chương mới có - Cho %d tiêu đề + Cho %d chương Kiểm tra các chương cập nhật mới Đang cập nhật thư viện @@ -371,7 +370,7 @@ Mục chính Nguồn Thêm - Chỉ bao gồm các nguồn được ghim + Chỉ bao gồm những nguồn được ghim khi tìm kiếm toàn bộ Đồng bộ một chiều để cập nhật tiến trình chương trong các dịch vụ theo dõi. Thiết lập theo dõi cho các truyện riêng lẻ từ nút theo dõi. 25% 20% @@ -427,14 +426,11 @@ Ngày bắt đầu Chế độ ẩn danh Làm mới ảnh bìa trong thư viện - Thông tin lỗi đã được lưu - Tệp tin báo lỗi tạm + Chia sẻ tệp báo lỗi tạm Lưu thông tin lỗi và gửi về nhà phát triển Không tìm thấy ứng dụng chọn tệp Trang trước - Logs lỗi Trang tiếp theo - Huy hiệu Đã cập nhật tuỳ chỉnh mặc định cho chương Lỗi Hoàn thành @@ -464,7 +460,7 @@ Bạn có chắc chắn muốn lưu lại tuỳ chỉnh này hay không\? Tuỳ chỉnh chương Bởi ngày đăng - Lưu vào bộ nhớ tạm thất bại + Sao chép vào bảng tạm thất bại %1$s chương @@ -666,7 +662,6 @@ Xóa dữ liệu WebView Chỉ trên mạng lưới không hạn định Số lượng chưa đọc - Tự động chia cắt những ảnh quá dài Cải thiện khả năng đọc bằng việc cắt nhỏ các ảnh quá dài Tất cả cài đặt đọc đã được cài lại Không thể cài lại cài đặt đọc @@ -687,7 +682,6 @@ Danh sách hoàn thành Danh sách đang chờ Danh sách muốn đọc - Không thể chia cắt ảnh đã tải xuống Trang %d không thể được tìm thấy khi chia cắt Phân loại độ tuổi Không thể tìm thấy đường dẫn của trang %d @@ -728,6 +722,37 @@ Đồng bộ tiến trình đọc, đồng bộ nâng cao Nguồn, tiện ích, tìm kiếm toàn bộ Hàng chờ tải xuống + Bản dựng F-Droid không được hỗ trợ chính thức. +\nNhấn để tìm hiểu thêm. Bỏ qua chương đúp Có sẵn nhưng nguồn không được cài đặt: %s + Ngày %d + Tách ra những hình ảnh dài + Giấu những truyện đã có trong thư viện + %d phút + Đang kiểm tra những truyện đã tải xuống + %s đã có lỗi. Bạn nên chia sẻ tệp báo lỗi tạm ở trong kênh hỗ trợ của chúng tôi trên Discord. + Cập nhật danh mục + Truyện này đã có sẵn trong thư viện bạn. +\n +\nBạn có muốn thêm lại không\? + Đã sao chép vào bảng tạm + + %d Chương tiếp theo + + Sao chép vào bảng tạm + %d giây + %d giờ + Quay những trang rộng để vừa màn hình + Lật hướng của các trang rộng đã xoay + Phủ lên + Có thể đang thiếu chương + + Đang thiếu %1$s + + Loại bỏ ngày\? + Thông tin worker + %1$s Lỗi: %2$s + N/A + *bắt buộc \ No newline at end of file diff --git a/i18n/src/main/res/values-zh-rCN/strings.xml b/i18n/src/main/res/values-zh-rCN/strings.xml index 183a1fd332..f79acb6876 100644 --- a/i18n/src/main/res/values-zh-rCN/strings.xml +++ b/i18n/src/main/res/values-zh-rCN/strings.xml @@ -37,7 +37,6 @@ 重命名分类 设置分类 编辑封面 - 停止 暂停 上一章 下一章 @@ -407,7 +406,6 @@ 迁移 松散网格 标签 - 标记 显示分类标签 没有找到页面 全部禁用 @@ -473,12 +471,10 @@ 边缘样式 Kindle 样式 L 样式 - 崩溃日志 阅读完毕日期 开始阅读日期 - 崩溃日志已保存 将错误日志保存到文件中,以便分享给开发者 - 导出崩溃日志 + 分享崩溃日志 降序 升序 按章节编号 @@ -634,15 +630,13 @@ 未找到已安装的图源 未找到图源 未读数 - 分割长图 可以改善阅读器的性能 拆分时未找到页 %d 找不到页面 %d 的文件路径 - 重置每一连载阅读器设置 - 重置所有连载的阅读模式和方向 + 重置各作品阅读器独立设置 + 重置所有作品的阅读模式和屏幕方向 已重置所有阅读器设置 - 无法重置阅读器设置 - 无法拆分下载的图像 + 重置阅读器设置失败 额,尴尬了 年龄分级 版本 @@ -687,7 +681,7 @@ 已跳过,因为作品无需更新 搜索… 发生了意外错误 - %s 发生了意外错误。建议你将此信息截图,导出崩溃日志,并在 Discord #support 频道或者 GitHub 上反馈。 + %s 发生了意外错误。建议你将崩溃日志分享到 Discord #support 频道或者在 GitHub 上反馈。 重启应用 主题 • 日期格式 应用语言 • 通知 @@ -720,4 +714,12 @@ 隐藏已添加到书架的作品 复制到剪贴板 更新分类 + 分割长图 + 标记和按钮 + 旋转横向图片 + + 缺少 %1$s 章 + + 可能缺少章节 + 旋转横向图片时反转方向 \ No newline at end of file diff --git a/i18n/src/main/res/values-zh-rTW/strings.xml b/i18n/src/main/res/values-zh-rTW/strings.xml index b0bcf245e1..19e1dd3870 100644 --- a/i18n/src/main/res/values-zh-rTW/strings.xml +++ b/i18n/src/main/res/values-zh-rTW/strings.xml @@ -39,7 +39,6 @@ 重新命名類別 設定類別 編輯封面 - 停止 暫停 上一章 下一章 @@ -221,7 +220,7 @@ 標題 狀態 狀態 - 無法載入該圖片 + 圖片無法載入 請先將作品收藏至書櫃 選擇備份檔案 尚無任何類別,輕觸新增按鈕即可建立類別以組織你的書櫃。 @@ -302,7 +301,7 @@ 永不 立即 閒置時鎖定 - 需要解鎖 + 上鎖應用程式 在切換應用程式時隱藏預覽,並禁止擷取螢幕畫面 防窺畫面 隱私 @@ -383,7 +382,6 @@ 還原失敗 備份失敗 登出 %1$s? - 標記 剩餘 %1$s 本 @@ -474,10 +472,8 @@ Kindle 式 L 式 已紀錄歷程 - 當機記錄 - 已產生當機記錄 產生錯誤記錄檔以便分享予開發人員 - 傾印當機記錄 + 分享當機記錄 輕觸區域 左右式 依上傳日期 @@ -488,7 +484,7 @@ 已下載的章節 分割寬頁 若分割後的排版方向與翻閱順序不符 - 反轉分割頁面排版 + 調換分割寬頁排版 DNS over HTTPS (DoH) 自動下載 直向 @@ -511,7 +507,7 @@ \n隨後請安裝所有遺失的擴充套件並重新登入各歷程平台。 類別同時屬於「排除」及「包含」的作品,將不會自動下載。 儲存頁面至個別資料夾 - 根據漫畫標題建立資料夾 + 根據作品標題建立資料夾 開始閱讀時,短暫浮現輕觸區域 輕觸區域提示 取消此叢書 @@ -607,7 +603,7 @@ 常見問題與指南 5% 縮放橫向圖片 - 輕觸時移動圖片 + 輕觸大圖時先平移後翻頁 純封面格狀 已開始閱讀 無已讀的章節 @@ -635,14 +631,12 @@ 找不到已安裝的來源 未讀章數 分割時找不到第 %d 頁 - 分割過高的圖片 用以改善閱讀器效能 找不到第 %d 頁的檔案路徑 重設個別閱讀器設定 將所有叢書的閱讀模式和螢幕方向恢復為預設值 已重設所有閱讀器設定 無法重設閱讀器設定 - 無法分割下載的圖片 呃…尷尬了 版本 語言 @@ -696,7 +690,7 @@ 傾印當機記錄、電池效能最佳化 重新啟動應用程式 發生意外錯誤 - 「%s」發生了未預期的錯誤。我們建議你擷取此畫面、傾印當機記錄並分享至我們位於 Discord 上的 support 頻道。 + 「%s」發生了未預期的錯誤。我們建議你將當機記錄分享至我們位於 Discord 上的 support 頻道。 類別、全域更新 來源、擴充套件、全域搜尋 無效的位置:%s @@ -722,4 +716,12 @@ 後續 %d 章 更新類別 + 分割過高的圖片 + 封面附加元素 + 部分章節可能缺漏 + + 缺漏 %1$s 章 + + 旋轉寬頁以符合螢幕 + 調換旋轉寬頁方向 \ No newline at end of file diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index c071e2cd80..5287a912dd 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -85,7 +85,6 @@ Delete category Edit cover View chapters - Stop Pause Previous chapter Next chapter @@ -312,6 +311,8 @@ Split wide pages Invert split page placement If the placement of the split wide pages don\'t match reading direction + Rotate wide pages to fit + Flip orientation of rotated wide pages Split tall images (BETA) Show content in cutout area Animate page transitions @@ -364,11 +365,11 @@ Next Left Right - Left to right - Right to left - Vertical - Webtoon - Continuous vertical + Paged (left to right) + Paged (right to left) + Paged (vertical) + Long strip + Long strip with gaps Paged Tap zones Scale type @@ -438,6 +439,7 @@ Only works on entries in library and if the current chapter plus the next one are already downloaded Save as CBZ archive + Split tall images Improves reader performance Maximum downloads @@ -516,9 +518,8 @@ Resets reading mode and orientation of all series All reader settings reset Couldn\'t reset reader settings - Dump crash logs + Share crash logs Saves error logs to a file for sharing with the developers - Crash logs saved Background activity Disable battery optimization Helps with background library updates and backups @@ -528,7 +529,7 @@ Tablet UI Verbose logging Print verbose logs to system log (reduces app performance) - Worker info + Debug info Website @@ -573,8 +574,8 @@ Updating category Local Downloaded chapters - - Complications + + Overlay Tabs @@ -599,6 +600,12 @@ Order by Date + + + Missing %1$s item + Missing %1$s items + + Ongoing Unknown @@ -770,7 +777,7 @@ Whoops! - %s ran into an unexpected error. We suggest you screenshot this message, dump the crash logs, and then share it in our support channel on Discord. + %s ran into an unexpected error. We suggest you share the crash logs in our support channel on Discord. Restart the application @@ -887,7 +894,6 @@ Skipped App updates Extension updates - Crash logs Previous page diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index d52362b19d..ee47ea68cb 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -3,15 +3,12 @@ package tachiyomi.presentation.core.components import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only @@ -21,7 +18,6 @@ import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.windowInsetsPadding -import androidx.compose.foundation.shape.ZeroCornerSize import androidx.compose.material.SwipeableState import androidx.compose.material.rememberSwipeableState import androidx.compose.material.swipeable @@ -46,18 +42,14 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlin.math.roundToInt -import kotlin.time.Duration.Companion.milliseconds -private const val SheetAnimationDuration = 500 +private const val SheetAnimationDuration = 350 private val SheetAnimationSpec = tween(durationMillis = SheetAnimationDuration) -private const val ScrimAnimationDuration = 350 -private val ScrimAnimationSpec = tween(durationMillis = ScrimAnimationDuration) @Composable fun AdaptiveSheet( @@ -72,12 +64,11 @@ fun AdaptiveSheet( var targetAlpha by remember { mutableStateOf(0f) } val alpha by animateFloatAsState( targetValue = targetAlpha, - animationSpec = ScrimAnimationSpec, + animationSpec = SheetAnimationSpec, ) val internalOnDismissRequest: () -> Unit = { scope.launch { targetAlpha = 0f - delay(ScrimAnimationSpec.durationMillis.milliseconds) onDismissRequest() } } @@ -93,11 +84,6 @@ fun AdaptiveSheet( .alpha(alpha), contentAlignment = Alignment.Center, ) { - Box( - modifier = Modifier - .matchParentSize() - .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f)), - ) Surface( modifier = Modifier .requiredWidthIn(max = 460.dp) @@ -138,16 +124,6 @@ fun AdaptiveSheet( ) { val fullHeight = constraints.maxHeight.toFloat() val anchors = mapOf(0f to 0, fullHeight to 1) - val scrimAlpha by animateFloatAsState( - targetValue = if (swipeState.targetValue == 1) 0f else 1f, - animationSpec = ScrimAnimationSpec, - ) - Box( - modifier = Modifier - .matchParentSize() - .alpha(scrimAlpha) - .background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.32f)), - ) Surface( modifier = Modifier .widthIn(max = 460.dp) @@ -180,12 +156,8 @@ fun AdaptiveSheet( .windowInsetsPadding( WindowInsets.systemBars .only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), - ) - .consumeWindowInsets( - WindowInsets.systemBars - .only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), ), - shape = MaterialTheme.shapes.extraLarge.copy(bottomStart = ZeroCornerSize, bottomEnd = ZeroCornerSize), + shape = MaterialTheme.shapes.extraLarge, tonalElevation = tonalElevation, content = { BackHandler(enabled = swipeState.targetValue == 0, onBack = internalOnDismissRequest) @@ -199,7 +171,6 @@ fun AdaptiveSheet( .drop(1) .filter { it == 1 } .collectLatest { - delay(ScrimAnimationSpec.durationMillis.milliseconds) onDismissRequest() } } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItemsPaddings.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItemsPaddings.kt index 5bdf37ffe2..e4c05afead 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItemsPaddings.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItemsPaddings.kt @@ -49,6 +49,18 @@ fun HeadingItem( ) } +@Composable +fun BasicItem( + label: String, + onClick: () -> Unit, +) { + SortItem( + label = label, + sortDescending = null, + onClick = onClick, + ) +} + @Composable fun SortItem( label: String, diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt index 92fcd0ca34..b7d769f357 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/VerticalFastScroller.kt @@ -48,6 +48,7 @@ import androidx.compose.ui.util.fastMaxBy import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.sample import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX import kotlin.math.abs import kotlin.math.max @@ -124,14 +125,16 @@ fun VerticalFastScroller( val alpha = remember { Animatable(0f) } val isThumbVisible = alpha.value > 0f LaunchedEffect(scrolled, alpha) { - scrolled.collectLatest { - if (thumbAllowed()) { - alpha.snapTo(1f) - alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) - } else { - alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + scrolled + .sample(100) + .collectLatest { + if (thumbAllowed()) { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } else { + alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + } } - } } Box( @@ -304,14 +307,16 @@ fun VerticalGridFastScroller( val alpha = remember { Animatable(0f) } val isThumbVisible = alpha.value > 0f LaunchedEffect(scrolled, alpha) { - scrolled.collectLatest { - if (thumbAllowed()) { - alpha.snapTo(1f) - alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) - } else { - alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + scrolled + .sample(100) + .collectLatest { + if (thumbAllowed()) { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } else { + alpha.animateTo(0f, animationSpec = ImmediateFadeOutAnimationSpec) + } } - } } Box( diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt index 093a4a8dd8..e244d08cc3 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/WheelPicker.kt @@ -4,15 +4,17 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.LazyListItemInfo import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,75 +22,55 @@ import androidx.compose.runtime.LaunchedEffect 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.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import tachiyomi.presentation.core.components.material.padding -import java.text.DateFormatSymbols -import java.time.LocalDate +import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide +import tachiyomi.presentation.core.util.clickableNoIndication +import tachiyomi.presentation.core.util.showSoftKeyboard import kotlin.math.absoluteValue @Composable -fun WheelPicker( +fun WheelNumberPicker( modifier: Modifier = Modifier, startIndex: Int = 0, - count: Int, + items: List, size: DpSize = DpSize(128.dp, 128.dp), onSelectionChanged: (index: Int) -> Unit = {}, backgroundContent: (@Composable (size: DpSize) -> Unit)? = { WheelPickerDefaults.Background(size = it) }, - itemContent: @Composable LazyItemScope.(index: Int) -> Unit, ) { - val lazyListState = rememberLazyListState(startIndex) - - LaunchedEffect(lazyListState, onSelectionChanged) { - snapshotFlow { lazyListState.firstVisibleItemScrollOffset } - .map { calculateSnappedItemIndex(lazyListState) } - .distinctUntilChanged() - .collectLatest { - onSelectionChanged(it) - } - } - - Box( + WheelPicker( modifier = modifier, - contentAlignment = Alignment.Center, + startIndex = startIndex, + items = items, + size = size, + onSelectionChanged = onSelectionChanged, + manualInputType = KeyboardType.Number, + backgroundContent = backgroundContent, ) { - backgroundContent?.invoke(size) - - LazyColumn( - modifier = Modifier - .height(size.height) - .width(size.width), - state = lazyListState, - contentPadding = PaddingValues(vertical = size.height / RowCount * ((RowCount - 1) / 2)), - flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), - ) { - items(count) { index -> - Box( - modifier = Modifier - .height(size.height / RowCount) - .width(size.width) - .alpha( - calculateAnimatedAlpha( - lazyListState = lazyListState, - index = index, - ), - ), - contentAlignment = Alignment.Center, - ) { - itemContent(index) - } - } - } + WheelPickerDefaults.Item(text = "$it") } } @@ -96,7 +78,7 @@ fun WheelPicker( fun WheelTextPicker( modifier: Modifier = Modifier, startIndex: Int = 0, - texts: List, + items: List, size: DpSize = DpSize(128.dp, 128.dp), onSelectionChanged: (index: Int) -> Unit = {}, backgroundContent: (@Composable (size: DpSize) -> Unit)? = { @@ -106,122 +88,126 @@ fun WheelTextPicker( WheelPicker( modifier = modifier, startIndex = startIndex, - count = remember(texts) { texts.size }, + items = items, size = size, onSelectionChanged = onSelectionChanged, backgroundContent = backgroundContent, ) { - WheelPickerDefaults.Item(text = texts[it]) + WheelPickerDefaults.Item(text = it) } } @Composable -fun WheelDatePicker( +private fun WheelPicker( modifier: Modifier = Modifier, - startDate: LocalDate = LocalDate.now(), - minDate: LocalDate? = null, - maxDate: LocalDate? = null, - size: DpSize = DpSize(256.dp, 128.dp), + startIndex: Int = 0, + items: List, + size: DpSize = DpSize(128.dp, 128.dp), + onSelectionChanged: (index: Int) -> Unit = {}, + manualInputType: KeyboardType? = null, backgroundContent: (@Composable (size: DpSize) -> Unit)? = { WheelPickerDefaults.Background(size = it) }, - onSelectionChanged: (date: LocalDate) -> Unit = {}, + itemContent: @Composable LazyItemScope.(item: T) -> Unit, ) { - var internalSelection by remember { mutableStateOf(startDate) } - val internalOnSelectionChange: (LocalDate) -> Unit = { - internalSelection = it - onSelectionChanged(internalSelection) + val haptic = LocalHapticFeedback.current + val lazyListState = rememberLazyListState(startIndex) + + var internalIndex by remember { mutableStateOf(startIndex) } + val internalOnSelectionChanged: (Int) -> Unit = { + internalIndex = it + onSelectionChanged(it) + } + + LaunchedEffect(lazyListState, onSelectionChanged) { + snapshotFlow { lazyListState.firstVisibleItemScrollOffset } + .map { calculateSnappedItemIndex(lazyListState) } + .distinctUntilChanged() + .drop(1) + .collectLatest { + haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) + internalOnSelectionChanged(it) + } } - Box(modifier = modifier, contentAlignment = Alignment.Center) { + Box( + modifier = modifier + .height(size.height) + .width(size.width), + contentAlignment = Alignment.Center, + ) { backgroundContent?.invoke(size) - Row { - val singularPickerSize = DpSize( - width = size.width / 3, - height = size.height, - ) - // Day - val dayOfMonths = remember(internalSelection, minDate, maxDate) { - if (minDate == null && maxDate == null) { - 1..internalSelection.lengthOfMonth() - } else { - val minDay = if (minDate?.month == internalSelection.month && - minDate?.year == internalSelection.year - ) { - minDate.dayOfMonth - } else { - 1 - } - val maxDay = if (maxDate?.month == internalSelection.month && - maxDate?.year == internalSelection.year - ) { - maxDate.dayOfMonth - } else { - 31 - } - minDay..maxDay.coerceAtMost(internalSelection.lengthOfMonth()) - }.toList() + var showManualInput by remember { mutableStateOf(false) } + if (showManualInput) { + var value by remember { + val currentString = items[internalIndex].toString() + mutableStateOf(TextFieldValue(text = currentString, selection = TextRange(currentString.length))) } - WheelTextPicker( - size = singularPickerSize, - texts = dayOfMonths.map { it.toString() }, - backgroundContent = null, - startIndex = dayOfMonths.indexOfFirst { it == startDate.dayOfMonth }.coerceAtLeast(0), - onSelectionChanged = { index -> - val newDayOfMonth = dayOfMonths[index] - internalOnSelectionChange(internalSelection.withDayOfMonth(newDayOfMonth)) - }, - ) - // Month - val months = remember(internalSelection, minDate, maxDate) { - val monthRange = if (minDate == null && maxDate == null) { - 1..12 - } else { - val minMonth = if (minDate?.year == internalSelection.year) { - minDate.monthValue - } else { - 1 - } - val maxMonth = if (maxDate?.year == internalSelection.year) { - maxDate.monthValue - } else { - 12 + val scope = rememberCoroutineScope() + BasicTextField( + modifier = Modifier + .align(Alignment.Center) + .showSoftKeyboard(true) + .clearFocusOnSoftKeyboardHide { + scope.launch { + items + .indexOfFirst { it.toString() == value.text } + .takeIf { it >= 0 } + ?.apply { + internalOnSelectionChanged(this) + lazyListState.scrollToItem(this) + } + + showManualInput = false + } + }, + value = value, + onValueChange = { value = it }, + singleLine = true, + keyboardOptions = KeyboardOptions( + keyboardType = manualInputType!!, + imeAction = ImeAction.Done, + ), + textStyle = MaterialTheme.typography.titleMedium + + TextStyle( + color = MaterialTheme.colorScheme.onSurface, + textAlign = TextAlign.Center, + ), + cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), + ) + } else { + LazyColumn( + modifier = Modifier + .let { + if (manualInputType != null) { + it.clickableNoIndication { showManualInput = true } + } else { + it + } + }, + state = lazyListState, + contentPadding = PaddingValues(vertical = size.height / RowCount * ((RowCount - 1) / 2)), + flingBehavior = rememberSnapFlingBehavior(lazyListState = lazyListState), + ) { + itemsIndexed(items) { index, item -> + Box( + modifier = Modifier + .height(size.height / RowCount) + .width(size.width) + .alpha( + calculateAnimatedAlpha( + lazyListState = lazyListState, + index = index, + ), + ), + contentAlignment = Alignment.Center, + ) { + itemContent(item) } - minMonth..maxMonth } - val dateFormatSymbols = DateFormatSymbols() - monthRange.map { it to dateFormatSymbols.months[it - 1] } - } - WheelTextPicker( - size = singularPickerSize, - texts = months.map { it.second }, - backgroundContent = null, - startIndex = months.indexOfFirst { it.first == startDate.monthValue }.coerceAtLeast(0), - onSelectionChanged = { index -> - val newMonth = months[index].first - internalOnSelectionChange(internalSelection.withMonth(newMonth)) - }, - ) - - // Year - val years = remember(minDate, maxDate) { - val minYear = minDate?.year?.coerceAtLeast(1900) ?: 1900 - val maxYear = maxDate?.year?.coerceAtMost(2100) ?: 2100 - val yearRange = minYear..maxYear - yearRange.toList() } - WheelTextPicker( - size = singularPickerSize, - texts = years.map { it.toString() }, - backgroundContent = null, - startIndex = years.indexOfFirst { it == startDate.year }.coerceAtLeast(0), - onSelectionChanged = { index -> - val newYear = years[index] - internalOnSelectionChange(internalSelection.withYear(newYear)) - }, - ) } } } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt index 80265cd815..6327632399 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/screens/EmptyScreen.kt @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.paddingFromBaseline +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -21,6 +23,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.util.secondaryItemAlpha import kotlin.random.Random @@ -54,6 +57,7 @@ fun EmptyScreen( Column( modifier = modifier .fillMaxSize() + .verticalScroll(rememberScrollState()) .padding(horizontal = 24.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, @@ -66,7 +70,9 @@ fun EmptyScreen( Text( text = message, - modifier = Modifier.paddingFromBaseline(top = 24.dp).secondaryItemAlpha(), + modifier = Modifier + .paddingFromBaseline(top = 24.dp) + .secondaryItemAlpha(), style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Center, ) @@ -74,14 +80,10 @@ fun EmptyScreen( if (!actions.isNullOrEmpty()) { Row( modifier = Modifier - .padding( - top = 24.dp, - start = 24.dp, - end = 24.dp, - ), + .padding(top = 24.dp), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), ) { - actions.forEach { + actions.fastForEach { ActionButton( modifier = Modifier.weight(1f), title = stringResource(it.stringResId), diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt index ca538a8660..fb8e139966 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt @@ -4,14 +4,25 @@ import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.isImeVisible import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.alpha +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.platform.LocalFocusManager import tachiyomi.presentation.core.components.material.SecondaryItemAlpha fun Modifier.selectedBackground(isSelected: Boolean): Modifier = composed { @@ -52,3 +63,56 @@ fun Modifier.runOnEnterKeyPressed(action: () -> Unit): Modifier = this.onPreview else -> false } } + +/** + * For TextField on AppBar, this modifier will request focus + * to the element the first time it's composed. + */ +fun Modifier.showSoftKeyboard(show: Boolean): Modifier = if (show) { + composed { + val focusRequester = remember { FocusRequester() } + var openKeyboard by rememberSaveable { mutableStateOf(show) } + LaunchedEffect(focusRequester) { + if (openKeyboard) { + focusRequester.requestFocus() + openKeyboard = false + } + } + + Modifier.focusRequester(focusRequester) + } +} else { + this +} + +/** + * For TextField, this modifier will clear focus when soft + * keyboard is hidden. + */ +fun Modifier.clearFocusOnSoftKeyboardHide( + onFocusCleared: (() -> Unit)? = null, +): Modifier = composed { + var isFocused by remember { mutableStateOf(false) } + var keyboardShowedSinceFocused by remember { mutableStateOf(false) } + if (isFocused) { + val imeVisible = WindowInsets.isImeVisible + val focusManager = LocalFocusManager.current + LaunchedEffect(imeVisible) { + if (imeVisible) { + keyboardShowedSinceFocused = true + } else if (keyboardShowedSinceFocused) { + focusManager.clearFocus() + onFocusCleared?.invoke() + } + } + } + + Modifier.onFocusChanged { + if (isFocused != it.isFocused) { + if (isFocused) { + keyboardShowedSinceFocused = false + } + isFocused = it.isFocused + } + } +} diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt index 55552d78c1..b842952e8f 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Scrollbar.kt @@ -67,6 +67,7 @@ import androidx.compose.ui.util.fastSumBy import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.sample import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX /** @@ -206,10 +207,12 @@ private fun Modifier.drawScrollbar( val alpha = remember { Animatable(0f) } LaunchedEffect(scrolled, alpha) { - scrolled.collectLatest { - alpha.snapTo(1f) - alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) - } + scrolled + .sample(100) + .collectLatest { + alpha.snapTo(1f) + alpha.animateTo(0f, animationSpec = FadeOutAnimationSpec) + } } val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt index 3182a91959..ce81fefe81 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/AnimeSource.kt @@ -7,12 +7,12 @@ import eu.kanade.tachiyomi.util.awaitSingle import rx.Observable /** - * A basic interface for creating a source. It could be an online source, a local source, etc... + * A basic interface for creating a source. It could be an online source, a local source, etc. */ interface AnimeSource { /** - * Id for the source. Must be unique. + * ID for the source. Must be unique. */ val id: Long @@ -46,9 +46,9 @@ interface AnimeSource { ) fun fetchEpisodeList(anime: SAnime): Observable> = throw IllegalStateException("Not used") - // TODO: remove direct usages on this method /** - * Returns an observable with the list of videos a episode has. + * Returns an observable with the list of videos a episode has. Videos should be returned + * in the expected order; the index is ignored. * * @param episode the episode. */ @@ -75,7 +75,8 @@ interface AnimeSource { } /** - * [1.x API] Get the list of videos a episode has. + * [1.x API] Get the list of videos a episode has. Videos should be returned + * in the expected order; the index is ignored. */ @Suppress("DEPRECATION") suspend fun getVideoList(episode: SEpisode): List