Compare commits

..

135 Commits

Author SHA1 Message Date
Jobobby04 509ace1a38 Revert "Reapply "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)""
This reverts commit 9c01119d24.

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
2026-04-08 15:02:37 -04:00
Jobobby04 170358f88e Revert "Reapply "Fix cache invalidation isn't done at startup (#2970)""
This reverts commit c17e9573b7.
2026-04-08 14:52:15 -04:00
Jobobby04 5c14879c12 Fix build 2026-04-06 16:25:25 -04:00
ArthurKun 1682962703 Add gradle.properties to build-logic synced with root gradle.properties (#3159)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-04-06 16:22:14 -04:00
Mend Renovate a1daf53188 Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.1 (#3173) 2026-04-06 16:03:22 -04:00
Mend Renovate b0bdee8c72 Update dependency io.kotest:kotest-assertions-core to v6.1.10 (#3172) 2026-04-06 16:03:16 -04:00
MajorTanya 72c091d317 Add informative error for unapproved MAL titles (#3155)
MAL has a concept of "titles waiting for approval". These titles
cannot be added to user lists, but they do show up on the website and
crucially, in search results.

However, trying to add such an "unapproved" title will return a 400
error response with the error "invalid_content".

Previously, the awaitSuccess() call would mean the generic "HTTP 400"
toast would be shown. Now, a dedicated informative error message is
shown instead.
# Conflicts:
#	CHANGELOG.md
2026-04-06 16:02:51 -04:00
Mend Renovate 1cc796a62e Update dependency androidx.compose:compose-bom-beta to v2026.03.01 (#3147) 2026-04-06 16:02:30 -04:00
Mend Renovate cd40c0fb32 Update dependency androidx.work:work-runtime to v2.11.2 (#3146) 2026-04-06 16:02:15 -04:00
Christos 88edd18d02 Use plural forms for update error notification (#3090)
* Use plural forms for update error notification

Replace the hardcoded 'X update(s) failed' string with Android
plural resources so the notification correctly shows '1 update failed'
vs '2 updates failed', enabling proper localization.

Fixes #3051

* Address review: rename plural key and remove comment

Rename plural key to notification_update_errors to avoid potential
conflicts with existing translated string keys. Remove leftover
comment from strings.xml.

* Revert key rename per review
2026-04-06 16:02:10 -04:00
Mend Renovate ba9d010f78 Update dependency io.kotest:kotest-assertions-core to v6.1.9 (#3137) 2026-04-06 16:02:02 -04:00
Mend Renovate 1ed375968e Update dependency io.kotest:kotest-assertions-core to v6.1.8 (#3132) 2026-04-06 16:01:55 -04:00
MajorTanya 9e525515ea Address Gradle warning about Task.project (#3118)
Apparently, this is all that's needed to replace the forbidden
`Task.project` accessor, which would be an error in Gradle v10 (it's
been deprecated for a while)

This will also allow us to use the Configuration Cache if we wanted
to.
2026-04-06 16:01:47 -04:00
AntsyLich bd7201cfb9 Fix app crash on startup on some Android TV
Why do you guys even exist

# Conflicts:
#	CHANGELOG.md
2026-04-06 16:01:40 -04:00
AntsyLich d9ca2b69e8 Fix occasional crash when mass installing/uninstalling extension using PackageManager
# Conflicts:
#	CHANGELOG.md
2026-04-06 16:01:20 -04:00
AntsyLich 925fb118af Potentially fix 'database is locked' crash
# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt
2026-04-06 15:59:05 -04:00
AntsyLich 4552221020 Fix wrong exception being caught after 8c480c6355 migration
`android.database.sqlite.SQLiteException` instead of `android.database.SQLException`

# Conflicts:
#	CHANGELOG.md
2026-04-06 15:54:09 -04:00
MajorTanya 24b66b7030 Address CancellableContinuation.resume deprecation (#3115)
According to the source docs, this has been a warning since
kotlinx.coroutines 1.9.0.
2026-04-06 15:52:45 -04:00
Mend Renovate 58decd076c Update Gradle to v9 (#2333) 2026-04-06 15:50:18 -04:00
AntsyLich a7d93ae751 Cleanup and rework build logic (#3113)
# Conflicts:
#	app/build.gradle.kts
#	app/shortcuts.xml
#	app/src/main/res/xml/shortcuts.xml
#	app/src/main/shortcuts.xml
#	build.gradle.kts
#	buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt
#	core/archive/build.gradle.kts
#	core/common/build.gradle.kts
#	gradle/build-logic/src/main/kotlin/mihon/gradle/BuildConfig.kt
#	settings.gradle.kts
#	source-api/build.gradle.kts
#	source-local/build.gradle.kts
#	telemetry/build.gradle.kts
2026-04-06 15:50:09 -04:00
Weblate (bot) d225b7c586 Translations update from Hosted Weblate (#3003)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/be/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/be/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ta/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: A <ogloppi@mailbox.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Kehribar <103407696+dpentx@users.noreply.github.com>
Co-authored-by: Lucas Correia <anicetoclucas@gmail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: lilp <felipegabriel.avila6@gmail.com>
Co-authored-by: nadevko <ormak@protonmail.com>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) <coool@mail.lv>
Co-authored-by: 안세훈 <on9686@gmail.com>
# Conflicts:
#	i18n/src/commonMain/moko-resources/be/strings.xml
2026-04-06 15:27:10 -04:00
AntsyLich 4c51d01236 Merge and cleanup version catalogs (#3103)
# Conflicts:
#	app/build.gradle.kts
#	core/archive/build.gradle.kts
#	gradle/libs.versions.toml
#	i18n/build.gradle.kts
#	settings.gradle.kts
#	source-api/build.gradle.kts
2026-04-06 15:26:05 -04:00
Mend Renovate 4d46e84f54 Update dependency com.google.firebase:firebase-bom to v34.11.0 (#3094) 2026-04-06 15:06:15 -04:00
AntsyLich af51607053 Replace preference getter functions with properties (#3091)
# Conflicts:
#	app/src/main/java/eu/kanade/domain/source/interactor/GetEnabledSources.kt
#	app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt
#	app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
#	app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt
#	app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt
#	app/src/main/java/eu/kanade/presentation/reader/settings/ReadingModePage.kt
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
#	app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
#	app/src/main/java/eu/kanade/tachiyomi/source/SourceExtensions.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/SecureActivityDelegate.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
#	app/src/main/java/mihon/feature/migration/list/MigrationListScreenModel.kt
#	core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt
#	domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt
2026-04-06 15:06:05 -04:00
Leodyver Semilla 1c8e6dcd6f Fix extension actions disappearing after installing and uninstalling in same session (#3049)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
# Conflicts:
#	CHANGELOG.md
2026-04-06 14:20:49 -04:00
AntsyLich 7a398dabba Make retry in reader redownload image (#3089)
# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt
2026-04-06 14:18:57 -04:00
Leodyver Semilla b7fcf7ccda Fix WebView JavaScript dialogs popup after screen is closed (#3041)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
# Conflicts:
#	CHANGELOG.md
2026-04-06 14:13:39 -04:00
Leodyver Semilla acbda604cc MangaUpdates API content-type heade (#3021)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
# Conflicts:
#	CHANGELOG.md
2026-04-06 14:13:18 -04:00
Leodyver Semilla 97f3dd3b25 Fix tracker-induced duplicate key crash in duplicate detection (#3040)
# Conflicts:
#	CHANGELOG.md
2026-04-06 14:12:59 -04:00
Mend Renovate 60758f89ed Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.4.0 (#3086) 2026-04-06 14:12:41 -04:00
KaiserBh e96895345e feat(sync): prevent deleted "ghost chapters" from reappearing during sync. (#1575)
* feat(sync): prevent deleted "ghost chapters" from reappearing during sync.

- Pass lastSyncTime down to mergeChapters in SyncService.kt.
- Apply timestamp-based tombstoning logic to chapter merging. When a chapter is missing from either the local or remote backup, its `lastModifiedAt` timestamp is checked against the device's last sync time.
- Ensure that chapters deleted on one device (or removed by a source) are recognized as deletions and dropped from the merged backup, rather than being erroneously restored as "new" chapters on subsequent syncs.

* chore: change timestamp to use duration-based calculations

* chore: spotless
2026-04-06 13:08:30 -04:00
MediocreLegion eec1236b8b fix(delegate): migrate NH to the v2 api (#1581)
* fix(delegate): migrate NH to the v2 api

* remove extra comment

* remove redundant data

* linting

* Code cleanup

---------

Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
2026-04-03 12:59:13 -04:00
Weblate (bot) ee1e783126 Translations update from Hosted Weblate (#1577)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translation: Mihon/TachiyomiSY

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: ZenVinny <atdenada@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-04-03 12:50:27 -04:00
renovate[bot] f3ab39cb1f Update dependency net.zetetic:sqlcipher-android to v4.14.1 (#1583)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-03 12:50:00 -04:00
KaiserBh ba75395648 refactor: improve sync merging categories (#1559)
* feat: Add versioning to categories

* feat: use random UID for categories.

For legacy and migration we should assign uid on insert, and modify existing one as well in the migration.

* feat: sync category metadata

Add version, uid and lastModifiedAt fields to Category model to allow syncing.

* chore: fix category merging logic

Improve the category merging logic by matching using UIDs first, with a fallback to matching by name for legacy remote categories.

Previously, categories were only matched by name, which could lead to incorrect merges if names were changed. This change ensures more accurate synchronization by prioritizing the unique identifier. Conflict resolution is now based on the `version` field, and logging has been added for better visibility into the merging process.

* refactor: prioritize UID when restoring categories

If a category with the same UID exists, update it instead of creating a new one. Fallback to matching by name if no UID match is found.

* chore: add isSyncing flag like before.

This make sure the version is consistent, and it's not accidentally appended by the trigger, if it does then one device will always be ahead, than previous, and they need to make multiple changes to increase the version.

* Apply suggestion from @jobobby04

Use SY specific numbers(601, 602 for now)

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>

* chore: commit review, re-order.

* chore: surround changes in // SY --> // SY <--

* refactor: fallback to existing category UID if backup UID is 0 during restore.

when dealing with old backups (backups created before we added UIDs). In those old backups, backupCategory.uid defaults to 0.
If a user restored an old backup, it would match by name, and then overwrite the newly generated local UID with 0. This would break the synchronization.

* refactor: change to 6xx

* feat: improve sync reliability for categories and settings

- Refactor `mergeCategoriesLists` to correctly match categories by name when UID matching fails, ensuring better reconciliation across devices.
- Fix a bug in category merging where multiple categories with UID 0 (common for non-synced items) caused data loss.
- Update `SyncManager` to detect changes in categories, sources, preferences, saved searches, and extension repos, ensuring they synchronize even when the library favorites haven't changed.
- Convert `BackupCategory` and `BackupExtensionRepos` to data classes to support robust content-aware comparison during the sync process.
- Fix data loss in `mergeSourcesLists`, `mergePreferencesLists`, and `mergeSavedSearchesLists` by retaining local versions when conflicting with remote data.

* fix(sync): properly sync category deletions across devices

Previously, the sync system could not distinguish between a category that was deleted locally and a new category created on another device, causing deleted categories to be restored from the remote backup.

- Update `SyncService` to use `lastSyncTimestamp` to deduce if a missing local category was deleted (if modified before last sync) or newly created remotely (if modified after).
- Update `SyncManager` to explicitly delete local categories that are absent from the merged remote backup, propagating deletions to other devices.
- Fix `RestoreOptions` in `SyncManager` to respect the user's sync preferences instead of hardcoding `categories = true`.

* chore: change it to 6xx and not 600.

* chore: don't need to change this.

* chore: use kotlin time duration units

---------

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2026-04-03 12:49:37 -04:00
Weblate (bot) fe0b14ab97 Translations update from Hosted Weblate (#1561)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/be/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ta/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/be/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ta/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Kehribar <103407696+dpentx@users.noreply.github.com>
Co-authored-by: Lucas Correia <anicetoclucas@gmail.com>
Co-authored-by: lilp <felipegabriel.avila6@gmail.com>
Co-authored-by: nadevko <ormak@protonmail.com>
Co-authored-by: தமிழ்நேரம் <tamilneram247@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-03-18 19:55:58 -04:00
renovate[bot] 91d2140288 Update koin to v4.2.0 (#1569)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 19:47:20 -04:00
renovate[bot] 0417969dd6 Update dependency net.zetetic:sqlcipher-android to v4.14.0 (#1567)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-18 19:47:08 -04:00
AntsyLich 5d8d2ce48a Switch to AndroidX bundled sqlite driver (#3082)
# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt
2026-03-18 19:45:20 -04:00
Mend Renovate b15277f134 Update paging.version to v3.4.2 (#3063) 2026-03-18 19:21:01 -04:00
Mend Renovate 76ca27f681 Update kotlin monorepo to v2.3.20 (#3074) 2026-03-18 19:20:57 -04:00
Mend Renovate 56923c76d4 Update sqldelight to v2.3.2 (#3077) 2026-03-18 19:20:53 -04:00
MajorTanya 32e19736b9 Address bundleOf deprecation (#3073) 2026-03-18 19:20:48 -04:00
Mend Renovate 11b01b2771 Update sqldelight to v2.3.1 (#3071)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-18 19:20:45 -04:00
Mend Renovate 460ff13e54 Update dependency io.kotest:kotest-assertions-core to v6.1.7 (#3062) 2026-03-18 19:20:42 -04:00
Mend Renovate 57f77c8105 Update moko to v0.26.1 (#3068) 2026-03-18 19:20:39 -04:00
Mend Renovate a2eb22964a Update dependency com.squareup.okio:okio to v3.17.0 (#3070) 2026-03-18 19:20:36 -04:00
Mend Renovate 7158bae26a Update dependency androidx.activity:activity-compose to v1.13.0 (#3065) 2026-03-18 19:20:32 -04:00
Mend Renovate 807ce846d5 Update dependency androidx.core:core-ktx to v1.18.0 (#3067) 2026-03-18 19:20:29 -04:00
Mend Renovate 0b68f2c62a Update dependency androidx.compose:compose-bom to v2026.03.00 (#3066) 2026-03-18 19:20:26 -04:00
AntsyLich b7d6cc8dd0 Add installation id for feature flags (#3052)
# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
#	app/src/main/java/mihon/core/migration/migrations/Migrations.kt
2026-03-18 19:20:17 -04:00
Mend Renovate 8b1fd30902 Update dependency androidx.compose:compose-bom to v2026.02.01 (#3009) 2026-03-18 19:09:37 -04:00
Mend Renovate aff43f3aeb Update dependency com.google.firebase:firebase-bom to v34.10.0 (#3006) 2026-03-18 19:09:29 -04:00
Mend Renovate 0047d2e5d8 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.3.0 (#3029) 2026-03-18 19:09:22 -04:00
Mend Renovate d87385f5b3 Update dependency com.materialkolor:material-kolor to v5.0.0-alpha07 (#3024) 2026-03-18 19:09:15 -04:00
AntsyLich c17e9573b7 Reapply "Fix cache invalidation isn't done at startup (#2970)"
This reverts commit d219c5e3bbcfb24c40fa69e40bff11b6fd81fd7f.

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt
2026-03-18 19:08:43 -04:00
AntsyLich 9c01119d24 Reapply "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)"
This reverts commit 1d7c838ae64e624d9dd0884722f0c6ae5d18e386.

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
#	app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
2026-03-18 19:06:16 -04:00
Jobobby04 bbc839e234 Lint 2026-02-27 22:44:01 -05:00
Jobobby04 917f20894b Bump version code 2026-02-27 22:08:28 -05:00
Jobobby04 3a3b719b8b Copy last page read in migrate 2026-02-27 22:07:59 -05:00
Jobobby04 1903437ecf Cleanup 2026-02-27 22:07:42 -05:00
Jobobby04 5c26bb3a52 Add recommended proguard rules 2026-02-27 22:07:31 -05:00
Jobobby04 07599ade3a Fix blank page on cloudflare guard 2026-02-27 13:49:26 -05:00
Jobobby04 0a9f36402b Remove migrate button from merged manga 2026-02-27 13:38:28 -05:00
Weblate (bot) d2b325cd02 Translations update from Hosted Weblate (#2997)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon

Co-authored-by: Filip Jaruška <filip.jaruska@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
(cherry picked from commit cc3f640faa7df9f2a64976d2639e6dfd60ebe213)
2026-02-27 13:33:34 -05:00
AntsyLich cdc64aceb7 Fix extension install/update stuck at pending (#3000)
Co-authored-by: p
(cherry picked from commit 84265febf3ce24d71994ced2b81215f858430d4e)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:33:28 -05:00
Mend Renovate 4bfd6e4026 Update dependency io.kotest:kotest-assertions-core to v6.1.4 (#2998)
(cherry picked from commit 171a06a8baccae7fb21d2c60150639941747d90c)
2026-02-27 13:33:01 -05:00
Weblate (bot) 50eebdf7d3 Translations update from Hosted Weblate (#2980)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translation: Mihon/Mihon

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: KraXen <dpelech1@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
(cherry picked from commit 565379779f82ee7dfdad45e251d82ff73dc3b7b2)
2026-02-27 13:32:56 -05:00
AntsyLich f843de28d7 Run automatic library updates even when connected to a VPN (#2996)
Co-authored-by: jeremiejig <3978761+jeremiejig@users.noreply.github.com>
(cherry picked from commit 039471427448347a1c12c39a488a1127a3ea1497)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
2026-02-27 13:32:48 -05:00
Cuong-Tran d250a9a680 Going back now first clears search query on browse extension tab (#2906)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 75b445fa8fd42b882266e27d9b979b22ca37d42a)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:30:07 -05:00
AntsyLich 4130db3920 Add all pages of adjacent chapters in the UI instead of only the first or last three (#2995)
(cherry picked from commit 0cc724108b4f29a3d1d33ac4666a14873460a657)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:29:44 -05:00
Mend Renovate f2cbff04ab Update dependency io.coil-kt.coil3:coil-bom to v3.4.0 (#2992)
(cherry picked from commit 5b85084d0a90b241fdf1d3ad018a27b49db15f12)
2026-02-27 13:29:19 -05:00
AntsyLich 061e9359e8 Fix migration dialog not showing for consecutive prompts from the same screen (#2994)
(cherry picked from commit 8b2d35f3068a3f9c5e1bba77ed825ae51531c91c)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:29:16 -05:00
AntsyLich 73258e9e05 Fix migration's selected sources order not preserved (#2993)
(cherry picked from commit 47816d4b218e6a62ef9fcd3097b6b0b8f2f95b17)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:28:55 -05:00
NarwhalHorns 73e4982ffb Utilize tracker for library duplicate detection (#2978)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 89bbdb17fb4ed1cbe99c14f389940e0f91093a10)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:28:38 -05:00
NarwhalHorns 185cd923c0 Add option for bookmarked chapters to download dropdown (#2891)
(cherry picked from commit 3c6f0f1697ccab055ee7af47da84b2161d406f0c)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
2026-02-27 13:28:16 -05:00
NarwhalHorns 3cfc53bf11 Optimize tracked library filter (#2977)
Co-authored-by: NarwhalHorns <onefailedgamer@gamil.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit ab214526c6f24466a0432b5c5c7d254a244cd958)

# Conflicts:
#	CHANGELOG.md
2026-02-27 13:21:29 -05:00
Weblate (bot) 1301acfdb7 Translations update from Hosted Weblate (#2843)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon

Co-authored-by: Ai Ai Ecchi <aiaiecchi9og@gmail.com>
Co-authored-by: Alexandre Dhooge <alexandre.dhooge@zaclys.net>
Co-authored-by: Cuong Tran <cuongtran.tm@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hualiang <642615676@qq.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Throw Away <throwawayacc4gulshan@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: clukki <nguyenhuuminh16911@gmail.com>
Co-authored-by: momoehab <momoehab@gmail.com>
Co-authored-by: yumekon <konrad.nowicki91@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
(cherry picked from commit 7c7d35e2c64d4f85b0db431fbe18408698af9c6e)
2026-02-27 13:21:06 -05:00
Cuong-Tran 9d9dbea48d Remove redundant userSelected from selection methods (#2976)
(cherry picked from commit 9867c160f781b52cd297f7b1202cc6963b375df0)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/updates/UpdatesUiItem.kt
2026-02-27 13:19:34 -05:00
Cuong-Tran c1df3eb1d0 Don't wrap an intent-chooser inside another intent-chooser (#2921)
(cherry picked from commit 20c899f2cd83fdd37dfc14d58ded6ae7581d3d65)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
2026-02-27 13:19:12 -05:00
Jobobby04 3154c97aee Fix some build warnings 2026-02-27 13:17:48 -05:00
Mend Renovate ffe1b160de Update moko to v0.26.0 (#2967)
(cherry picked from commit 27749fc583527ab1360594a69ef586a4311c93f5)
2026-02-27 13:10:37 -05:00
Mend Renovate 23272375b7 Update dependency com.google.firebase:firebase-bom to v34.9.0 (#2964)
(cherry picked from commit 2248bdac546914492b0adf75425915b899749900)
2026-02-27 13:10:30 -05:00
Mend Renovate 863b6ee784 Update dependency org.junit.jupiter:junit-jupiter to v6.0.3 (#2963)
(cherry picked from commit 42c7329a9e17ab15c0dc1c1f33eabf54286f9339)
2026-02-27 13:10:25 -05:00
Mend Renovate c4c8d4b9c3 Update dependency androidx.compose:compose-bom to v2026.02.00 (#2962)
(cherry picked from commit 5be5a4e81978954713da6f7faa24cc9956c823e9)
2026-02-27 13:10:18 -05:00
Mend Renovate b2bbbca585 Update dependency androidx.activity:activity-compose to v1.12.4 (#2959)
(cherry picked from commit b924d582f459835f533fa54badbe129eede4388f)
2026-02-27 13:10:12 -05:00
Mend Renovate df3b879cf6 Update kotlin monorepo to v2.3.10 (#2960)
(cherry picked from commit 6f601a18c5beb78a70a94471abc2960fb37199b4)
2026-02-27 13:10:05 -05:00
Mend Renovate 47c4f2cc8c Update paging.version to v3.4.1 (#2961)
(cherry picked from commit 1ff9a5625c442be1c9504df0f0eabdaf24aa2fda)
2026-02-27 13:10:00 -05:00
Luca Auer 905a1c1230 Add missing indexes to improve database query performance (#2950)
(cherry picked from commit cae9fbf3213987e7d263845431dfac10a2ecb3b0)

# Conflicts:
#	data/src/main/sqldelight/tachiyomi/migrations/10.sqm
2026-02-27 13:09:55 -05:00
Mend Renovate bcaf7f6415 Update dependency io.kotest:kotest-assertions-core to v6.1.3 (#2939)
(cherry picked from commit d17976c91068944be1a5cba9a959c3763f589590)
2026-02-27 13:02:50 -05:00
Mend Renovate 4639b3ecc3 Update dependency com.materialkolor:material-kolor to v5.0.0-alpha06 (#2938)
(cherry picked from commit b609166702e0788160a557fa1470c6dd8e585169)
2026-02-27 13:02:44 -05:00
MajorTanya 2034971cc0 Clean up some build warnings (#2929)
* Replace deprecated rememberPlainTooltipPositionProvider

* Remove superfluous when branch

This when is marked as exhaustive.

* Replace deprecated LibrariesContainer call

AboutLibraries now wants us to produce the libraries ourselves.

* Replace deprecated ClipboardManager with Clipboard

Clipboard uses suspend functions, hence the coroutine scope addition.

* Use multi-dollar strs to simplify GraphQL queries

These have been available since Kotlin 2.1.

* Remove various redundant casts & conversions

- WebViewScreenContent: loadingState is in the LoadingState.Loading
  branch, no need to cast at all
- Bangumi: username is not modified, make val
- Kavita: token is already a String
- PagerViewerAdapter: insertPageLastPage is already null-checked
- PagerViewerAdapter: use reified filterIsInstance
- ReaderViewModel: chapter IDs are already Longs
- CloudflareInterceptor: webview is smart-cast to non-null here

* Replace deprecated MenuAnchorType

Literally just a typealias for ExposedDropdownMenuAnchorType anyway.

* OptimizeNonSkippingGroups is enabled by default

* Suppress shadowing warning

This is explicitly intentional according to the KDocs.

* Migrate Context Receivers to Context Parameters

Requires changing the compiler arg, but that is part of the migration:

https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters

Apparently, the only visible change is that names are required now.
"_" can be used for anonymous context parameters.

* Fix expression bodies with explicit return

Naming conflict resolved by aliasing.

From 2.4/2.5 onward, these will only be allowed with explicit return
types, or have to be turned into a block body. I opted for the latter
since the function is reasonably dense already.

see: https://youtrack.jetbrains.com/issue/KTLC-288

* Suppress deprecation of non-AutoMirrored icons

We use these arrows for navigation in the Upcoming screen.
I strongly doubt the AutoMirrored versions would make sense for our
use-case.

* Explicitly opt-in to new annotation default rules

affects the following annotated value-parameters:
- Preference.SliderPreference.steps (`@IntRange`)
- ReaderViewModel.State.brightnessOverlayValue (`@IntRange`)
- ReadingMode.iconRes (`@DrawableRes`)
- MigrationListScreenModel.Dialog.Progress.progress (`@FloatRange`)

see: https://youtrack.jetbrains.com/issue/KT-73255
see: https://github.com/Kotlin/KEEP/blob/change-defaulting-rule/proposals/annotation-target-in-properties.md

Warning message was the following:

    This annotation is currently applied to the value parameter only, but in the future it will also be applied to field.
    - To opt in to applying to both value parameter and field, add '-Xannotation-default-target=param-property' to your compiler arguments.
    - To keep applying to the value parameter only, use the '@param:' annotation target.

(cherry picked from commit b543bc089a442c5e93b0fb6c83bc4037740b1eb5)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt
#	core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt
#	core/common/src/main/kotlin/mihon/core/common/archive/ArchiveInputStream.kt
2026-02-27 13:02:31 -05:00
Mend Renovate bb8698b2a6 Update dependency androidx.compose:compose-bom to v2026.01.01 (#2913)
(cherry picked from commit 100cea0757c930b153e50ca2ba52e4c06f14800d)
2026-02-27 12:52:37 -05:00
Mend Renovate cd69b09dd0 Update dependency androidx.work:work-runtime to v2.11.1 (#2914)
(cherry picked from commit e092b4208a0f8476c81ed58a2b0efd3a7c5996cf)
2026-02-27 12:52:29 -05:00
Mend Renovate 462b2164e8 Update paging.version to v3.4.0 (#2916)
(cherry picked from commit 1bf4eff931781eff11e3c1d59ca3747ffb069203)
2026-02-27 12:52:23 -05:00
Mend Renovate fb1a4ad828 Update dependency androidx.activity:activity-compose to v1.12.3 (#2917)
(cherry picked from commit b70edfac58e8e5722a51be398212ac025b818258)
2026-02-27 12:52:17 -05:00
Mend Renovate 3bd89cee26 Update markdown to v0.39.2 (#2923)
(cherry picked from commit 9c0eebb55ceb6a7edc50e414b4be19faf256bcda)
2026-02-27 12:52:11 -05:00
Mend Renovate 6f43e98fff Update dependency io.kotest:kotest-assertions-core to v6.1.2 (#2908)
(cherry picked from commit f6f1d13addea504ff7ed6ec87276d1cfdfcf2013)
2026-02-27 12:52:06 -05:00
Mend Renovate 6feeb4b1ee Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.1 (#2909)
(cherry picked from commit 31a9ff261e5e195fa2e27615bae0a86956a3c90a)
2026-02-27 12:52:00 -05:00
Mend Renovate fcfe750fcf Update dependency io.mockk:mockk to v1.14.9 (#2904)
(cherry picked from commit c0a1203541931a9090e6769acdb596c9ea4a011b)
2026-02-27 12:51:55 -05:00
Cuong-Tran 6e314e3643 Fix Add Repo input not taking up the full dialog width (#2816)
(cherry picked from commit 82ffc8efa69cb86229fe8b677f12942c80b88a15)

# Conflicts:
#	CHANGELOG.md
2026-02-27 12:51:48 -05:00
Mend Renovate 487ca49c11 Update serialization.version to v1.10.0 (#2879)
(cherry picked from commit d6e17b04c39c9d800c57d75e29714a51fbf902f8)
2026-02-27 12:51:27 -05:00
Mend Renovate 698abe8667 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.0 (#2892)
(cherry picked from commit e6c67003511b601d49bc175b83596568ddfe09d9)
2026-02-27 12:51:20 -05:00
Mend Renovate 13c9daf9a9 Update dependency io.kotest:kotest-assertions-core to v6.1.1 (#2893)
(cherry picked from commit ede2f56bc6531f69553d0f95cf422b7f6d15b90b)
2026-02-27 12:51:14 -05:00
Mend Renovate eb21454d6d Update Gradle to v8.14.4 (#2894)
(cherry picked from commit 2037ec45006b54da0e64b3e07bae4c87e261ee59)
2026-02-27 12:51:07 -05:00
Cuong-Tran 56347e6d52 Fix memoization in manga bottom action menus (#2886)
(cherry picked from commit 533a578bdb438fe4a23e069386a6616c7daedcc4)
2026-02-27 12:50:46 -05:00
MajorTanya 5c085a36e8 Reword download index message (#2874)
I'm tired of people thinking the current wording is an error. Improved wordings welcome, this was just my first decent guess.

(cherry picked from commit 05d90ea4d652cd7ead385ec954e0ae2dc332a012)

# Conflicts:
#	CHANGELOG.md
2026-02-27 12:50:38 -05:00
Mend Renovate 65ab676946 Update dependency io.kotest:kotest-assertions-core to v6.1.0 (#2870)
(cherry picked from commit 49c4d08b22b6a440b93b58bd78280807550d6198)
2026-02-27 12:50:19 -05:00
MajorTanya 1f51569a35 Add Filters to Updates screen (#2851)
* Add Filters to Updates screen

Behaves basically like the filters in the library:

- Unread: Show/Don't show unread chapters
- Downloaded: Show/Don't show downloaded chapters
- Started: Show/Don't show chapters that have some progress but aren't
  fully Read
- Bookmarked: Show/Don't show chapters that have been bookmarked

Started behaves differently from its Library counterpart because the
actual manga data is not available at this point in time and I thought
calling getManga for each entry without caching would be a pretty bad
idea.

I have modelled this closely on the filter control flow in the
Library, but I'm sure this can be simplified/adjusted in some way.

* Move most filtering logic to SQL

Unread, Started, and Bookmarked filters are now part of the SQL query.

Download state cannot be filtered in the database so it remains in
Kotlin.

Because the Downloaded filter has to be run in Kotlin, the combine
flow uses the preferences flow twice, once to get the SQL query params
and once for the Kotlin filters (only Downloaded at this time).

* Add "Hide excluded scanlators" to update filters

Based on the work done in #1623 but integrated with the other filters
in this PR. Added the user as a co-author for credit.

Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com>

---------

Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com>
(cherry picked from commit bbe9aa8561360f030072fbc49f79748e71c6535e)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt
#	data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt
#	data/src/main/sqldelight/tachiyomi/migrations/9.sqm
#	domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt
2026-02-27 12:35:44 -05:00
Jobobby04 b0d6e16ca3 Fix build 2026-02-27 11:51:08 -05:00
Mend Renovate 85cf54ccc8 Update dependency com.google.firebase:firebase-bom to v34.8.0 (#2856)
(cherry picked from commit 13975d6f7eea21b3ba4be31736038ef2f855bfea)
2026-02-27 11:46:14 -05:00
Mend Renovate 602df5a729 Update dependency androidx.compose:compose-bom to v2026 (#2853)
(cherry picked from commit e659e90c26814c9a29bf668545b4f10294f3c6b9)
2026-02-27 11:46:08 -05:00
Mend Renovate c8102836ce Update dependency com.materialkolor:material-kolor to v5.0.0-alpha05 (#2849)
(cherry picked from commit 99e6fa3c06e1dc24255a73aa5741a72c82d94d91)
2026-02-27 11:46:02 -05:00
Mend Renovate e641575941 Update markdown to v0.39.1 (#2850)
(cherry picked from commit cf13012629571e2097796b50df238f69e62843b3)
2026-02-27 11:45:49 -05:00
NGB-Was-Taken 83afcee4d1 Fix crash when trying to install/update extensions while shizuku isn't running (#2837)
(cherry picked from commit 4ce249c1a0ee9f3a20d91214fd09145e0924b2e4)

# Conflicts:
#	CHANGELOG.md
2026-02-27 11:45:04 -05:00
MajorTanya 2102e0594e Fix nullability of MAL authors breaking search (#2834)
One of these days I'll get through a tracker change without
nullability problems...

(cherry picked from commit edcf84d9022e7436606a0b8c493c1035888ac60a)
2026-02-27 11:44:38 -05:00
Constantin Piber 14c91da6b3 Add a small increment to chapter number before comparison to fix progress sync issues for Suwayomi (#2675)
Due to a `Float->Double->Float` conversion somewhere inside Mihon, the
tracker sees 2.1 as 2.0999999046325684, which means this filter ignores
the 2.1 chapter (which we just tried to mark as read). This small
epsilon is small enough to never bother any serious usage, but large
enough to ignore any such conversion errors.

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit bd5c4d48f980d2d3dcc1112fe499dba17ef8e507)

# Conflicts:
#	CHANGELOG.md
2026-02-27 11:44:26 -05:00
MajorTanya 46c1c6463a Add authors/artists to MAL search results (#2833)
(cherry picked from commit 51b3ab3fd19bdf6a7c3bd2085104392a9c412622)

# Conflicts:
#	CHANGELOG.md
2026-02-27 11:44:10 -05:00
Weblate (bot) 89a521b836 Translations update from Hosted Weblate (#2806)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Ahmed TOUCHANE <ahmedtouchane0@gmail.com>
Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Hiroshi <borlonjhayron1119@gmail.com>
Co-authored-by: Luis Antonio <getcyonic+zaorinu@gmail.com>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
(cherry picked from commit e6f72000ba62302c3e4817a5f17057e5a8d7eafc)
2026-02-27 11:43:50 -05:00
MajorTanya 65c6ed21ab Optimise MAL search queries by ~11x (#2832)
Previously, the app made one request for the search, and then fired
off 1 request per search result to obtain additional data, such as
each title's synopsis, etc.

However, MAL's search allows field selection during the initial query,
which will return all the data in that first response, avoiding the
massive bunch of requests (and alleviating some pressure on MAL from
our userbase).

By combining the selected fields into one constant, I was able to also
get rid of the MALUserListSearch entirely because it was redundant.
This allows for a unified MALManga->TrackSearch helper, further
reducing complexity.

I got to my "11x" improvement because on page of search results has 10
elements, and this change turns 11 (1+10 for results) requests into 1.

(cherry picked from commit 9bf2d78a421213b1885456f5b54c3286edc539e1)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt
2026-02-27 11:42:54 -05:00
Mend Renovate 1b911e7e15 Update dependency org.junit.jupiter:junit-jupiter to v6.0.2 (#2830)
(cherry picked from commit 89c4e3bb39b7450a9aedcf9d720c74c04cd560ff)
2026-02-27 11:41:41 -05:00
Mend Renovate 0535e41051 Update dependency org.jsoup:jsoup to v1.22.1 (#2826)
(cherry picked from commit 47fe792ddc5a342ecc4685d57143cfb40f0682f7)
2026-02-27 11:41:32 -05:00
AntsyLich 3fc802f837 Remember descriptionAnnotator across composition
Closes #2510

Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
(cherry picked from commit 906d6f3cdbd6f7168d9cdea0c3eb8f9c663a09c2)
2026-02-27 11:41:17 -05:00
AntsyLich 976b5cc03e Cleanup extension screen search query predicate
(cherry picked from commit e059190fabe3cbe8498fc3cec7e39b0350f3c289)
2026-02-27 11:41:10 -05:00
AntsyLich a9fe971337 Switch to M3E ExtendedFloatingActionButton
(cherry picked from commit a39b5a56e853a9c11c0984b0385a2e60b0addc3a)
2026-02-27 11:40:06 -05:00
AntsyLich 5d1dbcb390 Switch to MaterialExpressiveTheme
(cherry picked from commit 3e6afee13b9fd5716f7f2d547b3edcd3e17915db)
2026-02-27 11:28:24 -05:00
AntsyLich 8d11ef3244 Use materilalKolor for monet compat color scheme
(cherry picked from commit 9a11ec8ead41cb7199e10f3c7464790a0bd3b1ad)
2026-02-27 11:28:18 -05:00
Weblate (bot) 724a61f513 Translations update from Hosted Weblate (#1536)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Champ0999 <champ0999@users.noreply.hosted.weblate.org>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Erick Alejandro <erickux@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Luis Antonio <getcyonic+zaorinu@gmail.com>
Co-authored-by: Metin <durmus38metin@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NormalRandomPeople <normal.scribe833@silomails.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: momoehab <momoehab@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-02-27 11:25:48 -05:00
renovate[bot] 724c774dc9 Update dependency net.zetetic:sqlcipher-android to v4.13.0 (#1544)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 11:21:21 -05:00
renovate[bot] 29e0b2e4a5 Update actions/upload-artifact action to v7 (#1557)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-27 11:21:12 -05:00
KaiserBh 2776e41127 feat: Add sync events to SyncYomi (#1558)
* feat: Add sync events to SyncYomi

Now it will send the events back to `SyncYomi` server and then forward those to the notifications services that are enabled, such as discord, telegram, and etc.

* chore: fix build error.
2026-02-27 11:20:59 -05:00
Jobobby04 af1f77418f This is SY not Mihon Crashing 2026-02-13 22:41:04 -05:00
MajorTanya c1df5da062 This is SY not Mihon (#1549) 2026-01-31 11:24:37 -05:00
Jobobby04 f8f645772d Crashfix
Co-authored-by: name <arkon@users.noreply.github.com>
2026-01-01 12:26:13 -05:00
Jobobby04 b1e6fa65d6 Or 2025-12-26 23:31:31 -05:00
426 changed files with 6521 additions and 3807 deletions
+1 -1
View File
@@ -100,5 +100,5 @@ body:
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have filled out all of the requested information in this form, including specific version numbers.
required: true required: true
- label: I understand that **Mihon does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions. - label: I understand that **TachiyomiSY does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
required: true required: true
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
run: ./gradlew spotlessCheck assembleDevDebug run: ./gradlew spotlessCheck assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: TachiyomiSY-${{ github.sha }}.apk name: TachiyomiSY-${{ github.sha }}.apk
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
+105 -84
View File
@@ -1,16 +1,19 @@
@file:Suppress("ChromeOsAbiSupport")
import mihon.buildlogic.getBuildTime import mihon.gradle.getBuildTime
import mihon.buildlogic.getCommitCount import mihon.gradle.getLatestCommitCount
import mihon.buildlogic.getGitSha import mihon.gradle.getLatestCommitSha
import mihon.gradle.tasks.ReplaceShortcutsPlaceholderTask
plugins { plugins {
id("mihon.android.application") alias(mihonx.plugins.android.application)
id("mihon.android.application.compose") alias(mihonx.plugins.compose)
alias(mihonx.plugins.spotless)
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
kotlin("plugin.serialization")
// id("com.github.zellius.shortcut-helper")
alias(libs.plugins.aboutLibraries) alias(libs.plugins.aboutLibraries)
alias(libs.plugins.kotlin.serialization)
id("com.github.ben-manes.versions") id("com.github.ben-manes.versions")
} }
@@ -21,42 +24,26 @@ if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
} }
} }
// shortcutHelper.setFilePath("./shortcuts.xml")
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
android { android {
namespace = "eu.kanade.tachiyomi" namespace = "eu.kanade.tachiyomi"
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 75 versionCode = 77
versionName = "1.12.0" versionName = "1.12.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"") buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
buildConfigField("boolean", "INCLUDE_UPDATER", "false") buildConfigField("boolean", "INCLUDE_UPDATER", "false")
ndk {
abiFilters += supportedAbis
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
splits {
abi {
isEnable = true
reset()
include(*supportedAbis.toTypedArray())
isUniversalApk = true
}
}
buildTypes { buildTypes {
named("debug") { named("debug") {
versionNameSuffix = "-${getCommitCount()}" versionNameSuffix = "-${getLatestCommitCount()}"
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
isPseudoLocalesEnabled = true isPseudoLocalesEnabled = true
} }
@@ -72,7 +59,7 @@ android {
isShrinkResources = true isShrinkResources = true
setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")) setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"") buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = true)}\"")
} }
create("benchmark") { create("benchmark") {
initWith(getByName("release")) initWith(getByName("release"))
@@ -90,6 +77,15 @@ android {
getByName("benchmark").res.srcDirs("src/debug/res") getByName("benchmark").res.srcDirs("src/debug/res")
} }
splits {
abi {
isEnable = true
isUniversalApk = true
reset()
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
}
flavorDimensions.add("default") flavorDimensions.add("default")
productFlavors { productFlavors {
@@ -106,21 +102,32 @@ android {
} }
packaging { packaging {
resources.excludes.addAll( jniLibs {
listOf( keepDebugSymbols += listOf(
"libandroidx.graphics.path",
"libarchive-jni",
"libconscrypt_jni",
"libimagedecoder",
"libquickjs",
"libsqlite3x",
)
.map { "**/$it.so" }
}
resources {
excludes += setOf(
"kotlin-tooling-metadata.json", "kotlin-tooling-metadata.json",
"META-INF/DEPENDENCIES",
"LICENSE.txt", "LICENSE.txt",
"META-INF/LICENSE", "META-INF/**/*.properties",
"META-INF/**/LICENSE.txt", "META-INF/**/LICENSE.txt",
"META-INF/*.properties", "META-INF/*.properties",
"META-INF/**/*.properties",
"META-INF/README.md",
"META-INF/NOTICE",
"META-INF/*.version", "META-INF/*.version",
), "META-INF/DEPENDENCIES",
"META-INF/LICENSE",
"META-INF/NOTICE",
"META-INF/README.md",
) )
} }
}
dependenciesInfo { dependenciesInfo {
includeInApk = false includeInApk = false
@@ -150,12 +157,14 @@ kotlin {
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi", "-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xannotation-default-target=param-property",
) )
} }
} }
@@ -175,96 +184,95 @@ dependencies {
implementation(projects.presentationWidget) implementation(projects.presentationWidget)
// Compose // Compose
implementation(compose.activity) implementation(libs.androidx.activity.compose)
implementation(compose.foundation) implementation(libs.androidx.compose.foundation)
implementation(compose.material3.core) implementation(libs.androidx.compose.material3)
implementation(compose.material.icons) implementation(libs.androidx.compose.materialIcons)
implementation(compose.animation) implementation(libs.androidx.compose.animation)
implementation(compose.animation.graphics) implementation(libs.androidx.compose.animationGraphics)
debugImplementation(compose.ui.tooling) debugImplementation(libs.androidx.compose.uiTooling)
implementation(compose.ui.tooling.preview) implementation(libs.androidx.compose.uiToolingPreview)
implementation(compose.ui.util) implementation(libs.androidx.compose.uiUtil)
implementation(androidx.interpolator) implementation(libs.androidx.interpolator)
implementation(androidx.paging.runtime) implementation(libs.androidx.paging.runtime)
implementation(androidx.paging.compose) implementation(libs.androidx.paging.compose)
implementation(libs.bundles.sqlite) implementation(libs.androidx.sqlite.bundled)
// SY --> // SY -->
implementation(sylibs.sqlcipher) implementation(sylibs.sqlcipher)
// SY <-- // SY <--
implementation(kotlinx.reflect) implementation(libs.kotlin.reflect)
implementation(kotlinx.immutables) implementation(libs.kotlinx.collections.immutable)
implementation(platform(kotlinx.coroutines.bom)) implementation(libs.bundles.kotlinx.coroutines)
implementation(kotlinx.bundles.coroutines)
// AndroidX libraries // AndroidX libraries
implementation(androidx.annotation) implementation(libs.androidx.annotation)
implementation(androidx.appcompat) implementation(libs.androidx.appCompat)
implementation(androidx.biometricktx) implementation(libs.androidx.biometric)
implementation(androidx.constraintlayout) implementation(libs.androidx.constraintLayout)
implementation(androidx.corektx) implementation(libs.androidx.core)
implementation(androidx.splashscreen) implementation(libs.androidx.coreSplashScreen)
implementation(androidx.recyclerview) implementation(libs.androidx.recyclerView)
implementation(androidx.viewpager) implementation(libs.androidx.viewPager)
implementation(androidx.profileinstaller) implementation(libs.androidx.profileInstaller)
implementation(androidx.bundles.lifecycle) implementation(libs.bundles.androidx.lifecycle)
// Job scheduling // Job scheduling
implementation(androidx.workmanager) implementation(libs.androidx.work)
// RxJava // RxJava
implementation(libs.rxjava) implementation(libs.rxJava)
// Networking // Networking
implementation(libs.bundles.okhttp) implementation(libs.bundles.okhttp)
implementation(libs.okio) implementation(libs.okio)
implementation(libs.conscrypt.android) // TLS 1.3 support for Android < 10 implementation(libs.conscrypt) // TLS 1.3 support for Android < 10
// Data serialization (JSON, protobuf, xml) // Data serialization (JSON, protobuf, xml)
implementation(kotlinx.bundles.serialization) implementation(libs.bundles.serialization)
// HTML parser // HTML parser
implementation(libs.jsoup) implementation(libs.jsoup)
// Disk // Disk
implementation(libs.disklrucache) implementation(libs.diskLruCache)
implementation(libs.unifile) implementation(libs.unifile)
// Preferences // Preferences
implementation(libs.preferencektx) implementation(libs.androidx.preference)
// Dependency injection // Dependency injection
implementation(libs.injekt) implementation(libs.injekt)
// Image loading // Image loading
implementation(platform(libs.coil.bom))
implementation(libs.bundles.coil) implementation(libs.bundles.coil)
implementation(libs.subsamplingscaleimageview) { implementation(libs.subsamplingScaleImageView) {
exclude(module = "image-decoder") exclude(module = "image-decoder")
} }
implementation(libs.image.decoder) implementation(libs.image.decoder)
// UI libraries // UI libraries
implementation(libs.material) implementation(libs.material)
implementation(libs.flexible.adapter.core) implementation(libs.flexibleAdapter)
implementation(libs.photoview) implementation(libs.photoView)
implementation(libs.directionalviewpager) { implementation(libs.directionalViewPager) {
exclude(group = "androidx.viewpager", module = "viewpager") exclude(group = "androidx.viewpager", module = "viewpager")
} }
implementation(libs.richeditor.compose) implementation(libs.composeRichEditor)
implementation(libs.aboutLibraries.compose) implementation(libs.aboutLibraries.compose)
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion) implementation(libs.composeMaterialMotion)
implementation(libs.swipe) implementation(libs.swipe)
implementation(libs.compose.webview) implementation(libs.composeWebview)
implementation(libs.compose.grid) implementation(libs.composeGrid)
implementation(libs.reorderable) implementation(libs.reorderable)
implementation(libs.bundles.markdown) implementation(libs.bundles.markdown)
implementation(libs.materialKolor)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)
@@ -285,10 +293,10 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher) testRuntimeOnly(libs.junit.platform.launcher)
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android) // debugImplementation(libs.leakCanary.android)
implementation(libs.leakcanary.plumber) implementation(libs.leakCanary.plumber)
testImplementation(kotlinx.coroutines.test) testImplementation(libs.kotlinx.coroutines.test)
// SY --> // SY -->
// Firebase (EH) // Firebase (EH)
@@ -316,6 +324,19 @@ dependencies {
} }
androidComponents { androidComponents {
onVariants { variant ->
val resSource = variant.sources.res ?: return@onVariants
val variantName = variant.name.replaceFirstChar { it.uppercase() }
val replaceShortcutsPlaceholderTask = tasks.register<ReplaceShortcutsPlaceholderTask>(
"replace${variantName}ShortcutPlaceholder",
) {
applicationId.set(variant.applicationId)
shortcutsFile.set(projectDir.resolve("src/main/shortcuts.xml"))
}
resSource.addGeneratedSourceDirectory(replaceShortcutsPlaceholderTask) { it.outputDir }
}
onVariants(selector().withFlavor("default" to "standard")) { onVariants(selector().withFlavor("default" to "standard")) {
// Only excluding in standard flavor because this breaks // Only excluding in standard flavor because this breaks
// Layout Inspector's Compose tree // Layout Inspector's Compose tree
@@ -325,6 +346,6 @@ androidComponents {
buildscript { buildscript {
dependencies { dependencies {
classpath(kotlinx.gradle) classpath(libs.kotlin.gradle)
} }
} }
+2
View File
@@ -299,3 +299,5 @@
-dontwarn org.ietf.jgss.GSSManager -dontwarn org.ietf.jgss.GSSManager
-dontwarn org.ietf.jgss.GSSName -dontwarn org.ietf.jgss.GSSName
-dontwarn org.ietf.jgss.Oid -dontwarn org.ietf.jgss.Oid
-dontwarn com.google.re2j.Matcher
-dontwarn com.google.re2j.Pattern
@@ -60,6 +60,7 @@ import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.interactor.SetSortModeForCategory import tachiyomi.domain.category.interactor.SetSortModeForCategory
import tachiyomi.domain.category.interactor.UpdateCategory import tachiyomi.domain.category.interactor.UpdateCategory
import tachiyomi.domain.category.repository.CategoryRepository import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId
import tachiyomi.domain.chapter.interactor.GetChapter import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -156,6 +157,7 @@ class DomainModule : InjektModule {
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) } addFactory { GetChapter(get()) }
addFactory { GetChaptersByMangaId(get()) } addFactory { GetChaptersByMangaId(get()) }
addFactory { GetBookmarkedChaptersByMangaId(get(), get(), get()) }
addFactory { GetChapterByUrlAndMangaId(get()) } addFactory { GetChapterByUrlAndMangaId(get()) }
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get(), get()) } addFactory { SetReadStatus(get(), get(), get(), get(), get()) }
@@ -9,19 +9,22 @@ import tachiyomi.i18n.MR
class BasePreferences( class BasePreferences(
val context: Context, val context: Context,
private val preferenceStore: PreferenceStore, preferenceStore: PreferenceStore,
) { ) {
fun downloadedOnly() = preferenceStore.getBoolean( val downloadedOnly: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("pref_downloaded_only"), Preference.appStateKey("pref_downloaded_only"),
false, false,
) )
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false) val incognitoMode: Preference<Boolean> = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore) val extensionInstaller: ExtensionInstallerPreference = ExtensionInstallerPreference(context, preferenceStore)
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false) val shownOnboardingFlow: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("onboarding_complete"),
false,
)
enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) { enum class ExtensionInstaller(val titleRes: StringResource, val requiresSystemPermission: Boolean) {
LEGACY(MR.strings.ext_installer_legacy, true), LEGACY(MR.strings.ext_installer_legacy, true),
@@ -30,9 +33,17 @@ class BasePreferences(
PRIVATE(MR.strings.ext_installer_private, false), PRIVATE(MR.strings.ext_installer_private, false),
} }
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "") val displayProfile: Preference<String> = preferenceStore.getString("pref_display_profile_key", "")
fun hardwareBitmapThreshold() = preferenceStore.getInt("pref_hardware_bitmap_threshold", GLUtil.SAFE_TEXTURE_LIMIT) val hardwareBitmapThreshold: Preference<Int> = preferenceStore.getInt(
"pref_hardware_bitmap_threshold",
GLUtil.SAFE_TEXTURE_LIMIT,
)
fun alwaysDecodeLongStripWithSSIV() = preferenceStore.getBoolean("pref_always_decode_long_strip_with_ssiv", false) val alwaysDecodeLongStripWithSSIV: Preference<Boolean> = preferenceStore.getBoolean(
"pref_always_decode_long_strip_with_ssiv",
false,
)
val installationId: Preference<String> = preferenceStore.getString(Preference.appStateKey("installation_id"), "")
} }
@@ -51,7 +51,7 @@ class SetReadStatus(
return@withNonCancellableContext Result.InternalError(e) return@withNonCancellableContext Result.InternalError(e)
} }
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) { if (read && downloadPreferences.removeAfterMarkedAsRead.get()) {
chaptersToUpdate chaptersToUpdate
.groupBy { it.mangaId } .groupBy { it.mangaId }
.forEach { (mangaId, chapters) -> .forEach { (mangaId, chapters) ->
@@ -176,7 +176,7 @@ class SyncChaptersWithSource(
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch } val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
.associate { it.chapterNumber to it.dateFetch } .associate { it.chapterNumber to it.dateFetch }
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get() val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead.get()
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW) .contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones // Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
@@ -12,7 +12,7 @@ class GetExtensionLanguages(
) { ) {
fun subscribe(): Flow<List<String>> { fun subscribe(): Flow<List<String>> {
return combine( return combine(
preferences.enabledLanguages().changes(), preferences.enabledLanguages.changes(),
extensionManager.availableExtensionsFlow, extensionManager.availableExtensionsFlow,
) { enabledLanguage, availableExtensions -> ) { enabledLanguage, availableExtensions ->
availableExtensions availableExtensions
@@ -15,7 +15,7 @@ class GetExtensionSources(
val isMultiLangSingleSource = val isMultiLangSingleSource =
isMultiSource && extension.sources.map { it.name }.distinct().size == 1 isMultiSource && extension.sources.map { it.name }.distinct().size == 1
return preferences.disabledSources().changes().map { disabledSources -> return preferences.disabledSources.changes().map { disabledSources ->
fun Source.isEnabled() = id.toString() !in disabledSources fun Source.isEnabled() = id.toString() !in disabledSources
extension.sources extension.sources
@@ -13,10 +13,10 @@ class GetExtensionsByType(
) { ) {
fun subscribe(): Flow<Extensions> { fun subscribe(): Flow<Extensions> {
val showNsfwSources = preferences.showNsfwSource().get() val showNsfwSources = preferences.showNsfwSource.get()
return combine( return combine(
preferences.enabledLanguages().changes(), preferences.enabledLanguages.changes(),
extensionManager.installedExtensionsFlow, extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow, extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow, extensionManager.availableExtensionsFlow,
@@ -14,11 +14,11 @@ class TrustExtension(
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean { suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet() val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}" val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get() return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions.get()
} }
fun trust(pkgName: String, versionCode: Long, signatureHash: String) { fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
preferences.trustedExtensions().getAndSet { exts -> preferences.trustedExtensions.getAndSet { exts ->
// Remove previously trusted versions // Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
@@ -27,6 +27,6 @@ class TrustExtension(
} }
fun revokeAll() { fun revokeAll() {
preferences.trustedExtensions().delete() preferences.trustedExtensions.delete()
} }
} }
@@ -15,9 +15,9 @@ class CreateSortTag(
return Result.TagExists return Result.TagExists
} }
val size = preferences.sortTagsForLibrary().get().size val size = preferences.sortTagsForLibrary.get().size
preferences.sortTagsForLibrary() += encodeTag(size, tag) preferences.sortTagsForLibrary += encodeTag(size, tag)
return Result.Success return Result.Success
} }
@@ -8,7 +8,7 @@ class DeleteSortTag(
) { ) {
fun await(tag: String) { fun await(tag: String) {
preferences.sortTagsForLibrary().set( preferences.sortTagsForLibrary.set(
(getSortTag.await() - tag).mapIndexed { index, s -> (getSortTag.await() - tag).mapIndexed { index, s ->
CreateSortTag.encodeTag(index, s) CreateSortTag.encodeTag(index, s)
}.toSet(), }.toSet(),
@@ -7,14 +7,14 @@ import tachiyomi.domain.library.service.LibraryPreferences
class GetSortTag(private val preferences: LibraryPreferences) { class GetSortTag(private val preferences: LibraryPreferences) {
fun subscribe(): Flow<List<String>> { fun subscribe(): Flow<List<String>> {
return preferences.sortTagsForLibrary().changes() return preferences.sortTagsForLibrary.changes()
.map(::mapSortTags) .map(::mapSortTags)
} }
fun await() = getSortTags(preferences).let(::mapSortTags) fun await() = getSortTags(preferences).let(::mapSortTags)
companion object { companion object {
fun getSortTags(preferences: LibraryPreferences) = preferences.sortTagsForLibrary().get() fun getSortTags(preferences: LibraryPreferences) = preferences.sortTagsForLibrary.get()
fun mapSortTags(tags: Set<String>) = tags.mapNotNull { fun mapSortTags(tags: Set<String>) = tags.mapNotNull {
val index = it.indexOf('|') val index = it.indexOf('|')
@@ -23,7 +23,7 @@ class ReorderSortTag(
val reorderedTag = reorderedTags.removeAt(currentIndex) val reorderedTag = reorderedTags.removeAt(currentIndex)
reorderedTags.add(newPosition, reorderedTag) reorderedTags.add(newPosition, reorderedTag)
preferences.sortTagsForLibrary().set( preferences.sortTagsForLibrary.set(
reorderedTags.mapIndexed { index, s -> reorderedTags.mapIndexed { index, s ->
CreateSortTag.encodeTag(index, s) CreateSortTag.encodeTag(index, s)
}.toSet(), }.toSet(),
@@ -44,7 +44,7 @@ class UpdateManga(
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db // if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
val title = val title =
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles().get())) { if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles.get())) {
remoteTitle remoteTitle
} else { } else {
null null
@@ -23,7 +23,7 @@ val Manga.readerOrientation: Long
val Manga.downloadedFilter: TriState val Manga.downloadedFilter: TriState
get() { get() {
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS if (Injekt.get<BasePreferences>().downloadedOnly.get()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) { return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
@@ -11,7 +11,7 @@ class CreateSourceCategory(private val preferences: SourcePreferences) {
} }
// Create category. // Create category.
preferences.sourcesTabCategories() += category preferences.sourcesTabCategories += category
return Result.Success return Result.Success
} }
@@ -7,9 +7,9 @@ import tachiyomi.core.common.preference.minusAssign
class DeleteSourceCategory(private val preferences: SourcePreferences) { class DeleteSourceCategory(private val preferences: SourcePreferences) {
fun await(category: String) { fun await(category: String) {
preferences.sourcesTabSourcesInCategories().getAndSet { sourcesInCategories -> preferences.sourcesTabSourcesInCategories.getAndSet { sourcesInCategories ->
sourcesInCategories.filterNot { it.substringAfter("|") == category }.toSet() sourcesInCategories.filterNot { it.substringAfter("|") == category }.toSet()
} }
preferences.sourcesTabCategories() -= category preferences.sourcesTabCategories -= category
} }
} }
@@ -18,17 +18,17 @@ class GetEnabledSources(
fun subscribe(): Flow<List<Source>> { fun subscribe(): Flow<List<Source>> {
return combine( return combine(
preferences.pinnedSources().changes(), preferences.pinnedSources.changes(),
combine( combine(
preferences.enabledLanguages().changes(), preferences.enabledLanguages.changes(),
preferences.disabledSources().changes(), preferences.disabledSources.changes(),
preferences.lastUsedSource().changes(), preferences.lastUsedSource.changes(),
) { a, b, c -> Triple(a, b, c) }, ) { a, b, c -> Triple(a, b, c) },
// SY --> // SY -->
combine( combine(
preferences.dataSaverExcludedSources().changes(), preferences.dataSaverExcludedSources.changes(),
preferences.sourcesTabSourcesInCategories().changes(), preferences.sourcesTabSourcesInCategories.changes(),
preferences.sourcesTabCategoriesFilter().changes(), preferences.sourcesTabCategoriesFilter.changes(),
) { a, b, c -> Triple(a, b, c) }, ) { a, b, c -> Triple(a, b, c) },
// SY <-- // SY <--
repository.getSources(), repository.getSources(),
@@ -13,19 +13,19 @@ class GetIncognitoState(
private val extensionManager: ExtensionManager, private val extensionManager: ExtensionManager,
) { ) {
fun await(sourceId: Long?): Boolean { fun await(sourceId: Long?): Boolean {
if (basePreferences.incognitoMode().get()) return true if (basePreferences.incognitoMode.get()) return true
if (sourceId == null) return false if (sourceId == null) return false
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
return extensionPackage in sourcePreferences.incognitoExtensions().get() return extensionPackage in sourcePreferences.incognitoExtensions.get()
} }
fun subscribe(sourceId: Long?): Flow<Boolean> { fun subscribe(sourceId: Long?): Flow<Boolean> {
if (sourceId == null) return basePreferences.incognitoMode().changes() if (sourceId == null) return basePreferences.incognitoMode.changes()
return combine( return combine(
basePreferences.incognitoMode().changes(), basePreferences.incognitoMode.changes(),
sourcePreferences.incognitoExtensions().changes(), sourcePreferences.incognitoExtensions.changes(),
extensionManager.getExtensionPackageAsFlow(sourceId), extensionManager.getExtensionPackageAsFlow(sourceId),
) { incognito, incognitoExtensions, extensionPackage -> ) { incognito, incognitoExtensions, extensionPackage ->
incognito || (extensionPackage in incognitoExtensions) incognito || (extensionPackage in incognitoExtensions)
@@ -16,8 +16,8 @@ class GetLanguagesWithSources(
fun subscribe(): Flow<SortedMap<String, List<Source>>> { fun subscribe(): Flow<SortedMap<String, List<Source>>> {
return combine( return combine(
preferences.enabledLanguages().changes(), preferences.enabledLanguages.changes(),
preferences.disabledSources().changes(), preferences.disabledSources.changes(),
repository.getOnlineSources(), repository.getOnlineSources(),
) { enabledLanguage, disabledSource, onlineSources -> ) { enabledLanguage, disabledSource, onlineSources ->
val sortedSources = onlineSources.filterNot { it.id in BlacklistedSources.HIDDEN_SOURCES }.sortedWith( val sortedSources = onlineSources.filterNot { it.id in BlacklistedSources.HIDDEN_SOURCES }.sortedWith(
@@ -9,7 +9,7 @@ class GetShowLatest(
) { ) {
fun subscribe(hasSmartSearchConfig: Boolean): Flow<Boolean> { fun subscribe(hasSmartSearchConfig: Boolean): Flow<Boolean> {
return preferences.useNewSourceNavigation().changes() return preferences.useNewSourceNavigation.changes()
.map { .map {
!hasSmartSearchConfig && !it !hasSmartSearchConfig && !it
} }
@@ -9,6 +9,6 @@ class GetSourceCategories(
) { ) {
fun subscribe(): Flow<List<String>> { fun subscribe(): Flow<List<String>> {
return preferences.sourcesTabCategories().changes().map { it.sortedWith(String.CASE_INSENSITIVE_ORDER) } return preferences.sourcesTabCategories.changes().map { it.sortedWith(String.CASE_INSENSITIVE_ORDER) }
} }
} }
@@ -16,8 +16,8 @@ class GetSourcesWithFavoriteCount(
fun subscribe(): Flow<List<Pair<Source, Long>>> { fun subscribe(): Flow<List<Pair<Source, Long>>> {
return combine( return combine(
preferences.migrationSortingDirection().changes(), preferences.migrationSortingDirection.changes(),
preferences.migrationSortingMode().changes(), preferences.migrationSortingMode.changes(),
repository.getSourcesWithFavoriteCount(), repository.getSourcesWithFavoriteCount(),
) { direction, mode, list -> ) { direction, mode, list ->
list list
@@ -14,7 +14,7 @@ class RenameSourceCategory(
CreateSourceCategory.Result.Success -> {} CreateSourceCategory.Result.Success -> {}
} }
preferences.sourcesTabSourcesInCategories().getAndSet { sourcesInCategories -> preferences.sourcesTabSourcesInCategories.getAndSet { sourcesInCategories ->
sourcesInCategories.map { sourcesInCategories.map {
val index = it.indexOf('|') val index = it.indexOf('|')
if (index != -1 && it.substring(index + 1) == categoryOld) { if (index != -1 && it.substring(index + 1) == categoryOld) {
@@ -24,7 +24,7 @@ class RenameSourceCategory(
} }
}.toSet() }.toSet()
} }
preferences.sourcesTabCategories().getAndSet { preferences.sourcesTabCategories.getAndSet {
it.minus(categoryOld).plus(categoryNew) it.minus(categoryOld).plus(categoryNew)
} }
@@ -7,8 +7,8 @@ class SetMigrateSorting(
) { ) {
fun await(mode: Mode, direction: Direction) { fun await(mode: Mode, direction: Direction) {
preferences.migrationSortingMode().set(mode) preferences.migrationSortingMode.set(mode)
preferences.migrationSortingDirection().set(direction) preferences.migrationSortingDirection.set(direction)
} }
enum class Mode { enum class Mode {
@@ -10,7 +10,7 @@ class SetSourceCategories(
fun await(source: Source, sourceCategories: List<String>) { fun await(source: Source, sourceCategories: List<String>) {
val sourceIdString = source.id.toString() val sourceIdString = source.id.toString()
preferences.sourcesTabSourcesInCategories().getAndSet { sourcesInCategories -> preferences.sourcesTabSourcesInCategories.getAndSet { sourcesInCategories ->
val currentSourceCategories = sourcesInCategories.filterNot { val currentSourceCategories = sourcesInCategories.filterNot {
it.substringBefore('|') == sourceIdString it.substringBefore('|') == sourceIdString
} }
@@ -9,7 +9,7 @@ class ToggleExcludeFromDataSaver(
) { ) {
fun await(source: Source) { fun await(source: Source) {
preferences.dataSaverExcludedSources().getAndSet { preferences.dataSaverExcludedSources.getAndSet {
if (source.id.toString() in it) { if (source.id.toString() in it) {
it - source.id.toString() it - source.id.toString()
} else { } else {
@@ -7,7 +7,7 @@ class ToggleIncognito(
private val preferences: SourcePreferences, private val preferences: SourcePreferences,
) { ) {
fun await(extensions: String, enable: Boolean) { fun await(extensions: String, enable: Boolean) {
preferences.incognitoExtensions().getAndSet { preferences.incognitoExtensions.getAndSet {
if (enable) it.plus(extensions) else it.minus(extensions) if (enable) it.plus(extensions) else it.minus(extensions)
} }
} }
@@ -8,8 +8,8 @@ class ToggleLanguage(
) { ) {
fun await(language: String) { fun await(language: String) {
val isEnabled = language in preferences.enabledLanguages().get() val isEnabled = language in preferences.enabledLanguages.get()
preferences.enabledLanguages().getAndSet { enabled -> preferences.enabledLanguages.getAndSet { enabled ->
if (isEnabled) enabled.minus(language) else enabled.plus(language) if (isEnabled) enabled.minus(language) else enabled.plus(language)
} }
} }
@@ -13,19 +13,19 @@ class ToggleSource(
} }
fun await(sourceId: Long, enable: Boolean = isEnabled(sourceId)) { fun await(sourceId: Long, enable: Boolean = isEnabled(sourceId)) {
preferences.disabledSources().getAndSet { disabled -> preferences.disabledSources.getAndSet { disabled ->
if (enable) disabled.minus("$sourceId") else disabled.plus("$sourceId") if (enable) disabled.minus("$sourceId") else disabled.plus("$sourceId")
} }
} }
fun await(sourceIds: List<Long>, enable: Boolean) { fun await(sourceIds: List<Long>, enable: Boolean) {
val transformedSourceIds = sourceIds.map { it.toString() } val transformedSourceIds = sourceIds.map { it.toString() }
preferences.disabledSources().getAndSet { disabled -> preferences.disabledSources.getAndSet { disabled ->
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds) if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
} }
} }
private fun isEnabled(sourceId: Long): Boolean { private fun isEnabled(sourceId: Long): Boolean {
return sourceId.toString() in preferences.disabledSources().get() return sourceId.toString() in preferences.disabledSources.get()
} }
} }
@@ -9,8 +9,8 @@ class ToggleSourcePin(
) { ) {
fun await(source: Source) { fun await(source: Source) {
val isPinned = source.id.toString() in preferences.pinnedSources().get() val isPinned = source.id.toString() in preferences.pinnedSources.get()
preferences.pinnedSources().getAndSet { pinned -> preferences.pinnedSources.getAndSet { pinned ->
if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}") if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
} }
} }
@@ -10,80 +10,86 @@ import tachiyomi.core.common.preference.getLongArray
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
class SourcePreferences( class SourcePreferences(
private val preferenceStore: PreferenceStore, preferenceStore: PreferenceStore,
) { ) {
fun sourceDisplayMode() = preferenceStore.getObjectFromString( val sourceDisplayMode: Preference<LibraryDisplayMode> = preferenceStore.getObjectFromString(
"pref_display_mode_catalogue", "pref_display_mode_catalogue",
LibraryDisplayMode.default, LibraryDisplayMode.default,
LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::serialize,
LibraryDisplayMode.Serializer::deserialize, LibraryDisplayMode.Serializer::deserialize,
) )
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages()) val enabledLanguages: Preference<Set<String>> = preferenceStore.getStringSet(
"source_languages",
LocaleHelper.getDefaultEnabledLanguages(),
)
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet()) val disabledSources: Preference<Set<String>> = preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet()) val incognitoExtensions: Preference<Set<String>> = preferenceStore.getStringSet("incognito_extensions", emptySet())
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet()) val pinnedSources: Preference<Set<String>> = preferenceStore.getStringSet("pinned_catalogues", emptySet())
fun lastUsedSource() = preferenceStore.getLong( val lastUsedSource: Preference<Long> = preferenceStore.getLong(
Preference.appStateKey("last_catalogue_source"), Preference.appStateKey("last_catalogue_source"),
-1, -1,
) )
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true) val showNsfwSource: Preference<Boolean> = preferenceStore.getBoolean("show_nsfw_source", true)
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL) val migrationSortingMode: Preference<SetMigrateSorting.Mode> = preferenceStore.getEnum(
"pref_migration_sorting",
SetMigrateSorting.Mode.ALPHABETICAL,
)
fun migrationSortingDirection() = preferenceStore.getEnum( val migrationSortingDirection: Preference<SetMigrateSorting.Direction> = preferenceStore.getEnum(
"pref_migration_direction", "pref_migration_direction",
SetMigrateSorting.Direction.ASCENDING, SetMigrateSorting.Direction.ASCENDING,
) )
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false) val hideInLibraryItems: Preference<Boolean> = preferenceStore.getBoolean("browse_hide_in_library_items", false)
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet()) val extensionRepos: Preference<Set<String>> = preferenceStore.getStringSet("extension_repos", emptySet())
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0) val extensionUpdatesCount: Preference<Int> = preferenceStore.getInt("ext_updates_count", 0)
fun trustedExtensions() = preferenceStore.getStringSet( val trustedExtensions: Preference<Set<String>> = preferenceStore.getStringSet(
Preference.appStateKey("trusted_extensions"), Preference.appStateKey("trusted_extensions"),
emptySet(), emptySet(),
) )
fun globalSearchFilterState() = preferenceStore.getBoolean( val globalSearchFilterState: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("has_filters_toggle_state"), Preference.appStateKey("has_filters_toggle_state"),
false, false,
) )
// SY --> // SY -->
fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true) val enableSourceBlacklist: Preference<Boolean> = preferenceStore.getBoolean("eh_enable_source_blacklist", true)
fun sourcesTabCategories() = preferenceStore.getStringSet("sources_tab_categories", mutableSetOf()) val sourcesTabCategories: Preference<Set<String>> = preferenceStore.getStringSet("sources_tab_categories", mutableSetOf())
fun sourcesTabCategoriesFilter() = preferenceStore.getBoolean("sources_tab_categories_filter", false) val sourcesTabCategoriesFilter: Preference<Boolean> = preferenceStore.getBoolean("sources_tab_categories_filter", false)
fun sourcesTabSourcesInCategories() = preferenceStore.getStringSet("sources_tab_source_categories", mutableSetOf()) val sourcesTabSourcesInCategories: Preference<Set<String>> = preferenceStore.getStringSet("sources_tab_source_categories", mutableSetOf())
fun dataSaver() = preferenceStore.getEnum("data_saver", DataSaver.NONE) val dataSaver: Preference<DataSaver> = preferenceStore.getEnum("data_saver", DataSaver.NONE)
fun dataSaverIgnoreJpeg() = preferenceStore.getBoolean("ignore_jpeg", false) val dataSaverIgnoreJpeg: Preference<Boolean> = preferenceStore.getBoolean("ignore_jpeg", false)
fun dataSaverIgnoreGif() = preferenceStore.getBoolean("ignore_gif", true) val dataSaverIgnoreGif: Preference<Boolean> = preferenceStore.getBoolean("ignore_gif", true)
fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80) val dataSaverImageQuality: Preference<Int> = preferenceStore.getInt("data_saver_image_quality", 80)
fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false) val dataSaverImageFormatJpeg: Preference<Boolean> = preferenceStore.getBoolean("data_saver_image_format_jpeg", false)
fun dataSaverServer() = preferenceStore.getString("data_saver_server", "") val dataSaverServer: Preference<String> = preferenceStore.getString("data_saver_server", "")
fun dataSaverColorBW() = preferenceStore.getBoolean("data_saver_color_bw", false) val dataSaverColorBW: Preference<Boolean> = preferenceStore.getBoolean("data_saver_color_bw", false)
fun dataSaverExcludedSources() = preferenceStore.getStringSet("data_saver_excluded", emptySet()) val dataSaverExcludedSources: Preference<Set<String>> = preferenceStore.getStringSet("data_saver_excluded", emptySet())
fun dataSaverDownloader() = preferenceStore.getBoolean("data_saver_downloader", true) val dataSaverDownloader: Preference<Boolean> = preferenceStore.getBoolean("data_saver_downloader", true)
enum class DataSaver { enum class DataSaver {
NONE, NONE,
@@ -91,32 +97,38 @@ class SourcePreferences(
WSRV_NL, WSRV_NL,
} }
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false) val allowLocalSourceHiddenFolders: Preference<Boolean> = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0") val preferredMangaDexId: Preference<String> = preferenceStore.getString("preferred_mangaDex_id", "0")
fun mangadexSyncToLibraryIndexes() = preferenceStore.getStringSet( val mangadexSyncToLibraryIndexes: Preference<Set<String>> = preferenceStore.getStringSet(
"pref_mangadex_sync_to_library_indexes", "pref_mangadex_sync_to_library_indexes",
emptySet(), emptySet(),
) )
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE) val recommendationSearchFlags: Preference<Int> = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
// SY <-- // SY <--
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList()) val migrationSources: Preference<List<Long>> = preferenceStore.getLongArray("migration_sources", emptyList())
fun migrationFlags() = preferenceStore.getObjectFromInt( val migrationFlags: Preference<Set<MigrationFlag>> = preferenceStore.getObjectFromInt(
key = "migration_flags", key = "migration_flags",
defaultValue = MigrationFlag.entries.toSet(), defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) }, serializer = { MigrationFlag.toBit(it) },
deserializer = { value: Int -> MigrationFlag.fromBit(value) }, deserializer = { value: Int -> MigrationFlag.fromBit(value) },
) )
fun migrationDeepSearchMode() = preferenceStore.getBoolean("migration_deep_search", false) val migrationDeepSearchMode: Preference<Boolean> = preferenceStore.getBoolean("migration_deep_search", false)
fun migrationPrioritizeByChapters() = preferenceStore.getBoolean("migration_prioritize_by_chapters", false) val migrationPrioritizeByChapters: Preference<Boolean> = preferenceStore.getBoolean(
"migration_prioritize_by_chapters",
false,
)
fun migrationHideUnmatched() = preferenceStore.getBoolean("migration_hide_unmatched", false) val migrationHideUnmatched: Preference<Boolean> = preferenceStore.getBoolean("migration_hide_unmatched", false)
fun migrationHideWithoutUpdates() = preferenceStore.getBoolean("migration_hide_without_updates", false) val migrationHideWithoutUpdates: Preference<Boolean> = preferenceStore.getBoolean(
"migration_hide_without_updates",
false,
)
} }
@@ -9,21 +9,21 @@ import java.util.UUID
class SyncPreferences( class SyncPreferences(
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
) { ) {
fun clientHost() = preferenceStore.getString("sync_client_host", "https://sync.tachiyomi.org") val clientHost: Preference<String> = preferenceStore.getString("sync_client_host", "https://sync.tachiyomi.org")
fun clientAPIKey() = preferenceStore.getString("sync_client_api_key", "") val clientAPIKey: Preference<String> = preferenceStore.getString("sync_client_api_key", "")
fun lastSyncTimestamp() = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L) val lastSyncTimestamp: Preference<Long> = preferenceStore.getLong(Preference.appStateKey("last_sync_timestamp"), 0L)
fun lastSyncEtag() = preferenceStore.getString("sync_etag", "") val lastSyncEtag: Preference<String> = preferenceStore.getString("sync_etag", "")
fun syncInterval() = preferenceStore.getInt("sync_interval", 0) val syncInterval: Preference<Int> = preferenceStore.getInt("sync_interval", 0)
fun syncService() = preferenceStore.getInt("sync_service", 0) val syncService: Preference<Int> = preferenceStore.getInt("sync_service", 0)
fun googleDriveAccessToken() = preferenceStore.getString( val googleDriveAccessToken: Preference<String> = preferenceStore.getString(
Preference.appStateKey("google_drive_access_token"), Preference.appStateKey("google_drive_access_token"),
"", "",
) )
fun googleDriveRefreshToken() = preferenceStore.getString( val googleDriveRefreshToken: Preference<String> = preferenceStore.getString(
Preference.appStateKey("google_drive_refresh_token"), Preference.appStateKey("google_drive_refresh_token"),
"", "",
) )
@@ -42,7 +42,7 @@ class SyncPreferences(
} }
fun isSyncEnabled(): Boolean { fun isSyncEnabled(): Boolean {
return syncService().get() != 0 return syncService.get() != 0
} }
fun getSyncSettings(): SyncSettings { fun getSyncSettings(): SyncSettings {
@@ -34,17 +34,17 @@ class TrackPreferences(
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "") fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10) val anilistScoreType: Preference<String> = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true) val autoUpdateTrack: Preference<Boolean> = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum( val autoUpdateTrackOnMarkRead: Preference<AutoTrackState> = preferenceStore.getEnum(
"pref_auto_update_manga_on_mark_read", "pref_auto_update_manga_on_mark_read",
AutoTrackState.ALWAYS, AutoTrackState.ALWAYS,
) )
// SY --> // SY -->
fun resolveUsingSourceMetadata() = preferenceStore.getBoolean( val resolveUsingSourceMetadata: Preference<Boolean> = preferenceStore.getBoolean(
"pref_resolve_using_source_metadata_key", "pref_resolve_using_source_metadata_key",
true, true,
) )
@@ -6,6 +6,7 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum import tachiyomi.core.common.preference.getEnum
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@@ -13,10 +14,10 @@ import java.time.format.FormatStyle
import java.util.Locale import java.util.Locale
class UiPreferences( class UiPreferences(
private val preferenceStore: PreferenceStore, preferenceStore: PreferenceStore,
) { ) {
fun themeMode() = preferenceStore.getEnum( val themeMode = preferenceStore.getEnum(
"pref_theme_mode_key", "pref_theme_mode_key",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ThemeMode.SYSTEM ThemeMode.SYSTEM
@@ -25,7 +26,7 @@ class UiPreferences(
}, },
) )
fun appTheme() = preferenceStore.getEnum( val appTheme: Preference<AppTheme> = preferenceStore.getEnum(
"pref_app_theme", "pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET AppTheme.MONET
@@ -34,37 +35,37 @@ class UiPreferences(
}, },
) )
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false) val themeDarkAmoled: Preference<Boolean> = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true) val relativeTime: Preference<Boolean> = preferenceStore.getBoolean("relative_time_v2", true)
fun dateFormat() = preferenceStore.getString("app_date_format", "") val dateFormat: Preference<String> = preferenceStore.getString("app_date_format", "")
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC) val tabletUiMode: Preference<TabletUiMode> = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
fun imagesInDescription() = preferenceStore.getBoolean("pref_render_images_description", true) val imagesInDescription: Preference<Boolean> = preferenceStore.getBoolean("pref_render_images_description", true)
// SY --> // SY -->
fun expandFilters() = preferenceStore.getBoolean("eh_expand_filters", false) val expandFilters: Preference<Boolean> = preferenceStore.getBoolean("eh_expand_filters", false)
fun hideFeedTab() = preferenceStore.getBoolean("hide_latest_tab", false) val hideFeedTab: Preference<Boolean> = preferenceStore.getBoolean("hide_latest_tab", false)
fun feedTabInFront() = preferenceStore.getBoolean("latest_tab_position", false) val feedTabInFront: Preference<Boolean> = preferenceStore.getBoolean("latest_tab_position", false)
fun recommendsInOverflow() = preferenceStore.getBoolean("recommends_in_overflow", false) val recommendsInOverflow: Preference<Boolean> = preferenceStore.getBoolean("recommends_in_overflow", false)
fun mergeInOverflow() = preferenceStore.getBoolean("merge_in_overflow", true) val mergeInOverflow: Preference<Boolean> = preferenceStore.getBoolean("merge_in_overflow", true)
fun previewsRowCount() = preferenceStore.getInt("pref_previews_row_count", 4) val previewsRowCount: Preference<Int> = preferenceStore.getInt("pref_previews_row_count", 4)
fun useNewSourceNavigation() = preferenceStore.getBoolean("use_new_source_navigation", true) val useNewSourceNavigation: Preference<Boolean> = preferenceStore.getBoolean("use_new_source_navigation", true)
fun bottomBarLabels() = preferenceStore.getBoolean("pref_show_bottom_bar_labels", true) val bottomBarLabels: Preference<Boolean> = preferenceStore.getBoolean("pref_show_bottom_bar_labels", true)
fun showNavUpdates() = preferenceStore.getBoolean("pref_show_updates_button", true) val showNavUpdates: Preference<Boolean> = preferenceStore.getBoolean("pref_show_updates_button", true)
fun showNavHistory() = preferenceStore.getBoolean("pref_show_history_button", true) val showNavHistory: Preference<Boolean> = preferenceStore.getBoolean("pref_show_history_button", true)
// SY <-- // SY <--
@@ -31,7 +31,7 @@ import androidx.compose.ui.unit.sp
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import com.gowtham.ratingbar.ComposeStars import com.gowtham.ratingbar.ComposeStars
import com.gowtham.ratingbar.RatingBarConfig import com.gowtham.ratingbar.RatingBarStyle
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import exh.metadata.MetadataUtil import exh.metadata.MetadataUtil
@@ -224,13 +224,16 @@ fun BrowseSourceEHentaiListItem(
) { ) {
ComposeStars( ComposeStars(
value = rating, value = rating,
config = RatingBarConfig().apply { numOfStars = 5,
isIndicator(true) size = 18.dp,
numStars(5) spaceBetween = 2.dp,
size(18.dp) hideInactiveStars = false,
activeColor(Color(0xFF005ED7)) style = RatingBarStyle.Fill(
inactiveColor(Color(0xE1E2ECFF)) activeColor = Color(0xFF005ED7),
}, inActiveColor = Color(0xE1E2ECFF),
),
painterEmpty = null,
painterFilled = null,
) )
val color = genre?.first?.color val color = genre?.first?.color
val res = genre?.second val res = genre?.second
@@ -3,12 +3,12 @@ package eu.kanade.presentation.browse.components
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
@@ -17,7 +17,7 @@ fun BrowseSourceFloatingActionButton(
onFabClick: () -> Unit, onFabClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
ExtendedFloatingActionButton( SmallExtendedFloatingActionButton(
modifier = modifier, modifier = modifier,
text = { text = {
Text( Text(
@@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.shouldExpandFAB import tachiyomi.presentation.core.util.shouldExpandFAB
@@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
onCreate: () -> Unit, onCreate: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
ExtendedFloatingActionButton( SmallExtendedFloatingActionButton(
text = { Text(text = stringResource(MR.strings.action_add)) }, text = { Text(text = stringResource(MR.strings.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
onClick = onCreate, onClick = onCreate,
@@ -21,8 +21,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
@@ -195,7 +196,7 @@ fun AppBarActions(
actions.filterIsInstance<AppBar.Action>().map { actions.filterIsInstance<AppBar.Action>().map {
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { tooltip = {
PlainTooltip { PlainTooltip {
Text(it.title) Text(it.title)
@@ -220,7 +221,7 @@ fun AppBarActions(
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>() val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
if (overflowActions.isNotEmpty()) { if (overflowActions.isNotEmpty()) {
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { tooltip = {
PlainTooltip { PlainTooltip {
Text(stringResource(MR.strings.action_menu_overflow_description)) Text(stringResource(MR.strings.action_menu_overflow_description))
@@ -349,7 +350,7 @@ fun SearchToolbar(
// Don't show search action // Don't show search action
} else if (searchQuery == null) { } else if (searchQuery == null) {
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { tooltip = {
PlainTooltip { PlainTooltip {
Text(stringResource(MR.strings.action_search)) Text(stringResource(MR.strings.action_search))
@@ -369,7 +370,7 @@ fun SearchToolbar(
} }
} else if (searchQuery.isNotEmpty()) { } else if (searchQuery.isNotEmpty()) {
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { tooltip = {
PlainTooltip { PlainTooltip {
Text(stringResource(MR.strings.action_reset)) Text(stringResource(MR.strings.action_reset))
@@ -33,8 +33,8 @@ fun relativeDateText(
val context = LocalContext.current val context = LocalContext.current
val preferences = remember { Injekt.get<UiPreferences>() } val preferences = remember { Injekt.get<UiPreferences>() }
val relativeTime = remember { preferences.relativeTime().get() } val relativeTime = remember { preferences.relativeTime.get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) } val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get()) }
return localDate?.toRelativeString( return localDate?.toRelativeString(
context = context, context = context,
@@ -1,6 +1,5 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -14,11 +13,11 @@ import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
fun DownloadDropdownMenu( fun DownloadDropdownMenu(
modifier: Modifier = Modifier,
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit, onDownloadClicked: (DownloadAction) -> Unit,
offset: DpOffset? = null, offset: DpOffset? = null,
modifier: Modifier = Modifier,
) { ) {
if (offset != null) { if (offset != null) {
DropdownMenu( DropdownMenu(
@@ -49,7 +48,7 @@ fun DownloadDropdownMenu(
} }
@Composable @Composable
private fun ColumnScope.DownloadDropdownMenuItems( private fun DownloadDropdownMenuItems(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit, onDownloadClicked: (DownloadAction) -> Unit,
) { ) {
@@ -59,6 +58,7 @@ private fun ColumnScope.DownloadDropdownMenuItems(
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10), DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25), DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread), DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
DownloadAction.BOOKMARKED_CHAPTERS to stringResource(MR.strings.download_bookmarked),
) )
options.map { (downloadAction, string) -> options.map { (downloadAction, string) ->
@@ -100,9 +100,9 @@ fun LibrarySettingsDialog(
private fun ColumnScope.FilterPage( private fun ColumnScope.FilterPage(
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
) { ) {
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState() val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState() val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState() val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.label_downloaded), label = stringResource(MR.strings.label_downloaded),
@@ -114,25 +114,25 @@ private fun ColumnScope.FilterPage(
enabled = !downloadedOnly, enabled = !downloadedOnly,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) }, onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
) )
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState() val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.action_filter_unread), label = stringResource(MR.strings.action_filter_unread),
state = filterUnread, state = filterUnread,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) }, onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
) )
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState() val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.label_started), label = stringResource(MR.strings.label_started),
state = filterStarted, state = filterStarted,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) }, onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
) )
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState() val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked), label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked, state = filterBookmarked,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) }, onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
) )
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState() val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.completed), label = stringResource(MR.strings.completed),
state = filterCompleted, state = filterCompleted,
@@ -143,7 +143,7 @@ private fun ColumnScope.FilterPage(
(isDevFlavor || isPreviewBuildType) && (isDevFlavor || isPreviewBuildType) &&
LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions
) { ) {
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState() val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.action_filter_interval_custom), label = stringResource(MR.strings.action_filter_interval_custom),
state = filterIntervalCustom, state = filterIntervalCustom,
@@ -151,7 +151,7 @@ private fun ColumnScope.FilterPage(
) )
} }
// SY --> // SY -->
val filterLewd by screenModel.libraryPreferences.filterLewd().collectAsState() val filterLewd by screenModel.libraryPreferences.filterLewd.collectAsState()
TriStateItem( TriStateItem(
label = stringResource(SYMR.strings.lewd), label = stringResource(SYMR.strings.lewd),
state = filterLewd, state = filterLewd,
@@ -194,7 +194,7 @@ private fun ColumnScope.SortPage(
) { ) {
val trackers by screenModel.trackersFlow.collectAsState() val trackers by screenModel.trackersFlow.collectAsState()
// SY --> // SY -->
val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState() val globalSortMode by screenModel.libraryPreferences.sortingMode.collectAsState()
val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) { val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
category.sort.type category.sort.type
} else { } else {
@@ -206,9 +206,9 @@ private fun ColumnScope.SortPage(
!globalSortMode.isAscending !globalSortMode.isAscending
} }
val hasSortTags by remember { val hasSortTags by remember {
screenModel.libraryPreferences.sortTagsForLibrary().changes() screenModel.libraryPreferences.sortTagsForLibrary.changes()
.map { it.isNotEmpty() } .map { it.isNotEmpty() }
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty()) }.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary.get().isNotEmpty())
// SY <-- // SY <--
val options = remember(trackers.isEmpty()/* SY --> */, hasSortTags/* SY <-- */) { val options = remember(trackers.isEmpty()/* SY --> */, hasSortTags/* SY <-- */) {
@@ -287,7 +287,7 @@ private val displayModes = listOf(
private fun ColumnScope.DisplayPage( private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
) { ) {
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState() val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
SettingsChipRow(MR.strings.action_display_mode) { SettingsChipRow(MR.strings.action_display_mode) {
displayModes.map { (titleRes, mode) -> displayModes.map { (titleRes, mode) ->
FilterChip( FilterChip(
@@ -302,9 +302,9 @@ private fun ColumnScope.DisplayPage(
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val columnPreference = remember { val columnPreference = remember {
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenModel.libraryPreferences.landscapeColumns() screenModel.libraryPreferences.landscapeColumns
} else { } else {
screenModel.libraryPreferences.portraitColumns() screenModel.libraryPreferences.portraitColumns
} }
} }
@@ -326,33 +326,33 @@ private fun ColumnScope.DisplayPage(
HeadingItem(MR.strings.overlay_header) HeadingItem(MR.strings.overlay_header)
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_download_badge), label = stringResource(MR.strings.action_display_download_badge),
pref = screenModel.libraryPreferences.downloadBadge(), pref = screenModel.libraryPreferences.downloadBadge,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_unread_badge), label = stringResource(MR.strings.action_display_unread_badge),
pref = screenModel.libraryPreferences.unreadBadge(), pref = screenModel.libraryPreferences.unreadBadge,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_local_badge), label = stringResource(MR.strings.action_display_local_badge),
pref = screenModel.libraryPreferences.localBadge(), pref = screenModel.libraryPreferences.localBadge,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_language_badge), label = stringResource(MR.strings.action_display_language_badge),
pref = screenModel.libraryPreferences.languageBadge(), pref = screenModel.libraryPreferences.languageBadge,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_show_continue_reading_button), label = stringResource(MR.strings.action_display_show_continue_reading_button),
pref = screenModel.libraryPreferences.showContinueReadingButton(), pref = screenModel.libraryPreferences.showContinueReadingButton,
) )
HeadingItem(MR.strings.tabs_header) HeadingItem(MR.strings.tabs_header)
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_show_tabs), label = stringResource(MR.strings.action_display_show_tabs),
pref = screenModel.libraryPreferences.categoryTabs(), pref = screenModel.libraryPreferences.categoryTabs,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.action_display_show_number_of_items), label = stringResource(MR.strings.action_display_show_number_of_items),
pref = screenModel.libraryPreferences.categoryNumberOfItems(), pref = screenModel.libraryPreferences.categoryNumberOfItems,
) )
} }
@@ -66,7 +66,7 @@ fun ChapterSettingsDialog(
) )
} }
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() } val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly.get() }
TabbedDialog( TabbedDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
@@ -1,10 +1,7 @@
package eu.kanade.presentation.manga package eu.kanade.presentation.manga
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -27,9 +24,11 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -101,7 +100,6 @@ import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.TwoPanelBox import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.VerticalFastScroller import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.PullRefresh
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@@ -167,7 +165,7 @@ fun MangaScreen(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
@@ -331,7 +329,7 @@ private fun MangaScreenSmallImpl(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
@@ -418,12 +416,7 @@ private fun MangaScreenSmallImpl(
val isFABVisible = remember(chapters) { val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected chapters.fastAny { !it.chapter.read } && !isAnySelected
} }
AnimatedVisibility( SmallExtendedFloatingActionButton(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
ExtendedFloatingActionButton(
text = { text = {
val isReading = remember(state.chapters) { val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read } state.chapters.fastAny { it.chapter.read }
@@ -435,8 +428,11 @@ private fun MangaScreenSmallImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(), expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
),
) )
}
}, },
) { contentPadding -> ) { contentPadding ->
val topPadding = contentPadding.calculateTopPadding() val topPadding = contentPadding.calculateTopPadding()
@@ -654,7 +650,7 @@ fun MangaScreenLargeImpl(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection // Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit, onInvertSelection: () -> Unit,
) { ) {
@@ -737,12 +733,7 @@ fun MangaScreenLargeImpl(
val isFABVisible = remember(chapters) { val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected chapters.fastAny { !it.chapter.read } && !isAnySelected
} }
AnimatedVisibility( SmallExtendedFloatingActionButton(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
ExtendedFloatingActionButton(
text = { text = {
val isReading = remember(state.chapters) { val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read } state.chapters.fastAny { it.chapter.read }
@@ -756,8 +747,11 @@ fun MangaScreenLargeImpl(
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading, onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(), expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
),
) )
}
}, },
) { contentPadding -> ) { contentPadding ->
PullRefresh( PullRefresh(
@@ -953,7 +947,7 @@ private fun LazyListScope.sharedChapterItems(
// SY <-- // SY <--
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
) { ) {
items( items(
@@ -1020,14 +1014,14 @@ private fun LazyListScope.sharedChapterItems(
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction, chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = { onLongClick = {
onChapterSelected(item, !item.selected, true, true) onChapterSelected(item, !item.selected, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}, },
onClick = { onClick = {
onChapterItemClick( onChapterItemClick(
chapterItem = item, chapterItem = item,
isAnyChapterSelected = isAnyChapterSelected, isAnyChapterSelected = isAnyChapterSelected,
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) }, onToggleSelection = { onChapterSelected(item, !item.selected, false) },
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
) )
}, },
@@ -6,6 +6,7 @@ enum class DownloadAction {
NEXT_10_CHAPTERS, NEXT_10_CHAPTERS,
NEXT_25_CHAPTERS, NEXT_25_CHAPTERS,
UNREAD_CHAPTERS, UNREAD_CHAPTERS,
BOOKMARKED_CHAPTERS,
} }
enum class EditCoverAction { enum class EditCoverAction {
@@ -93,10 +93,10 @@ fun MangaBottomActionMenu(
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
var resetJob: Job? = remember { null } var resetJob by remember { mutableStateOf<Job?>(null) }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex } confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel() resetJob?.cancel()
resetJob = scope.launch { resetJob = scope.launch {
delay(1.seconds) delay(1.seconds)
@@ -260,10 +260,10 @@ fun LibraryBottomActionMenu(
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
var resetJob: Job? = remember { null } var resetJob by remember { mutableStateOf<Job?>(null) }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0..5).forEach { i -> confirm[i] = i == toConfirmIndex } confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel() resetJob?.cancel()
resetJob = scope.launch { resetJob = scope.launch {
delay(1.seconds) delay(1.seconds)
@@ -605,7 +605,9 @@ private fun ColumnScope.MangaContentInfo(
} }
} }
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = markdownAnnotator( @Composable
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = remember(loadImages, linkStyle) {
markdownAnnotator(
annotate = { content, child -> annotate = { content, child ->
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) { if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK) val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
@@ -642,7 +644,8 @@ private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = ma
config = markdownAnnotatorConfig( config = markdownAnnotatorConfig(
eolAsNewLine = true, eolAsNewLine = true,
), ),
) )
}
@Composable @Composable
private fun MangaSummary( private fun MangaSummary(
@@ -653,7 +656,7 @@ private fun MangaSummary(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val preferences = remember { Injekt.get<UiPreferences>() } val preferences = remember { Injekt.get<UiPreferences>() }
val loadImages = remember { preferences.imagesInDescription().get() } val loadImages = remember { preferences.imagesInDescription.get() }
val animProgress by animateFloatAsState( val animProgress by animateFloatAsState(
targetValue = if (expanded) 1f else 0f, targetValue = if (expanded) 1f else 0f,
label = "summary", label = "summary",
@@ -122,7 +122,7 @@ internal class PermissionStep : OnboardingStep {
color = MaterialTheme.colorScheme.onPrimaryContainer, color = MaterialTheme.colorScheme.onPrimaryContainer,
) )
val crashlyticsPref = privacyPreferences.crashlytics() val crashlyticsPref = privacyPreferences.crashlytics
val crashlytics by crashlyticsPref.collectAsState() val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch( PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics), title = stringResource(MR.strings.onboarding_permission_crashlytics),
@@ -131,7 +131,7 @@ internal class PermissionStep : OnboardingStep {
onToggleChange = crashlyticsPref::set, onToggleChange = crashlyticsPref::set,
) )
val analyticsPref = privacyPreferences.analytics() val analyticsPref = privacyPreferences.analytics
val analytics by analyticsPref.collectAsState() val analytics by analyticsPref.collectAsState()
PermissionSwitch( PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics), title = stringResource(MR.strings.onboarding_permission_analytics),
@@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
internal class StorageStep : OnboardingStep { internal class StorageStep : OnboardingStep {
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory() private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory
private var _isComplete by mutableStateOf(false) private var _isComplete by mutableStateOf(false)
@@ -19,13 +19,13 @@ internal class ThemeStep : OnboardingStep {
@Composable @Composable
override fun Content() { override fun Content() {
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState() val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled() val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState() val amoled by amoledPref.collectAsState()
Column { Column {
@@ -30,6 +30,7 @@ sealed class Preference {
override val title: String, override val title: String,
override val subtitle: CharSequence? = null, override val subtitle: CharSequence? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
val widget: @Composable (() -> Unit)? = null,
val onClick: (() -> Unit)? = null, val onClick: (() -> Unit)? = null,
) : PreferenceItem<String, Unit>() { ) : PreferenceItem<String, Unit>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
@@ -147,6 +147,7 @@ internal fun PreferenceItem(
title = item.title, title = item.title,
subtitle = item.subtitle, subtitle = item.subtitle,
icon = item.icon, icon = item.icon,
widget = item.widget,
onPreferenceClick = item.onClick, onPreferenceClick = item.onClick,
) )
} }
@@ -38,7 +38,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
LaunchedEffect(run) { LaunchedEffect(run) {
if (run) { if (run) {
if (exhPreferences.exhShowSettingsUploadWarning().get()) { if (exhPreferences.exhShowSettingsUploadWarning.get()) {
warnDialogOpen = true warnDialogOpen = true
} else { } else {
configureDialogOpen = true configureDialogOpen = true
@@ -57,7 +57,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
exhPreferences.exhShowSettingsUploadWarning().set(false) exhPreferences.exhShowSettingsUploadWarning.set(false)
configureDialogOpen = true configureDialogOpen = true
warnDialogOpen = false warnDialogOpen = false
}, },
@@ -131,7 +131,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}, },
), ),
/* SY --> Preference.PreferenceItem.SwitchPreference( /* SY --> Preference.PreferenceItem.SwitchPreference(
preference = networkPreferences.verboseLogging(), preference = networkPreferences.verboseLogging,
title = stringResource(MR.strings.pref_verbose_logging), title = stringResource(MR.strings.pref_verbose_logging),
subtitle = stringResource(MR.strings.pref_verbose_logging_summary), subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
onValueChanged = { onValueChanged = {
@@ -251,7 +251,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val networkHelper = remember { Injekt.get<NetworkHelper>() } val networkHelper = remember { Injekt.get<NetworkHelper>() }
val userAgentPref = networkPreferences.defaultUserAgent() val userAgentPref = networkPreferences.defaultUserAgent
val userAgent by userAgentPref.collectAsState() val userAgent by userAgentPref.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
@@ -287,7 +287,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = networkPreferences.dohProvider(), preference = networkPreferences.dohProvider,
entries = persistentMapOf( entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled), -1 to stringResource(MR.strings.disabled),
PREF_DOH_CLOUDFLARE to "Cloudflare", PREF_DOH_CLOUDFLARE to "Cloudflare",
@@ -368,12 +368,12 @@ object SettingsAdvancedScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.updateMangaTitles(), preference = libraryPreferences.updateMangaTitles,
title = stringResource(MR.strings.pref_update_library_manga_titles), title = stringResource(MR.strings.pref_update_library_manga_titles),
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary), subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.disallowNonAsciiFilenames(), preference = libraryPreferences.disallowNonAsciiFilenames,
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames), title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details), subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
), ),
@@ -390,7 +390,7 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_downloads), title = stringResource(MR.strings.pref_category_downloads),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.includeChapterUrlHash(), preference = downloadPreferences.includeChapterUrlHash,
title = stringResource(SYMR.strings.pref_include_chapter_url_hash), title = stringResource(SYMR.strings.pref_include_chapter_url_hash),
subtitle = stringResource(SYMR.strings.pref_include_chapter_url_hash_desc), subtitle = stringResource(SYMR.strings.pref_include_chapter_url_hash_desc),
), ),
@@ -410,14 +410,14 @@ object SettingsAdvancedScreen : SearchableSettings {
uri?.let { uri?.let {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags) context.contentResolver.takePersistableUriPermission(uri, flags)
basePreferences.displayProfile().set(uri.toString()) basePreferences.displayProfile.set(uri.toString())
} }
} }
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader), title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = basePreferences.hardwareBitmapThreshold(), preference = basePreferences.hardwareBitmapThreshold,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option -> .mapIndexed { index, option ->
val display = if (index == 0) { val display = if (index == 0) {
@@ -437,13 +437,13 @@ object SettingsAdvancedScreen : SearchableSettings {
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT, GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = basePreferences.alwaysDecodeLongStripWithSSIV(), preference = basePreferences.alwaysDecodeLongStripWithSSIV,
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2), title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary), subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile), title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(), subtitle = basePreferences.displayProfile.get(),
onClick = { onClick = {
chooseColorProfile.launch(arrayOf("*/*")) chooseColorProfile.launch(arrayOf("*/*"))
}, },
@@ -458,7 +458,7 @@ object SettingsAdvancedScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current val context = LocalContext.current
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val extensionInstallerPref = basePreferences.extensionInstaller() val extensionInstallerPref = basePreferences.extensionInstaller
var shizukuMissing by rememberSaveable { mutableStateOf(false) } var shizukuMissing by rememberSaveable { mutableStateOf(false) }
val trustExtension = remember { Injekt.get<TrustExtension>() } val trustExtension = remember { Injekt.get<TrustExtension>() }
@@ -658,12 +658,12 @@ object SettingsAdvancedScreen : SearchableSettings {
@Composable @Composable
private fun getDataSaverGroup(): Preference.PreferenceGroup { private fun getDataSaverGroup(): Preference.PreferenceGroup {
val sourcePreferences = remember { Injekt.get<SourcePreferences>() } val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val dataSaver by sourcePreferences.dataSaver().collectAsState() val dataSaver by sourcePreferences.dataSaver.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(SYMR.strings.data_saver), title = stringResource(SYMR.strings.data_saver),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = sourcePreferences.dataSaver(), preference = sourcePreferences.dataSaver,
title = stringResource(SYMR.strings.data_saver), title = stringResource(SYMR.strings.data_saver),
subtitle = stringResource(SYMR.strings.data_saver_summary), subtitle = stringResource(SYMR.strings.data_saver_summary),
entries = persistentMapOf( entries = persistentMapOf(
@@ -673,28 +673,28 @@ object SettingsAdvancedScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.EditTextPreference( Preference.PreferenceItem.EditTextPreference(
preference = sourcePreferences.dataSaverServer(), preference = sourcePreferences.dataSaverServer,
title = stringResource(SYMR.strings.bandwidth_data_saver_server), title = stringResource(SYMR.strings.bandwidth_data_saver_server),
subtitle = stringResource(SYMR.strings.data_saver_server_summary), subtitle = stringResource(SYMR.strings.data_saver_server_summary),
enabled = dataSaver == DataSaver.BANDWIDTH_HERO, enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.dataSaverDownloader(), preference = sourcePreferences.dataSaverDownloader,
title = stringResource(SYMR.strings.data_saver_downloader), title = stringResource(SYMR.strings.data_saver_downloader),
enabled = dataSaver != DataSaver.NONE, enabled = dataSaver != DataSaver.NONE,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.dataSaverIgnoreJpeg(), preference = sourcePreferences.dataSaverIgnoreJpeg,
title = stringResource(SYMR.strings.data_saver_ignore_jpeg), title = stringResource(SYMR.strings.data_saver_ignore_jpeg),
enabled = dataSaver != DataSaver.NONE, enabled = dataSaver != DataSaver.NONE,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.dataSaverIgnoreGif(), preference = sourcePreferences.dataSaverIgnoreGif,
title = stringResource(SYMR.strings.data_saver_ignore_gif), title = stringResource(SYMR.strings.data_saver_ignore_gif),
enabled = dataSaver != DataSaver.NONE, enabled = dataSaver != DataSaver.NONE,
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = sourcePreferences.dataSaverImageQuality(), preference = sourcePreferences.dataSaverImageQuality,
title = stringResource(SYMR.strings.data_saver_image_quality), title = stringResource(SYMR.strings.data_saver_image_quality),
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary), subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
entries = listOf( entries = listOf(
@@ -710,10 +710,10 @@ object SettingsAdvancedScreen : SearchableSettings {
enabled = dataSaver != DataSaver.NONE, enabled = dataSaver != DataSaver.NONE,
), ),
kotlin.run { kotlin.run {
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg() val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg
.collectAsState() .collectAsState()
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.dataSaverImageFormatJpeg(), preference = sourcePreferences.dataSaverImageFormatJpeg,
title = stringResource(SYMR.strings.data_saver_image_format), title = stringResource(SYMR.strings.data_saver_image_format),
subtitle = if (dataSaverImageFormatJpeg) { subtitle = if (dataSaverImageFormatJpeg) {
stringResource(SYMR.strings.data_saver_image_format_summary_on) stringResource(SYMR.strings.data_saver_image_format_summary_on)
@@ -724,7 +724,7 @@ object SettingsAdvancedScreen : SearchableSettings {
) )
}, },
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.dataSaverColorBW(), preference = sourcePreferences.dataSaverColorBW,
title = stringResource(SYMR.strings.data_saver_color_bw), title = stringResource(SYMR.strings.data_saver_color_bw),
enabled = dataSaver == DataSaver.BANDWIDTH_HERO, enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
), ),
@@ -744,7 +744,7 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(SYMR.strings.developer_tools), title = stringResource(SYMR.strings.developer_tools),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.isHentaiEnabled(), preference = exhPreferences.isHentaiEnabled,
title = stringResource(SYMR.strings.toggle_hentai_features), title = stringResource(SYMR.strings.toggle_hentai_features),
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary), subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
onValueChanged = { onValueChanged = {
@@ -759,7 +759,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = delegateSourcePreferences.delegateSources(), preference = delegateSourcePreferences.delegateSources,
title = stringResource(SYMR.strings.toggle_delegated_sources), title = stringResource(SYMR.strings.toggle_delegated_sources),
subtitle = stringResource( subtitle = stringResource(
SYMR.strings.toggle_delegated_sources_summary, SYMR.strings.toggle_delegated_sources_summary,
@@ -769,7 +769,7 @@ object SettingsAdvancedScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = exhPreferences.logLevel(), preference = exhPreferences.logLevel,
title = stringResource(SYMR.strings.log_level), title = stringResource(SYMR.strings.log_level),
subtitle = stringResource(SYMR.strings.log_level_summary), subtitle = stringResource(SYMR.strings.log_level_summary),
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel -> entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
@@ -779,7 +779,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}.toMap().toImmutableMap(), }.toMap().toImmutableMap(),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.enableSourceBlacklist(), preference = sourcePreferences.enableSourceBlacklist,
title = stringResource(SYMR.strings.enable_source_blacklist), title = stringResource(SYMR.strings.enable_source_blacklist),
subtitle = stringResource( subtitle = stringResource(
SYMR.strings.enable_source_blacklist_summary, SYMR.strings.enable_source_blacklist_summary,
@@ -813,7 +813,7 @@ object SettingsAdvancedScreen : SearchableSettings {
TextButton( TextButton(
onClick = { onClick = {
dismiss() dismiss()
securityPreferences.encryptDatabase().set(true) securityPreferences.encryptDatabase.set(true)
}, },
) { ) {
Text(text = stringResource(MR.strings.action_ok)) Text(text = stringResource(MR.strings.action_ok))
@@ -823,7 +823,7 @@ object SettingsAdvancedScreen : SearchableSettings {
} }
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
title = stringResource(SYMR.strings.encrypt_database), title = stringResource(SYMR.strings.encrypt_database),
preference = securityPreferences.encryptDatabase(), preference = securityPreferences.encryptDatabase,
subtitle = stringResource(SYMR.strings.encrypt_database_subtitle), subtitle = stringResource(SYMR.strings.encrypt_database_subtitle),
onValueChanged = { onValueChanged = {
if (it) { if (it) {
@@ -56,13 +56,13 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current val context = LocalContext.current
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState() val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled() val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState() val amoled by amoledPref.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
@@ -109,7 +109,7 @@ object SettingsAppearanceScreen : SearchableSettings {
val now = remember { LocalDate.now() } val now = remember { LocalDate.now() }
val dateFormat by uiPreferences.dateFormat().collectAsState() val dateFormat by uiPreferences.dateFormat.collectAsState()
val formattedNow = remember(dateFormat) { val formattedNow = remember(dateFormat) {
UiPreferences.dateFormat(dateFormat).format(now) UiPreferences.dateFormat(dateFormat).format(now)
} }
@@ -122,7 +122,7 @@ object SettingsAppearanceScreen : SearchableSettings {
onClick = { navigator.push(AppLanguageScreen()) }, onClick = { navigator.push(AppLanguageScreen()) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = uiPreferences.tabletUiMode(), preference = uiPreferences.tabletUiMode,
entries = TabletUiMode.entries entries = TabletUiMode.entries
.associateWith { stringResource(it.titleRes) } .associateWith { stringResource(it.titleRes) }
.toImmutableMap(), .toImmutableMap(),
@@ -133,7 +133,7 @@ object SettingsAppearanceScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = uiPreferences.dateFormat(), preference = uiPreferences.dateFormat,
entries = DateFormats entries = DateFormats
.associateWith { .associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now) val formattedDate = UiPreferences.dateFormat(it).format(now)
@@ -143,7 +143,7 @@ object SettingsAppearanceScreen : SearchableSettings {
title = stringResource(MR.strings.pref_date_format), title = stringResource(MR.strings.pref_date_format),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.relativeTime(), preference = uiPreferences.relativeTime,
title = stringResource(MR.strings.pref_relative_format), title = stringResource(MR.strings.pref_relative_format),
subtitle = stringResource( subtitle = stringResource(
MR.strings.pref_relative_format_summary, MR.strings.pref_relative_format_summary,
@@ -152,7 +152,7 @@ object SettingsAppearanceScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.imagesInDescription(), preference = uiPreferences.imagesInDescription,
title = stringResource(MR.strings.pref_display_images_description), title = stringResource(MR.strings.pref_display_images_description),
), ),
), ),
@@ -162,22 +162,22 @@ object SettingsAppearanceScreen : SearchableSettings {
// SY --> // SY -->
@Composable @Composable
fun getForkGroup(uiPreferences: UiPreferences): Preference.PreferenceGroup { fun getForkGroup(uiPreferences: UiPreferences): Preference.PreferenceGroup {
val previewsRowCount by uiPreferences.previewsRowCount().collectAsState() val previewsRowCount by uiPreferences.previewsRowCount.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
stringResource(SYMR.strings.pref_category_fork), stringResource(SYMR.strings.pref_category_fork),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.expandFilters(), preference = uiPreferences.expandFilters,
title = stringResource(SYMR.strings.toggle_expand_search_filters), title = stringResource(SYMR.strings.toggle_expand_search_filters),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.recommendsInOverflow(), preference = uiPreferences.recommendsInOverflow,
title = stringResource(SYMR.strings.put_recommends_in_overflow), title = stringResource(SYMR.strings.put_recommends_in_overflow),
subtitle = stringResource(SYMR.strings.put_recommends_in_overflow_summary), subtitle = stringResource(SYMR.strings.put_recommends_in_overflow_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.mergeInOverflow(), preference = uiPreferences.mergeInOverflow,
title = stringResource(SYMR.strings.put_merge_in_overflow), title = stringResource(SYMR.strings.put_merge_in_overflow),
subtitle = stringResource(SYMR.strings.put_merge_in_overflow_summary), subtitle = stringResource(SYMR.strings.put_merge_in_overflow_summary),
), ),
@@ -195,7 +195,7 @@ object SettingsAppearanceScreen : SearchableSettings {
}, },
valueRange = 0..10, valueRange = 0..10,
onValueChanged = { onValueChanged = {
uiPreferences.previewsRowCount().set(it) uiPreferences.previewsRowCount.set(it)
true true
}, },
), ),
@@ -209,15 +209,15 @@ object SettingsAppearanceScreen : SearchableSettings {
stringResource(SYMR.strings.pref_category_navbar), stringResource(SYMR.strings.pref_category_navbar),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.showNavUpdates(), preference = uiPreferences.showNavUpdates,
title = stringResource(SYMR.strings.pref_hide_updates_button), title = stringResource(SYMR.strings.pref_hide_updates_button),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.showNavHistory(), preference = uiPreferences.showNavHistory,
title = stringResource(SYMR.strings.pref_hide_history_button), title = stringResource(SYMR.strings.pref_hide_history_button),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.bottomBarLabels(), preference = uiPreferences.bottomBarLabels,
title = stringResource(SYMR.strings.pref_show_bottom_bar_labels), title = stringResource(SYMR.strings.pref_show_bottom_bar_labels),
), ),
), ),
@@ -46,7 +46,7 @@ object SettingsBrowseScreen : SearchableSettings {
// SY --> // SY -->
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab().asState(scope) } val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab.asState(scope) }
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
// SY <-- // SY <--
return listOf( return listOf(
@@ -55,7 +55,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.label_sources), title = stringResource(MR.strings.label_sources),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
kotlin.run { kotlin.run {
val count by sourcePreferences.sourcesTabCategories().collectAsState() val count by sourcePreferences.sourcesTabCategories.collectAsState()
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.action_edit_categories), title = stringResource(MR.strings.action_edit_categories),
subtitle = pluralStringResource(MR.plurals.num_categories, count.size, count.size), subtitle = pluralStringResource(MR.plurals.num_categories, count.size, count.size),
@@ -65,17 +65,17 @@ object SettingsBrowseScreen : SearchableSettings {
) )
}, },
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.sourcesTabCategoriesFilter(), preference = sourcePreferences.sourcesTabCategoriesFilter,
title = stringResource(SYMR.strings.pref_source_source_filtering), title = stringResource(SYMR.strings.pref_source_source_filtering),
subtitle = stringResource(SYMR.strings.pref_source_source_filtering_summery), subtitle = stringResource(SYMR.strings.pref_source_source_filtering_summery),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.useNewSourceNavigation(), preference = uiPreferences.useNewSourceNavigation,
title = stringResource(SYMR.strings.pref_source_navigation), title = stringResource(SYMR.strings.pref_source_navigation),
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery), subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.allowLocalSourceHiddenFolders(), preference = sourcePreferences.allowLocalSourceHiddenFolders,
title = stringResource(SYMR.strings.pref_local_source_hidden_folders), title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery), subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
), ),
@@ -85,11 +85,11 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(SYMR.strings.feed), title = stringResource(SYMR.strings.feed),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.hideFeedTab(), preference = uiPreferences.hideFeedTab,
title = stringResource(SYMR.strings.pref_hide_feed), title = stringResource(SYMR.strings.pref_hide_feed),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.feedTabInFront(), preference = uiPreferences.feedTabInFront,
title = stringResource(SYMR.strings.pref_feed_position), title = stringResource(SYMR.strings.pref_feed_position),
subtitle = stringResource(SYMR.strings.pref_feed_position_summery), subtitle = stringResource(SYMR.strings.pref_feed_position_summery),
enabled = hideFeedTab.not(), enabled = hideFeedTab.not(),
@@ -101,7 +101,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.label_sources), title = stringResource(MR.strings.label_sources),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.hideInLibraryItems(), preference = sourcePreferences.hideInLibraryItems,
title = stringResource(MR.strings.pref_hide_in_library_items), title = stringResource(MR.strings.pref_hide_in_library_items),
), ),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
@@ -117,7 +117,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_nsfw_content), title = stringResource(MR.strings.pref_category_nsfw_content),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.showNsfwSource(), preference = sourcePreferences.showNsfwSource,
title = stringResource(MR.strings.pref_show_nsfw_source), title = stringResource(MR.strings.pref_show_nsfw_source),
subtitle = stringResource(MR.strings.requires_app_restart), subtitle = stringResource(MR.strings.requires_app_restart),
onValueChanged = { onValueChanged = {
@@ -119,7 +119,7 @@ object SettingsDataScreen : SearchableSettings {
val storagePreferences = Injekt.get<StoragePreferences>() val storagePreferences = Injekt.get<StoragePreferences>()
val syncPreferences = remember { Injekt.get<SyncPreferences>() } val syncPreferences = remember { Injekt.get<SyncPreferences>() }
val syncService by syncPreferences.syncService().collectAsState() val syncService by syncPreferences.syncService.collectAsState()
return persistentListOf( return persistentListOf(
getStorageLocationPref(storagePreferences = storagePreferences), getStorageLocationPref(storagePreferences = storagePreferences),
@@ -185,11 +185,11 @@ object SettingsDataScreen : SearchableSettings {
storagePreferences: StoragePreferences, storagePreferences: StoragePreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current val context = LocalContext.current
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory()) val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory)
return Preference.PreferenceItem.TextPreference( return Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_storage_location), title = stringResource(MR.strings.pref_storage_location),
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()), subtitle = storageLocationText(storagePreferences.baseStorageDirectory),
onClick = { onClick = {
try { try {
pickStorageLocation.launch(null) pickStorageLocation.launch(null)
@@ -205,7 +205,7 @@ object SettingsDataScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState() val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp.collectAsState()
val chooseBackup = rememberLauncherForActivityResult( val chooseBackup = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() { object : ActivityResultContracts.GetContent() {
@@ -272,7 +272,7 @@ object SettingsDataScreen : SearchableSettings {
// Automatic backups // Automatic backups
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = backupPreferences.backupInterval(), preference = backupPreferences.backupInterval,
entries = persistentMapOf( entries = persistentMapOf(
0 to stringResource(MR.strings.off), 0 to stringResource(MR.strings.off),
6 to stringResource(MR.strings.update_6hour), 6 to stringResource(MR.strings.update_6hour),
@@ -365,7 +365,7 @@ object SettingsDataScreen : SearchableSettings {
), ),
// SY <-- // SY <--
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoClearChapterCache(), preference = libraryPreferences.autoClearChapterCache,
title = stringResource(MR.strings.pref_auto_clear_chapter_cache), title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
), ),
), ),
@@ -517,7 +517,7 @@ object SettingsDataScreen : SearchableSettings {
title = stringResource(SYMR.strings.pref_sync_service_category), title = stringResource(SYMR.strings.pref_sync_service_category),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = syncPreferences.syncService(), preference = syncPreferences.syncService,
title = stringResource(SYMR.strings.pref_sync_service), title = stringResource(SYMR.strings.pref_sync_service),
entries = persistentMapOf( entries = persistentMapOf(
SyncManager.SyncService.NONE.value to stringResource(MR.strings.off), SyncManager.SyncService.NONE.value to stringResource(MR.strings.off),
@@ -660,7 +660,7 @@ object SettingsDataScreen : SearchableSettings {
val qrScanLauncher = rememberLauncherForActivityResult(ScanContract()) { val qrScanLauncher = rememberLauncherForActivityResult(ScanContract()) {
if (it.contents != null && it.contents.isNotEmpty()) { if (it.contents != null && it.contents.isNotEmpty()) {
syncPreferences.clientAPIKey().set(it.contents) syncPreferences.clientAPIKey.set(it.contents)
} }
} }
val context = LocalContext.current val context = LocalContext.current
@@ -677,13 +677,13 @@ object SettingsDataScreen : SearchableSettings {
Preference.PreferenceItem.EditTextPreference( Preference.PreferenceItem.EditTextPreference(
title = stringResource(SYMR.strings.pref_sync_host), title = stringResource(SYMR.strings.pref_sync_host),
subtitle = stringResource(SYMR.strings.pref_sync_host_summ), subtitle = stringResource(SYMR.strings.pref_sync_host_summ),
preference = syncPreferences.clientHost(), preference = syncPreferences.clientHost,
onValueChanged = { newValue -> onValueChanged = { newValue ->
scope.launch { scope.launch {
// Trim spaces at the beginning and end, then remove trailing slash if present // Trim spaces at the beginning and end, then remove trailing slash if present
val trimmedValue = newValue.trim() val trimmedValue = newValue.trim()
val modifiedValue = trimmedValue.trimEnd { it == '/' } val modifiedValue = trimmedValue.trimEnd { it == '/' }
syncPreferences.clientHost().set(modifiedValue) syncPreferences.clientHost.set(modifiedValue)
} }
true true
}, },
@@ -691,12 +691,12 @@ object SettingsDataScreen : SearchableSettings {
Preference.PreferenceItem.CustomPreference( Preference.PreferenceItem.CustomPreference(
title = stringResource(SYMR.strings.pref_sync_api_key), title = stringResource(SYMR.strings.pref_sync_api_key),
) { ) {
val values by syncPreferences.clientAPIKey().collectAsState() val values by syncPreferences.clientAPIKey.collectAsState()
EditTextPreferenceWidget( EditTextPreferenceWidget(
title = stringResource(SYMR.strings.pref_sync_api_key), title = stringResource(SYMR.strings.pref_sync_api_key),
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ), subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
onConfirm = { onConfirm = {
syncPreferences.clientAPIKey().set(it) syncPreferences.clientAPIKey.set(it)
true true
}, },
icon = null, icon = null,
@@ -752,8 +752,8 @@ object SettingsDataScreen : SearchableSettings {
@Composable @Composable
private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup { private fun getAutomaticSyncGroup(syncPreferences: SyncPreferences): Preference.PreferenceGroup {
val context = LocalContext.current val context = LocalContext.current
val syncIntervalPref = syncPreferences.syncInterval() val syncIntervalPref = syncPreferences.syncInterval
val lastSync by syncPreferences.lastSyncTimestamp().collectAsState() val lastSync by syncPreferences.lastSyncTimestamp.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(SYMR.strings.pref_sync_automatic_category), title = stringResource(SYMR.strings.pref_sync_automatic_category),
@@ -37,19 +37,19 @@ object SettingsDownloadScreen : SearchableSettings {
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList()) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() } val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState() val parallelSourceLimit by downloadPreferences.parallelSourceLimit.collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState() val parallelPageLimit by downloadPreferences.parallelPageLimit.collectAsState()
return listOf( return listOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.downloadOnlyOverWifi(), preference = downloadPreferences.downloadOnlyOverWifi,
title = stringResource(MR.strings.connected_to_wifi), title = stringResource(MR.strings.connected_to_wifi),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.saveChaptersAsCBZ(), preference = downloadPreferences.saveChaptersAsCBZ,
title = stringResource(MR.strings.save_chapter_as_cbz), title = stringResource(MR.strings.save_chapter_as_cbz),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.splitTallImages(), preference = downloadPreferences.splitTallImages,
title = stringResource(MR.strings.split_tall_images), title = stringResource(MR.strings.split_tall_images),
subtitle = stringResource(MR.strings.split_tall_images_summary), subtitle = stringResource(MR.strings.split_tall_images_summary),
), ),
@@ -57,14 +57,14 @@ object SettingsDownloadScreen : SearchableSettings {
value = parallelSourceLimit, value = parallelSourceLimit,
valueRange = 1..10, valueRange = 1..10,
title = stringResource(MR.strings.pref_download_concurrent_sources), title = stringResource(MR.strings.pref_download_concurrent_sources),
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) }, onValueChanged = { downloadPreferences.parallelSourceLimit.set(it) },
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = parallelPageLimit, value = parallelPageLimit,
valueRange = 1..15, valueRange = 1..15,
title = stringResource(MR.strings.pref_download_concurrent_pages), title = stringResource(MR.strings.pref_download_concurrent_pages),
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary), subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) }, onValueChanged = { downloadPreferences.parallelPageLimit.set(it) },
), ),
getDeleteChaptersGroup( getDeleteChaptersGroup(
downloadPreferences = downloadPreferences, downloadPreferences = downloadPreferences,
@@ -87,11 +87,11 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_delete_chapters), title = stringResource(MR.strings.pref_category_delete_chapters),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeAfterMarkedAsRead(), preference = downloadPreferences.removeAfterMarkedAsRead,
title = stringResource(MR.strings.pref_remove_after_marked_as_read), title = stringResource(MR.strings.pref_remove_after_marked_as_read),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.removeAfterReadSlots(), preference = downloadPreferences.removeAfterReadSlots,
entries = persistentMapOf( entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled), -1 to stringResource(MR.strings.disabled),
0 to stringResource(MR.strings.last_read_chapter), 0 to stringResource(MR.strings.last_read_chapter),
@@ -103,7 +103,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_remove_after_read), title = stringResource(MR.strings.pref_remove_after_read),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeBookmarkedChapters(), preference = downloadPreferences.removeBookmarkedChapters,
title = stringResource(MR.strings.pref_remove_bookmarked_chapters), title = stringResource(MR.strings.pref_remove_bookmarked_chapters),
), ),
getExcludedCategoriesPreference( getExcludedCategoriesPreference(
@@ -120,7 +120,7 @@ object SettingsDownloadScreen : SearchableSettings {
categories: () -> List<Category>, categories: () -> List<Category>,
): Preference.PreferenceItem.MultiSelectListPreference { ): Preference.PreferenceItem.MultiSelectListPreference {
return Preference.PreferenceItem.MultiSelectListPreference( return Preference.PreferenceItem.MultiSelectListPreference(
preference = downloadPreferences.removeExcludeCategories(), preference = downloadPreferences.removeExcludeCategories,
entries = categories() entries = categories()
.associate { it.id.toString() to it.visualName } .associate { it.id.toString() to it.visualName }
.toImmutableMap(), .toImmutableMap(),
@@ -133,10 +133,10 @@ object SettingsDownloadScreen : SearchableSettings {
downloadPreferences: DownloadPreferences, downloadPreferences: DownloadPreferences,
allCategories: List<Category>, allCategories: List<Category>,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters() val downloadNewChaptersPref = downloadPreferences.downloadNewChapters
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly() val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories() val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude() val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude
val downloadNewChapters by downloadNewChaptersPref.collectAsState() val downloadNewChapters by downloadNewChaptersPref.collectAsState()
@@ -194,7 +194,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.download_ahead), title = stringResource(MR.strings.download_ahead),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.autoDownloadWhileReading(), preference = downloadPreferences.autoDownloadWhileReading,
entries = listOf(0, 2, 3, 5, 10) entries = listOf(0, 2, 3, 5, 10)
.associateWith { .associateWith {
if (it == 0) { if (it == 0) {
@@ -88,7 +88,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
override fun getTitleRes() = SYMR.strings.pref_category_eh override fun getTitleRes() = SYMR.strings.pref_category_eh
override fun isEnabled(): Boolean = Injekt.get<ExhPreferences>().isHentaiEnabled().get() override fun isEnabled(): Boolean = Injekt.get<ExhPreferences>().isHentaiEnabled.get()
@Composable @Composable
fun Reconfigure( fun Reconfigure(
@@ -96,14 +96,14 @@ object SettingsEhScreen : SearchableSettings {
openWarnConfigureDialogController: () -> Unit, openWarnConfigureDialogController: () -> Unit,
) { ) {
var initialLoadGuard by remember { mutableStateOf(false) } var initialLoadGuard by remember { mutableStateOf(false) }
val useHentaiAtHome by exhPreferences.useHentaiAtHome().collectAsState() val useHentaiAtHome by exhPreferences.useHentaiAtHome.collectAsState()
val useJapaneseTitle by exhPreferences.useJapaneseTitle().collectAsState() val useJapaneseTitle by exhPreferences.useJapaneseTitle.collectAsState()
val useOriginalImages by exhPreferences.exhUseOriginalImages().collectAsState() val useOriginalImages by exhPreferences.exhUseOriginalImages.collectAsState()
val ehTagFilterValue by exhPreferences.ehTagFilterValue().collectAsState() val ehTagFilterValue by exhPreferences.ehTagFilterValue.collectAsState()
val ehTagWatchingValue by exhPreferences.ehTagWatchingValue().collectAsState() val ehTagWatchingValue by exhPreferences.ehTagWatchingValue.collectAsState()
val settingsLanguages by exhPreferences.exhSettingsLanguages().collectAsState() val settingsLanguages by exhPreferences.exhSettingsLanguages.collectAsState()
val enabledCategories by exhPreferences.exhEnabledCategories().collectAsState() val enabledCategories by exhPreferences.exhEnabledCategories.collectAsState()
val imageQuality by exhPreferences.imageQuality().collectAsState() val imageQuality by exhPreferences.imageQuality.collectAsState()
DisposableEffect( DisposableEffect(
useHentaiAtHome, useHentaiAtHome,
useJapaneseTitle, useJapaneseTitle,
@@ -128,7 +128,7 @@ object SettingsEhScreen : SearchableSettings {
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() } val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() } val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() } val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
val exhentaiEnabled by exhPreferences.enableExhentai().collectAsState() val exhentaiEnabled by exhPreferences.enableExhentai.collectAsState()
var runConfigureDialog by remember { mutableStateOf(false) } var runConfigureDialog by remember { mutableStateOf(false) }
val openWarnConfigureDialogController = { runConfigureDialog = true } val openWarnConfigureDialogController = { runConfigureDialog = true }
@@ -191,9 +191,9 @@ object SettingsEhScreen : SearchableSettings {
} }
} }
val context = LocalContext.current val context = LocalContext.current
val value by exhPreferences.enableExhentai().collectAsState() val value by exhPreferences.enableExhentai.collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.enableExhentai(), preference = exhPreferences.enableExhentai,
title = stringResource(SYMR.strings.enable_exhentai), title = stringResource(SYMR.strings.enable_exhentai),
subtitle = if (!value) { subtitle = if (!value) {
stringResource(SYMR.strings.requires_login) stringResource(SYMR.strings.requires_login)
@@ -202,7 +202,7 @@ object SettingsEhScreen : SearchableSettings {
}, },
onValueChanged = { newVal -> onValueChanged = { newVal ->
if (!newVal) { if (!newVal) {
exhPreferences.enableExhentai().set(false) exhPreferences.enableExhentai.set(false)
true true
} else { } else {
activityResultContract.launch(EhLoginActivity.newIntent(context)) activityResultContract.launch(EhLoginActivity.newIntent(context))
@@ -218,7 +218,7 @@ object SettingsEhScreen : SearchableSettings {
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<Int> { ): Preference.PreferenceItem.ListPreference<Int> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = exhPreferences.useHentaiAtHome(), preference = exhPreferences.useHentaiAtHome,
title = stringResource(SYMR.strings.use_hentai_at_home), title = stringResource(SYMR.strings.use_hentai_at_home),
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary), subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
entries = persistentMapOf( entries = persistentMapOf(
@@ -234,9 +234,9 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
val value by exhPreferences.useJapaneseTitle().collectAsState() val value by exhPreferences.useJapaneseTitle.collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.useJapaneseTitle(), preference = exhPreferences.useJapaneseTitle,
title = stringResource(SYMR.strings.show_japanese_titles), title = stringResource(SYMR.strings.show_japanese_titles),
subtitle = if (value) { subtitle = if (value) {
stringResource(SYMR.strings.show_japanese_titles_option_1) stringResource(SYMR.strings.show_japanese_titles_option_1)
@@ -252,9 +252,9 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
val value by exhPreferences.exhUseOriginalImages().collectAsState() val value by exhPreferences.exhUseOriginalImages.collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.exhUseOriginalImages(), preference = exhPreferences.exhUseOriginalImages,
title = stringResource(SYMR.strings.use_original_images), title = stringResource(SYMR.strings.use_original_images),
subtitle = if (value) { subtitle = if (value) {
stringResource(SYMR.strings.use_original_images_on) stringResource(SYMR.strings.use_original_images_on)
@@ -353,7 +353,7 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by exhPreferences.ehTagFilterValue().collectAsState() val value by exhPreferences.ehTagFilterValue.collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
TagThresholdDialog( TagThresholdDialog(
@@ -364,7 +364,7 @@ object SettingsEhScreen : SearchableSettings {
outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error), outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error),
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
exhPreferences.ehTagFilterValue().set(it) exhPreferences.ehTagFilterValue.set(it)
}, },
) )
} }
@@ -383,7 +383,7 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by exhPreferences.ehTagWatchingValue().collectAsState() val value by exhPreferences.ehTagWatchingValue.collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
TagThresholdDialog( TagThresholdDialog(
@@ -394,7 +394,7 @@ object SettingsEhScreen : SearchableSettings {
outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error), outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error),
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
exhPreferences.ehTagWatchingValue().set(it) exhPreferences.ehTagWatchingValue.set(it)
}, },
) )
} }
@@ -606,7 +606,7 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by exhPreferences.exhSettingsLanguages().collectAsState() val value by exhPreferences.exhSettingsLanguages.collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
LanguagesDialog( LanguagesDialog(
@@ -614,7 +614,7 @@ object SettingsEhScreen : SearchableSettings {
initialValue = value, initialValue = value,
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
exhPreferences.exhSettingsLanguages().set(it) exhPreferences.exhSettingsLanguages.set(it)
}, },
) )
} }
@@ -772,7 +772,7 @@ object SettingsEhScreen : SearchableSettings {
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by exhPreferences.exhEnabledCategories().collectAsState() val value by exhPreferences.exhEnabledCategories.collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
FrontPageCategoriesDialog( FrontPageCategoriesDialog(
@@ -780,7 +780,7 @@ object SettingsEhScreen : SearchableSettings {
initialValue = value, initialValue = value,
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
exhPreferences.exhEnabledCategories().set(it) exhPreferences.exhEnabledCategories.set(it)
}, },
) )
} }
@@ -800,7 +800,7 @@ object SettingsEhScreen : SearchableSettings {
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.exhWatchedListDefaultState(), preference = exhPreferences.exhWatchedListDefaultState,
title = stringResource(SYMR.strings.watched_list_default), title = stringResource(SYMR.strings.watched_list_default),
subtitle = stringResource(SYMR.strings.watched_list_state_summary), subtitle = stringResource(SYMR.strings.watched_list_state_summary),
enabled = exhentaiEnabled, enabled = exhentaiEnabled,
@@ -813,7 +813,7 @@ object SettingsEhScreen : SearchableSettings {
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<String> { ): Preference.PreferenceItem.ListPreference<String> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = exhPreferences.imageQuality(), preference = exhPreferences.imageQuality,
title = stringResource(SYMR.strings.eh_image_quality_summary), title = stringResource(SYMR.strings.eh_image_quality_summary),
subtitle = stringResource(SYMR.strings.eh_image_quality), subtitle = stringResource(SYMR.strings.eh_image_quality),
entries = persistentMapOf( entries = persistentMapOf(
@@ -831,7 +831,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun enhancedEhentaiView(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference { fun enhancedEhentaiView(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.enhancedEHentaiView(), preference = exhPreferences.enhancedEHentaiView,
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view), title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary), subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
) )
@@ -840,7 +840,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun readOnlySync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference { fun readOnlySync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.exhReadOnlySync(), preference = exhPreferences.exhReadOnlySync,
title = stringResource(SYMR.strings.disable_favorites_uploading), title = stringResource(SYMR.strings.disable_favorites_uploading),
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary), subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
) )
@@ -865,7 +865,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun lenientSync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference { fun lenientSync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = exhPreferences.exhLenientSync(), preference = exhPreferences.exhLenientSync,
title = stringResource(SYMR.strings.ignore_sync_errors), title = stringResource(SYMR.strings.ignore_sync_errors),
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary), subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
) )
@@ -937,10 +937,10 @@ object SettingsEhScreen : SearchableSettings {
fun updateCheckerFrequency( fun updateCheckerFrequency(
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<Int> { ): Preference.PreferenceItem.ListPreference<Int> {
val value by exhPreferences.exhAutoUpdateFrequency().collectAsState() val value by exhPreferences.exhAutoUpdateFrequency.collectAsState()
val context = LocalContext.current val context = LocalContext.current
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = exhPreferences.exhAutoUpdateFrequency(), preference = exhPreferences.exhAutoUpdateFrequency,
title = stringResource(SYMR.strings.time_between_batches), title = stringResource(SYMR.strings.time_between_batches),
subtitle = if (value == 0) { subtitle = if (value == 0) {
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name)) stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
@@ -973,10 +973,10 @@ object SettingsEhScreen : SearchableSettings {
fun autoUpdateRequirements( fun autoUpdateRequirements(
exhPreferences: ExhPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.MultiSelectListPreference { ): Preference.PreferenceItem.MultiSelectListPreference {
val value by exhPreferences.exhAutoUpdateRequirements().collectAsState() val value by exhPreferences.exhAutoUpdateRequirements.collectAsState()
val context = LocalContext.current val context = LocalContext.current
return Preference.PreferenceItem.MultiSelectListPreference( return Preference.PreferenceItem.MultiSelectListPreference(
preference = exhPreferences.exhAutoUpdateRequirements(), preference = exhPreferences.exhAutoUpdateRequirements,
title = stringResource(SYMR.strings.auto_update_restrictions), title = stringResource(SYMR.strings.auto_update_restrictions),
subtitle = remember(value) { subtitle = remember(value) {
context.stringResource( context.stringResource(
@@ -1150,7 +1150,7 @@ object SettingsEhScreen : SearchableSettings {
value = withIOContext { value = withIOContext {
try { try {
val stats = val stats =
exhPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let { exhPreferences.exhAutoUpdateStats.get().nullIfBlank()?.let {
Json.decodeFromString<EHentaiUpdaterStats>(it) Json.decodeFromString<EHentaiUpdaterStats>(it)
} }
@@ -79,7 +79,7 @@ object SettingsLibraryScreen : SearchableSettings {
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
// For default category // For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) + val ids = listOf(libraryPreferences.defaultCategory.defaultValue()) +
allCategories.fastMap { it.id.toInt() } allCategories.fastMap { it.id.toInt() }
val labels = listOf(stringResource(MR.strings.default_category_summary)) + val labels = listOf(stringResource(MR.strings.default_category_summary)) +
allCategories.fastMap { it.visualName } allCategories.fastMap { it.visualName }
@@ -97,12 +97,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { navigator.push(CategoryScreen()) }, onClick = { navigator.push(CategoryScreen()) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.defaultCategory(), preference = libraryPreferences.defaultCategory,
entries = ids.zip(labels).toMap().toImmutableMap(), entries = ids.zip(labels).toMap().toImmutableMap(),
title = stringResource(MR.strings.default_category), title = stringResource(MR.strings.default_category),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.categorizedDisplaySettings(), preference = libraryPreferences.categorizedDisplaySettings,
title = stringResource(MR.strings.categorized_display_settings), title = stringResource(MR.strings.categorized_display_settings),
onValueChanged = { onValueChanged = {
if (!it) { if (!it) {
@@ -124,9 +124,9 @@ object SettingsLibraryScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current val context = LocalContext.current
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval() val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval
val autoUpdateCategoriesPref = libraryPreferences.updateCategories() val autoUpdateCategoriesPref = libraryPreferences.updateCategories
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude() val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude
val autoUpdateInterval by autoUpdateIntervalPref.collectAsState() val autoUpdateInterval by autoUpdateIntervalPref.collectAsState()
@@ -170,7 +170,7 @@ object SettingsLibraryScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateDeviceRestrictions(), preference = libraryPreferences.autoUpdateDeviceRestrictions,
entries = persistentMapOf( entries = persistentMapOf(
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi), DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered), DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
@@ -196,7 +196,7 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
// SY --> // SY -->
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.groupLibraryUpdateType(), preference = libraryPreferences.groupLibraryUpdateType,
title = stringResource(SYMR.strings.library_group_updates), title = stringResource(SYMR.strings.library_group_updates),
entries = persistentMapOf( entries = persistentMapOf(
GroupLibraryMode.GLOBAL to stringResource(SYMR.strings.library_group_updates_global), GroupLibraryMode.GLOBAL to stringResource(SYMR.strings.library_group_updates_global),
@@ -207,12 +207,12 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
// SY <-- // SY <--
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoUpdateMetadata(), preference = libraryPreferences.autoUpdateMetadata,
title = stringResource(MR.strings.pref_library_update_refresh_metadata), title = stringResource(MR.strings.pref_library_update_refresh_metadata),
subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary), subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary),
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateMangaRestrictions(), preference = libraryPreferences.autoUpdateMangaRestrictions,
entries = persistentMapOf( entries = persistentMapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read), MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started), MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
@@ -222,7 +222,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_library_update_smart_update), title = stringResource(MR.strings.pref_library_update_smart_update),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.newShowUpdatesCount(), preference = libraryPreferences.newShowUpdatesCount,
title = stringResource(MR.strings.pref_library_update_show_tab_badge), title = stringResource(MR.strings.pref_library_update_show_tab_badge),
), ),
), ),
@@ -237,7 +237,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_behavior), title = stringResource(MR.strings.pref_behavior),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToStartAction(), preference = libraryPreferences.swipeToStartAction,
entries = persistentMapOf( entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled), stringResource(MR.strings.disabled),
@@ -251,7 +251,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_start), title = stringResource(MR.strings.pref_chapter_swipe_start),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToEndAction(), preference = libraryPreferences.swipeToEndAction,
entries = persistentMapOf( entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled), stringResource(MR.strings.disabled),
@@ -265,7 +265,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_end), title = stringResource(MR.strings.pref_chapter_swipe_end),
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.markDuplicateReadChapterAsRead(), preference = libraryPreferences.markDuplicateReadChapterAsRead,
entries = persistentMapOf( entries = persistentMapOf(
MARK_DUPLICATE_CHAPTER_READ_EXISTING to MARK_DUPLICATE_CHAPTER_READ_EXISTING to
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing), stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
@@ -281,7 +281,7 @@ object SettingsLibraryScreen : SearchableSettings {
// SY --> // SY -->
@Composable @Composable
fun getSortingCategory(navigator: Navigator, libraryPreferences: LibraryPreferences): Preference.PreferenceGroup { fun getSortingCategory(navigator: Navigator, libraryPreferences: LibraryPreferences): Preference.PreferenceGroup {
val tagCount by libraryPreferences.sortTagsForLibrary().collectAsState() val tagCount by libraryPreferences.sortTagsForLibrary.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
stringResource(SYMR.strings.pref_sorting_settings), stringResource(SYMR.strings.pref_sorting_settings),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
@@ -175,7 +175,7 @@ object SettingsMangadexScreen : SearchableSettings {
sourcePreferences: SourcePreferences, sourcePreferences: SourcePreferences,
): Preference.PreferenceItem.ListPreference<String> { ): Preference.PreferenceItem.ListPreference<String> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = sourcePreferences.preferredMangaDexId(), preference = sourcePreferences.preferredMangaDexId,
title = stringResource(SYMR.strings.mangadex_preffered_source), title = stringResource(SYMR.strings.mangadex_preffered_source),
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary), subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
entries = MdUtil.getEnabledMangaDexs(sourcePreferences) entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
@@ -255,7 +255,7 @@ object SettingsMangadexScreen : SearchableSettings {
onDismissRequest = { dialogOpen = false }, onDismissRequest = { dialogOpen = false },
onSelectionConfirmed = { items -> onSelectionConfirmed = { items ->
dialogOpen = false dialogOpen = false
sourcePreferences.mangadexSyncToLibraryIndexes().set( sourcePreferences.mangadexSyncToLibraryIndexes.set(
List(items.size) { index -> (index + 1).toString() }.toSet(), List(items.size) { index -> (index + 1).toString() }.toSet(),
) )
LibraryUpdateJob.startNow( LibraryUpdateJob.startNow(
@@ -34,19 +34,19 @@ object SettingsReaderScreen : SearchableSettings {
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val readerPref = remember { Injekt.get<ReaderPreferences>() } val readerPref = remember { Injekt.get<ReaderPreferences>() }
// SY --> // SY -->
val forceHorizontalSeekbar by readerPref.forceHorizontalSeekbar().collectAsState() val forceHorizontalSeekbar by readerPref.forceHorizontalSeekbar.collectAsState()
// SY <-- // SY <--
return listOf( return listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPref.defaultReadingMode(), preference = readerPref.defaultReadingMode,
entries = ReadingMode.entries.drop(1) entries = ReadingMode.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) } .associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(), .toImmutableMap(),
title = stringResource(MR.strings.pref_viewer_type), title = stringResource(MR.strings.pref_viewer_type),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPref.doubleTapAnimSpeed(), preference = readerPref.doubleTapAnimSpeed,
entries = persistentMapOf( entries = persistentMapOf(
1 to stringResource(MR.strings.double_tap_anim_speed_0), 1 to stringResource(MR.strings.double_tap_anim_speed_0),
500 to stringResource(MR.strings.double_tap_anim_speed_normal), 500 to stringResource(MR.strings.double_tap_anim_speed_normal),
@@ -55,29 +55,29 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_double_tap_anim_speed), title = stringResource(MR.strings.pref_double_tap_anim_speed),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showReadingMode(), preference = readerPref.showReadingMode,
title = stringResource(MR.strings.pref_show_reading_mode), title = stringResource(MR.strings.pref_show_reading_mode),
subtitle = stringResource(MR.strings.pref_show_reading_mode_summary), subtitle = stringResource(MR.strings.pref_show_reading_mode_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showNavigationOverlayOnStart(), preference = readerPref.showNavigationOverlayOnStart,
title = stringResource(MR.strings.pref_show_navigation_mode), title = stringResource(MR.strings.pref_show_navigation_mode),
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary), subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
), ),
// SY --> // SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.forceHorizontalSeekbar(), preference = readerPref.forceHorizontalSeekbar,
title = stringResource(SYMR.strings.pref_force_horz_seekbar), title = stringResource(SYMR.strings.pref_force_horz_seekbar),
subtitle = stringResource(SYMR.strings.pref_force_horz_seekbar_summary), subtitle = stringResource(SYMR.strings.pref_force_horz_seekbar_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.landscapeVerticalSeekbar(), preference = readerPref.landscapeVerticalSeekbar,
title = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape), title = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape),
subtitle = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape_summary), subtitle = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape_summary),
enabled = !forceHorizontalSeekbar, enabled = !forceHorizontalSeekbar,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.leftVerticalSeekbar(), preference = readerPref.leftVerticalSeekbar,
title = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar), title = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar),
subtitle = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar_summary), subtitle = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar_summary),
enabled = !forceHorizontalSeekbar, enabled = !forceHorizontalSeekbar,
@@ -85,7 +85,7 @@ object SettingsReaderScreen : SearchableSettings {
// SY <-- // SY <--
/* SY --> /* SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPref.pageTransitions(), preference = readerPref.pageTransitions,
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
SY <-- */ SY <-- */
@@ -108,20 +108,20 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreenPref = readerPreferences.fullscreen() val fullscreenPref = readerPreferences.fullscreen
val fullscreen by fullscreenPref.collectAsState() val fullscreen by fullscreenPref.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display), title = stringResource(MR.strings.pref_category_display),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.defaultOrientationType(), preference = readerPreferences.defaultOrientationType,
entries = ReaderOrientation.entries.drop(1) entries = ReaderOrientation.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) } .associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(), .toImmutableMap(),
title = stringResource(MR.strings.pref_rotation_type), title = stringResource(MR.strings.pref_rotation_type),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerTheme(), preference = readerPreferences.readerTheme,
entries = persistentMapOf( entries = persistentMapOf(
1 to stringResource(MR.strings.black_background), 1 to stringResource(MR.strings.black_background),
2 to stringResource(MR.strings.gray_background), 2 to stringResource(MR.strings.gray_background),
@@ -135,16 +135,16 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen), title = stringResource(MR.strings.pref_fullscreen),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.drawUnderCutout(), preference = readerPreferences.drawUnderCutout,
title = stringResource(MR.strings.pref_cutout_short), title = stringResource(MR.strings.pref_cutout_short),
enabled = LocalView.current.hasDisplayCutout() && fullscreen, enabled = LocalView.current.hasDisplayCutout() && fullscreen,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.keepScreenOn(), preference = readerPreferences.keepScreenOn,
title = stringResource(MR.strings.pref_keep_screen_on), title = stringResource(MR.strings.pref_keep_screen_on),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.showPageNumber(), preference = readerPreferences.showPageNumber,
title = stringResource(MR.strings.pref_show_page_number), title = stringResource(MR.strings.pref_show_page_number),
), ),
), ),
@@ -153,21 +153,21 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val flashPageState by readerPreferences.flashOnPageChange().collectAsState() val flashPageState by readerPreferences.flashOnPageChange.collectAsState()
val flashMillisPref = readerPreferences.flashDurationMillis() val flashMillisPref = readerPreferences.flashDurationMillis
val flashMillis by flashMillisPref.collectAsState() val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = readerPreferences.flashPageInterval() val flashIntervalPref = readerPreferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState() val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor() val flashColorPref = readerPreferences.flashColor
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = "E-Ink", title = "E-Ink",
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.flashOnPageChange(), preference = readerPreferences.flashOnPageChange,
title = stringResource(MR.strings.pref_flash_page), title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ), subtitle = stringResource(MR.strings.pref_flash_page_summ),
), ),
@@ -208,19 +208,19 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_reading), title = stringResource(MR.strings.pref_category_reading),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipRead(), preference = readerPreferences.skipRead,
title = stringResource(MR.strings.pref_skip_read_chapters), title = stringResource(MR.strings.pref_skip_read_chapters),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipFiltered(), preference = readerPreferences.skipFiltered,
title = stringResource(MR.strings.pref_skip_filtered_chapters), title = stringResource(MR.strings.pref_skip_filtered_chapters),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipDupe(), preference = readerPreferences.skipDupe,
title = stringResource(MR.strings.pref_skip_dupe_chapters), title = stringResource(MR.strings.pref_skip_dupe_chapters),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.alwaysShowChapterTransition(), preference = readerPreferences.alwaysShowChapterTransition,
title = stringResource(MR.strings.pref_always_show_chapter_transition), title = stringResource(MR.strings.pref_always_show_chapter_transition),
), ),
), ),
@@ -229,10 +229,10 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
private fun getPagedGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getPagedGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val navModePref = readerPreferences.navigationModePager() val navModePref = readerPreferences.navigationModePager
val imageScaleTypePref = readerPreferences.imageScaleType() val imageScaleTypePref = readerPreferences.imageScaleType
val dualPageSplitPref = readerPreferences.dualPageSplitPaged() val dualPageSplitPref = readerPreferences.dualPageSplitPaged
val rotateToFitPref = readerPreferences.dualPageRotateToFit() val rotateToFitPref = readerPreferences.dualPageRotateToFit
val navMode by navModePref.collectAsState() val navMode by navModePref.collectAsState()
val imageScaleType by imageScaleTypePref.collectAsState() val imageScaleType by imageScaleTypePref.collectAsState()
@@ -251,7 +251,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav), title = stringResource(MR.strings.pref_viewer_nav),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.pagerNavInverted(), preference = readerPreferences.pagerNavInverted,
entries = persistentListOf( entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE, ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL, ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -272,7 +272,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_image_scale_type), title = stringResource(MR.strings.pref_image_scale_type),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.zoomStart(), preference = readerPreferences.zoomStart,
entries = ReaderPreferences.ZoomStart entries = ReaderPreferences.ZoomStart
.mapIndexed { index, it -> index + 1 to stringResource(it) } .mapIndexed { index, it -> index + 1 to stringResource(it) }
.toMap() .toMap()
@@ -280,22 +280,22 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_zoom_start), title = stringResource(MR.strings.pref_zoom_start),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBorders(), preference = readerPreferences.cropBorders,
title = stringResource(MR.strings.pref_crop_borders), title = stringResource(MR.strings.pref_crop_borders),
), ),
// SY --> // SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.pageTransitionsPager(), preference = readerPreferences.pageTransitionsPager,
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
// SY <-- // SY <--
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.landscapeZoom(), preference = readerPreferences.landscapeZoom,
title = stringResource(MR.strings.pref_landscape_zoom), title = stringResource(MR.strings.pref_landscape_zoom),
enabled = imageScaleType == 1, enabled = imageScaleType == 1,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.navigateToPan(), preference = readerPreferences.navigateToPan,
title = stringResource(MR.strings.pref_navigate_pan), title = stringResource(MR.strings.pref_navigate_pan),
enabled = navMode != 5, enabled = navMode != 5,
), ),
@@ -308,7 +308,7 @@ object SettingsReaderScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertPaged(), preference = readerPreferences.dualPageInvertPaged,
title = stringResource(MR.strings.pref_dual_page_invert), title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary), subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit, enabled = dualPageSplit,
@@ -322,7 +322,7 @@ object SettingsReaderScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvert(), preference = readerPreferences.dualPageRotateToFitInvert,
title = stringResource(MR.strings.pref_page_rotate_invert), title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit, enabled = rotateToFit,
), ),
@@ -334,10 +334,10 @@ object SettingsReaderScreen : SearchableSettings {
private fun getWebtoonGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getWebtoonGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val numberFormat = remember { NumberFormat.getPercentInstance() } val numberFormat = remember { NumberFormat.getPercentInstance() }
val navModePref = readerPreferences.navigationModeWebtoon() val navModePref = readerPreferences.navigationModeWebtoon
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon() val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon
val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon() val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding() val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding
val navMode by navModePref.collectAsState() val navMode by navModePref.collectAsState()
val dualPageSplit by dualPageSplitPref.collectAsState() val dualPageSplit by dualPageSplitPref.collectAsState()
@@ -356,7 +356,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav), title = stringResource(MR.strings.pref_viewer_nav),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.webtoonNavInverted(), preference = readerPreferences.webtoonNavInverted,
entries = persistentListOf( entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE, ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL, ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -378,7 +378,7 @@ object SettingsReaderScreen : SearchableSettings {
onValueChanged = { webtoonSidePaddingPref.set(it) }, onValueChanged = { webtoonSidePaddingPref.set(it) },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(), preference = readerPreferences.readerHideThreshold,
entries = persistentMapOf( entries = persistentMapOf(
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest), ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high), ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
@@ -388,7 +388,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_hide_threshold), title = stringResource(MR.strings.pref_hide_threshold),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBordersWebtoon(), preference = readerPreferences.cropBordersWebtoon,
title = stringResource(MR.strings.pref_crop_borders), title = stringResource(MR.strings.pref_crop_borders),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
@@ -400,7 +400,7 @@ object SettingsReaderScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertWebtoon(), preference = readerPreferences.dualPageInvertWebtoon,
title = stringResource(MR.strings.pref_dual_page_invert), title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary), subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit, enabled = dualPageSplit,
@@ -414,21 +414,21 @@ object SettingsReaderScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvertWebtoon(), preference = readerPreferences.dualPageRotateToFitInvertWebtoon,
title = stringResource(MR.strings.pref_page_rotate_invert), title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit, enabled = rotateToFit,
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDoubleTapZoomEnabled(), preference = readerPreferences.webtoonDoubleTapZoomEnabled,
title = stringResource(MR.strings.pref_double_tap_zoom), title = stringResource(MR.strings.pref_double_tap_zoom),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDisableZoomOut(), preference = readerPreferences.webtoonDisableZoomOut,
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out), title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
), ),
// SY --> // SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.pageTransitionsWebtoon(), preference = readerPreferences.pageTransitionsWebtoon,
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
// SY <-- // SY <--
@@ -443,12 +443,12 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.vertical_plus_viewer), title = stringResource(MR.strings.vertical_plus_viewer),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.continuousVerticalTappingByPage(), preference = readerPreferences.continuousVerticalTappingByPage,
title = stringResource(SYMR.strings.tap_scroll_page), title = stringResource(SYMR.strings.tap_scroll_page),
subtitle = stringResource(SYMR.strings.tap_scroll_page_summary), subtitle = stringResource(SYMR.strings.tap_scroll_page_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBordersContinuousVertical(), preference = readerPreferences.cropBordersContinuousVertical,
title = stringResource(MR.strings.pref_crop_borders), title = stringResource(MR.strings.pref_crop_borders),
), ),
), ),
@@ -458,7 +458,7 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys() val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys
val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState() val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_reader_navigation), title = stringResource(MR.strings.pref_reader_navigation),
@@ -468,7 +468,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_read_with_volume_keys), title = stringResource(MR.strings.pref_read_with_volume_keys),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithVolumeKeysInverted(), preference = readerPreferences.readWithVolumeKeysInverted,
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted), title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
enabled = readWithVolumeKeys, enabled = readWithVolumeKeys,
), ),
@@ -482,11 +482,11 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_reader_actions), title = stringResource(MR.strings.pref_reader_actions),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithLongTap(), preference = readerPreferences.readWithLongTap,
title = stringResource(MR.strings.pref_read_with_long_tap), title = stringResource(MR.strings.pref_read_with_long_tap),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.folderPerManga(), preference = readerPreferences.folderPerManga,
title = stringResource(MR.strings.pref_create_folder_per_manga), title = stringResource(MR.strings.pref_create_folder_per_manga),
subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary), subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary),
), ),
@@ -501,7 +501,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(SYMR.strings.page_downloading), title = stringResource(SYMR.strings.page_downloading),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.preloadSize(), preference = readerPreferences.preloadSize,
title = stringResource(SYMR.strings.reader_preload_amount), title = stringResource(SYMR.strings.reader_preload_amount),
subtitle = stringResource(SYMR.strings.reader_preload_amount_summary), subtitle = stringResource(SYMR.strings.reader_preload_amount_summary),
entries = persistentMapOf( entries = persistentMapOf(
@@ -516,13 +516,13 @@ object SettingsReaderScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerThreads(), preference = readerPreferences.readerThreads,
title = stringResource(SYMR.strings.download_threads), title = stringResource(SYMR.strings.download_threads),
subtitle = stringResource(SYMR.strings.download_threads_summary), subtitle = stringResource(SYMR.strings.download_threads_summary),
entries = List(5) { it }.associateWith { it.toString() }.toImmutableMap(), entries = List(5) { it }.associateWith { it.toString() }.toImmutableMap(),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.cacheSize(), preference = readerPreferences.cacheSize,
title = stringResource(SYMR.strings.reader_cache_size), title = stringResource(SYMR.strings.reader_cache_size),
subtitle = stringResource(SYMR.strings.reader_cache_size_summary), subtitle = stringResource(SYMR.strings.reader_cache_size_summary),
entries = persistentMapOf( entries = persistentMapOf(
@@ -545,7 +545,7 @@ object SettingsReaderScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.aggressivePageLoading(), preference = readerPreferences.aggressivePageLoading,
title = stringResource(SYMR.strings.aggressively_load_pages), title = stringResource(SYMR.strings.aggressively_load_pages),
subtitle = stringResource(SYMR.strings.aggressively_load_pages_summary), subtitle = stringResource(SYMR.strings.aggressively_load_pages_summary),
), ),
@@ -555,26 +555,26 @@ object SettingsReaderScreen : SearchableSettings {
@Composable @Composable
private fun getForkSettingsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getForkSettingsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val pageLayout by readerPreferences.pageLayout().collectAsState() val pageLayout by readerPreferences.pageLayout.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(SYMR.strings.pref_category_fork), title = stringResource(SYMR.strings.pref_category_fork),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readerInstantRetry(), preference = readerPreferences.readerInstantRetry,
title = stringResource(SYMR.strings.skip_queue_on_retry), title = stringResource(SYMR.strings.skip_queue_on_retry),
subtitle = stringResource(SYMR.strings.skip_queue_on_retry_summary), subtitle = stringResource(SYMR.strings.skip_queue_on_retry_summary),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.preserveReadingPosition(), preference = readerPreferences.preserveReadingPosition,
title = stringResource(SYMR.strings.preserve_reading_position), title = stringResource(SYMR.strings.preserve_reading_position),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.useAutoWebtoon(), preference = readerPreferences.useAutoWebtoon,
title = stringResource(SYMR.strings.auto_webtoon_mode), title = stringResource(SYMR.strings.auto_webtoon_mode),
subtitle = stringResource(SYMR.strings.auto_webtoon_mode_summary), subtitle = stringResource(SYMR.strings.auto_webtoon_mode_summary),
), ),
Preference.PreferenceItem.MultiSelectListPreference( Preference.PreferenceItem.MultiSelectListPreference(
preference = readerPreferences.readerBottomButtons(), preference = readerPreferences.readerBottomButtons,
title = stringResource(SYMR.strings.reader_bottom_buttons), title = stringResource(SYMR.strings.reader_bottom_buttons),
subtitle = stringResource(SYMR.strings.reader_bottom_buttons_summary), subtitle = stringResource(SYMR.strings.reader_bottom_buttons_summary),
entries = ReaderBottomButton.entries entries = ReaderBottomButton.entries
@@ -582,7 +582,7 @@ object SettingsReaderScreen : SearchableSettings {
.toImmutableMap(), .toImmutableMap(),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.pageLayout(), preference = readerPreferences.pageLayout,
title = stringResource(SYMR.strings.page_layout), title = stringResource(SYMR.strings.page_layout),
subtitle = stringResource(SYMR.strings.automatic_can_still_switch), subtitle = stringResource(SYMR.strings.automatic_can_still_switch),
entries = ReaderPreferences.PageLayouts entries = ReaderPreferences.PageLayouts
@@ -591,12 +591,12 @@ object SettingsReaderScreen : SearchableSettings {
.toImmutableMap(), .toImmutableMap(),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.invertDoublePages(), preference = readerPreferences.invertDoublePages,
title = stringResource(SYMR.strings.invert_double_pages), title = stringResource(SYMR.strings.invert_double_pages),
enabled = pageLayout != PagerConfig.PageLayout.SINGLE_PAGE, enabled = pageLayout != PagerConfig.PageLayout.SINGLE_PAGE,
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.centerMarginType(), preference = readerPreferences.centerMarginType,
title = stringResource(SYMR.strings.center_margin), title = stringResource(SYMR.strings.center_margin),
subtitle = stringResource(SYMR.strings.pref_center_margin_summary), subtitle = stringResource(SYMR.strings.pref_center_margin_summary),
entries = ReaderPreferences.CenterMarginTypes entries = ReaderPreferences.CenterMarginTypes
@@ -605,7 +605,7 @@ object SettingsReaderScreen : SearchableSettings {
.toImmutableMap(), .toImmutableMap(),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.archiveReaderMode(), preference = readerPreferences.archiveReaderMode,
title = stringResource(SYMR.strings.pref_archive_reader_mode), title = stringResource(SYMR.strings.pref_archive_reader_mode),
subtitle = stringResource(SYMR.strings.pref_archive_reader_mode_summary), subtitle = stringResource(SYMR.strings.pref_archive_reader_mode_summary),
entries = ReaderPreferences.archiveModeTypes entries = ReaderPreferences.archiveModeTypes
@@ -85,12 +85,12 @@ object SettingsSecurityScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() } val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator() val useAuthPref = securityPreferences.useAuthenticator
val useAuth by useAuthPref.collectAsState() val useAuth by useAuthPref.collectAsState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState() val isCbzPasswordSet by remember { CbzCrypto.isPasswordSetState(scope) }.collectAsState()
val passwordProtectDownloads by securityPreferences.passwordProtectDownloads().collectAsState() val passwordProtectDownloads by securityPreferences.passwordProtectDownloads.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_security), title = stringResource(MR.strings.pref_security),
@@ -106,7 +106,7 @@ object SettingsSecurityScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = securityPreferences.lockAppAfter(), preference = securityPreferences.lockAppAfter,
entries = LockAfterValues entries = LockAfterValues
.associateWith { .associateWith {
when (it) { when (it) {
@@ -125,11 +125,11 @@ object SettingsSecurityScreen : SearchableSettings {
}, },
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = securityPreferences.hideNotificationContent(), preference = securityPreferences.hideNotificationContent,
title = stringResource(MR.strings.hide_notification_content), title = stringResource(MR.strings.hide_notification_content),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = securityPreferences.secureScreen(), preference = securityPreferences.secureScreen,
entries = SecurityPreferences.SecureScreenMode.entries entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) } .associateWith { stringResource(it.titleRes) }
.toImmutableMap(), .toImmutableMap(),
@@ -137,13 +137,13 @@ object SettingsSecurityScreen : SearchableSettings {
), ),
// SY --> // SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = securityPreferences.passwordProtectDownloads(), preference = securityPreferences.passwordProtectDownloads,
title = stringResource(SYMR.strings.password_protect_downloads), title = stringResource(SYMR.strings.password_protect_downloads),
subtitle = stringResource(SYMR.strings.password_protect_downloads_summary), subtitle = stringResource(SYMR.strings.password_protect_downloads_summary),
enabled = isCbzPasswordSet, enabled = isCbzPasswordSet,
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = securityPreferences.encryptionType(), preference = securityPreferences.encryptionType,
title = stringResource(SYMR.strings.encryption_type), title = stringResource(SYMR.strings.encryption_type),
entries = SecurityPreferences.EncryptionType.entries entries = SecurityPreferences.EncryptionType.entries
.associateWith { stringResource(it.titleRes) } .associateWith { stringResource(it.titleRes) }
@@ -160,7 +160,7 @@ object SettingsSecurityScreen : SearchableSettings {
dialogOpen = false dialogOpen = false
CbzCrypto.deleteKeyCbz() CbzCrypto.deleteKeyCbz()
securityPreferences.cbzPassword().set(CbzCrypto.encryptCbz(password.replace("\n", ""))) securityPreferences.cbzPassword.set(CbzCrypto.encryptCbz(password.replace("\n", "")))
}, },
) )
} }
@@ -175,13 +175,13 @@ object SettingsSecurityScreen : SearchableSettings {
title = stringResource(SYMR.strings.delete_cbz_archive_password), title = stringResource(SYMR.strings.delete_cbz_archive_password),
onClick = { onClick = {
CbzCrypto.deleteKeyCbz() CbzCrypto.deleteKeyCbz()
securityPreferences.cbzPassword().set("") securityPreferences.cbzPassword.set("")
}, },
enabled = isCbzPasswordSet, enabled = isCbzPasswordSet,
), ),
kotlin.run { kotlin.run {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val count by securityPreferences.authenticatorTimeRanges().collectAsState() val count by securityPreferences.authenticatorTimeRanges.collectAsState()
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(SYMR.strings.action_edit_biometric_lock_times), title = stringResource(SYMR.strings.action_edit_biometric_lock_times),
subtitle = pluralStringResource( subtitle = pluralStringResource(
@@ -196,7 +196,7 @@ object SettingsSecurityScreen : SearchableSettings {
) )
}, },
kotlin.run { kotlin.run {
val selection by securityPreferences.authenticatorDays().collectAsState() val selection by securityPreferences.authenticatorDays.collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
SetLockedDaysDialog( SetLockedDaysDialog(
@@ -204,7 +204,7 @@ object SettingsSecurityScreen : SearchableSettings {
initialSelection = selection, initialSelection = selection,
onDaysSelected = { onDaysSelected = {
dialogOpen = false dialogOpen = false
securityPreferences.authenticatorDays().set(it) securityPreferences.authenticatorDays.set(it)
}, },
) )
} }
@@ -384,12 +384,12 @@ object SettingsSecurityScreen : SearchableSettings {
title = stringResource(MR.strings.pref_firebase), title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.crashlytics(), preference = privacyPreferences.crashlytics,
title = stringResource(MR.strings.onboarding_permission_crashlytics), title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description), subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.analytics(), preference = privacyPreferences.analytics,
title = stringResource(MR.strings.onboarding_permission_analytics), title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description), subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
), ),
@@ -91,7 +91,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() } val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() } val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() } val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead() val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead
var dialog by remember { mutableStateOf<Any?>(null) } var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run { dialog?.run {
@@ -129,11 +129,11 @@ object SettingsTrackingScreen : SearchableSettings {
return listOf( return listOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = trackPreferences.autoUpdateTrack(), preference = trackPreferences.autoUpdateTrack,
title = stringResource(MR.strings.pref_auto_update_manga_sync), title = stringResource(MR.strings.pref_auto_update_manga_sync),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = trackPreferences.autoUpdateTrackOnMarkRead(), preference = trackPreferences.autoUpdateTrackOnMarkRead,
entries = AutoTrackState.entries entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) } .associateWith { stringResource(it.titleRes) }
.toPersistentMap(), .toPersistentMap(),
@@ -141,7 +141,7 @@ object SettingsTrackingScreen : SearchableSettings {
), ),
// SY --> // SY -->
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = trackPreferences.resolveUsingSourceMetadata(), preference = trackPreferences.resolveUsingSourceMetadata,
title = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata), title = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata),
subtitle = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata_summary), subtitle = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata_summary),
), ),
@@ -245,7 +245,6 @@ object AboutScreen : Screen() {
is GetApplicationRelease.Result.OsTooOld -> { is GetApplicationRelease.Result.OsTooOld -> {
context.toast(MR.strings.update_check_eol) context.toast(MR.strings.update_check_eol)
} }
else -> {}
} }
} catch (e: Exception) { } catch (e: Exception) {
context.toast(e.message) context.toast(e.message)
@@ -298,7 +297,7 @@ object AboutScreen : Screen() {
) )
.toDateTimestampString( .toDateTimestampString(
UiPreferences.dateFormat( UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(), Injekt.get<UiPreferences>().dateFormat.get(),
), ),
) )
} catch (e: Exception) { } catch (e: Exception) {
@@ -2,13 +2,16 @@ package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@@ -27,7 +30,9 @@ class OpenSourceLicensesScreen : Screen() {
) )
}, },
) { contentPadding -> ) { contentPadding ->
val libraries by produceLibraries(R.raw.aboutlibraries)
LibrariesContainer( LibrariesContainer(
libraries = libraries,
modifier = Modifier modifier = Modifier
.fillMaxSize(), .fillMaxSize(),
contentPadding = contentPadding, contentPadding = contentPadding,
@@ -1,6 +1,7 @@
package eu.kanade.presentation.more.settings.screen.browse.components package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@@ -61,6 +62,7 @@ fun ExtensionRepoCreateDialog(
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester), .focusRequester(focusRequester),
value = name, value = name,
onValueChange = { name = it }, onValueChange = { name = it },
@@ -1,24 +1,38 @@
package eu.kanade.presentation.more.settings.screen.debug package eu.kanade.presentation.more.settings.screen.debug
import android.os.Build import android.os.Build
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.profileinstaller.ProfileVerifier import androidx.profileinstaller.ProfileVerifier
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow 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.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.about.AboutScreen import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.guava.await import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import mihon.core.common.FeatureFlags
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DebugInfoScreen : Screen() { class DebugInfoScreen : Screen() {
@@ -47,6 +61,12 @@ class DebugInfoScreen : Screen() {
@Composable @Composable
private fun getAppInfoGroup(): Preference.PreferenceGroup { private fun getAppInfoGroup(): Preference.PreferenceGroup {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val installationIdPref = remember { Injekt.get<BasePreferences>().installationId }
val installationId by installationIdPref.collectAsState()
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = "App info", title = "App info",
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
@@ -58,6 +78,28 @@ class DebugInfoScreen : Screen() {
title = "Build time", title = "Build time",
subtitle = AboutScreen.getFormattedBuildTime(), subtitle = AboutScreen.getFormattedBuildTime(),
), ),
Preference.PreferenceItem.TextPreference(
title = "Installation ID",
subtitle = installationId,
widget = {
IconButton(
onClick = {
scope.launch {
installationIdPref.set(FeatureFlags.newInstallationId())
}
},
) {
Icon(
imageVector = Icons.Outlined.Autorenew,
tint = MaterialTheme.colorScheme.primary,
contentDescription = null,
)
}
},
onClick = {
context.copyToClipboard(installationId, installationId)
},
),
getProfileVerifierPreference(), getProfileVerifierPreference(),
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = "WebView version", title = "WebView version",
@@ -78,7 +120,7 @@ class DebugInfoScreen : Screen() {
val status by produceState(initialValue = "-") { val status by produceState(initialValue = "-") {
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
value = when (result) { value = when (result) {
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed" ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE_INSTALLED -> "No profile installed"
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled" ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING -> ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
"Compiled non-matching" "Compiled non-matching"
@@ -156,7 +156,7 @@ class WorkerInfoScreen : Screen() {
) )
.toDateTimestampString( .toDateTimestampString(
UiPreferences.dateFormat( UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(), Injekt.get<UiPreferences>().dateFormat.get(),
), ),
) )
appendLine("Next scheduled run: $timestamp") appendLine("Next scheduled run: $timestamp")
@@ -24,10 +24,10 @@ class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false) internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>() private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis() internal val flashMillis = readerPreferences.flashDurationMillis
internal val flashMode = readerPreferences.flashColor() internal val flashMode = readerPreferences.flashColor
internal val flashIntervalPref = readerPreferences.flashPageInterval() internal val flashIntervalPref = readerPreferences.flashPageInterval
// Internal State for Flash // Internal State for Flash
private var flashInterval = flashIntervalPref.get() private var flashInterval = flashIntervalPref.get()
@@ -22,10 +22,10 @@ import tachiyomi.presentation.core.util.collectAsState
@Composable @Composable
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
val customBrightness by screenModel.preferences.customBrightness().collectAsState() val customBrightness by screenModel.preferences.customBrightness.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_custom_brightness), label = stringResource(MR.strings.pref_custom_brightness),
pref = screenModel.preferences.customBrightness(), pref = screenModel.preferences.customBrightness,
) )
/* /*
@@ -35,31 +35,31 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
* 0 sets system brightness and hides the overlay. * 0 sets system brightness and hides the overlay.
*/ */
if (customBrightness) { if (customBrightness) {
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState() val customBrightnessValue by screenModel.preferences.customBrightnessValue.collectAsState()
SliderItem( SliderItem(
value = customBrightnessValue, value = customBrightnessValue,
valueRange = -75..100, valueRange = -75..100,
steps = 0, steps = 0,
label = stringResource(MR.strings.pref_custom_brightness), label = stringResource(MR.strings.pref_custom_brightness),
onChange = { screenModel.preferences.customBrightnessValue().set(it) }, onChange = { screenModel.preferences.customBrightnessValue.set(it) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
} }
val colorFilter by screenModel.preferences.colorFilter().collectAsState() val colorFilter by screenModel.preferences.colorFilter.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_custom_color_filter), label = stringResource(MR.strings.pref_custom_color_filter),
pref = screenModel.preferences.colorFilter(), pref = screenModel.preferences.colorFilter,
) )
if (colorFilter) { if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState() val colorFilterValue by screenModel.preferences.colorFilterValue.collectAsState()
SliderItem( SliderItem(
value = colorFilterValue.red, value = colorFilterValue.red,
valueRange = 0..255, valueRange = 0..255,
steps = 0, steps = 0,
label = stringResource(MR.strings.color_filter_r_value), label = stringResource(MR.strings.color_filter_r_value),
onChange = { newRValue -> onChange = { newRValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newRValue, RED_MASK, 16) getColorValue(it, newRValue, RED_MASK, 16)
} }
}, },
@@ -71,7 +71,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0, steps = 0,
label = stringResource(MR.strings.color_filter_g_value), label = stringResource(MR.strings.color_filter_g_value),
onChange = { newGValue -> onChange = { newGValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newGValue, GREEN_MASK, 8) getColorValue(it, newGValue, GREEN_MASK, 8)
} }
}, },
@@ -83,7 +83,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0, steps = 0,
label = stringResource(MR.strings.color_filter_b_value), label = stringResource(MR.strings.color_filter_b_value),
onChange = { newBValue -> onChange = { newBValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newBValue, BLUE_MASK, 0) getColorValue(it, newBValue, BLUE_MASK, 0)
} }
}, },
@@ -95,19 +95,19 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0, steps = 0,
label = stringResource(MR.strings.color_filter_a_value), label = stringResource(MR.strings.color_filter_a_value),
onChange = { newAValue -> onChange = { newAValue ->
screenModel.preferences.colorFilterValue().getAndSet { screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newAValue, ALPHA_MASK, 24) getColorValue(it, newAValue, ALPHA_MASK, 24)
} }
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState() val colorFilterMode by screenModel.preferences.colorFilterMode.collectAsState()
SettingsChipRow(MR.strings.pref_color_filter_mode) { SettingsChipRow(MR.strings.pref_color_filter_mode) {
ColorFilterMode.mapIndexed { index, it -> ColorFilterMode.mapIndexed { index, it ->
FilterChip( FilterChip(
selected = colorFilterMode == index, selected = colorFilterMode == index,
onClick = { screenModel.preferences.colorFilterMode().set(index) }, onClick = { screenModel.preferences.colorFilterMode.set(index) },
label = { Text(stringResource(it.first)) }, label = { Text(stringResource(it.first)) },
) )
} }
@@ -116,11 +116,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_grayscale), label = stringResource(MR.strings.pref_grayscale),
pref = screenModel.preferences.grayscale(), pref = screenModel.preferences.grayscale,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_inverted_colors), label = stringResource(MR.strings.pref_inverted_colors),
pref = screenModel.preferences.invertedColors(), pref = screenModel.preferences.invertedColors,
) )
} }
@@ -34,24 +34,24 @@ private val flashColors = listOf(
@Composable @Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState() val readerTheme by screenModel.preferences.readerTheme.collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState() val flashPageState by screenModel.preferences.flashOnPageChange.collectAsState()
val flashMillisPref = screenModel.preferences.flashDurationMillis() val flashMillisPref = screenModel.preferences.flashDurationMillis
val flashMillis by flashMillisPref.collectAsState() val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = screenModel.preferences.flashPageInterval() val flashIntervalPref = screenModel.preferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState() val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor() val flashColorPref = screenModel.preferences.flashColor
val flashColor by flashColorPref.collectAsState() val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) { SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) -> themes.map { (labelRes, value) ->
FilterChip( FilterChip(
selected = readerTheme == value, selected = readerTheme == value,
onClick = { screenModel.preferences.readerTheme().set(value) }, onClick = { screenModel.preferences.readerTheme.set(value) },
label = { Text(stringResource(labelRes)) }, label = { Text(stringResource(labelRes)) },
) )
} }
@@ -59,66 +59,66 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_show_page_number), label = stringResource(MR.strings.pref_show_page_number),
pref = screenModel.preferences.showPageNumber(), pref = screenModel.preferences.showPageNumber,
) )
// SY --> // SY -->
val forceHorizontalSeekbar by screenModel.preferences.forceHorizontalSeekbar().collectAsState() val forceHorizontalSeekbar by screenModel.preferences.forceHorizontalSeekbar.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.pref_force_horz_seekbar), label = stringResource(SYMR.strings.pref_force_horz_seekbar),
pref = screenModel.preferences.forceHorizontalSeekbar(), pref = screenModel.preferences.forceHorizontalSeekbar,
) )
if (!forceHorizontalSeekbar) { if (!forceHorizontalSeekbar) {
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape), label = stringResource(SYMR.strings.pref_show_vert_seekbar_landscape),
pref = screenModel.preferences.landscapeVerticalSeekbar(), pref = screenModel.preferences.landscapeVerticalSeekbar,
) )
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar), label = stringResource(SYMR.strings.pref_left_handed_vertical_seekbar),
pref = screenModel.preferences.leftVerticalSeekbar(), pref = screenModel.preferences.leftVerticalSeekbar,
) )
} }
// SY <-- // SY <--
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_fullscreen), label = stringResource(MR.strings.pref_fullscreen),
pref = screenModel.preferences.fullscreen(), pref = screenModel.preferences.fullscreen,
) )
val isFullscreen by screenModel.preferences.fullscreen().collectAsState() val isFullscreen by screenModel.preferences.fullscreen.collectAsState()
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) { if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short), label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.drawUnderCutout(), pref = screenModel.preferences.drawUnderCutout,
) )
} }
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_keep_screen_on), label = stringResource(MR.strings.pref_keep_screen_on),
pref = screenModel.preferences.keepScreenOn(), pref = screenModel.preferences.keepScreenOn,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_read_with_long_tap), label = stringResource(MR.strings.pref_read_with_long_tap),
pref = screenModel.preferences.readWithLongTap(), pref = screenModel.preferences.readWithLongTap,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_always_show_chapter_transition), label = stringResource(MR.strings.pref_always_show_chapter_transition),
pref = screenModel.preferences.alwaysShowChapterTransition(), pref = screenModel.preferences.alwaysShowChapterTransition,
) )
// SY --> // SY -->
/*CheckboxItem( /*CheckboxItem(
label = stringResource(MR.strings.pref_page_transitions), label = stringResource(MR.strings.pref_page_transitions),
pref = screenModel.preferences.pageTransitions(), pref = screenModel.preferences.pageTransitions,
) SY <-- */ ) SY <-- */
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_flash_page), label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(), pref = screenModel.preferences.flashOnPageChange,
) )
if (flashPageState) { if (flashPageState) {
SliderItem( SliderItem(
@@ -153,7 +153,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
// SY --> // SY -->
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.auto_webtoon_mode), label = stringResource(SYMR.strings.auto_webtoon_mode),
pref = screenModel.preferences.useAutoWebtoon(), pref = screenModel.preferences.useAutoWebtoon,
) )
// SY <-- // SY <--
} }
@@ -67,44 +67,44 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) { private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
HeadingItem(MR.strings.pager_viewer) HeadingItem(MR.strings.pager_viewer)
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState() val navigationModePager by screenModel.preferences.navigationModePager.collectAsState()
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState() val pagerNavInverted by screenModel.preferences.pagerNavInverted.collectAsState()
TapZonesItems( TapZonesItems(
selected = navigationModePager, selected = navigationModePager,
onSelect = screenModel.preferences.navigationModePager()::set, onSelect = screenModel.preferences.navigationModePager::set,
invertMode = pagerNavInverted, invertMode = pagerNavInverted,
onSelectInvertMode = screenModel.preferences.pagerNavInverted()::set, onSelectInvertMode = screenModel.preferences.pagerNavInverted::set,
) )
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState() val imageScaleType by screenModel.preferences.imageScaleType.collectAsState()
SettingsChipRow(MR.strings.pref_image_scale_type) { SettingsChipRow(MR.strings.pref_image_scale_type) {
ReaderPreferences.ImageScaleType.mapIndexed { index, it -> ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
FilterChip( FilterChip(
selected = imageScaleType == index + 1, selected = imageScaleType == index + 1,
onClick = { screenModel.preferences.imageScaleType().set(index + 1) }, onClick = { screenModel.preferences.imageScaleType.set(index + 1) },
label = { Text(stringResource(it)) }, label = { Text(stringResource(it)) },
) )
} }
} }
val zoomStart by screenModel.preferences.zoomStart().collectAsState() val zoomStart by screenModel.preferences.zoomStart.collectAsState()
SettingsChipRow(MR.strings.pref_zoom_start) { SettingsChipRow(MR.strings.pref_zoom_start) {
ReaderPreferences.ZoomStart.mapIndexed { index, it -> ReaderPreferences.ZoomStart.mapIndexed { index, it ->
FilterChip( FilterChip(
selected = zoomStart == index + 1, selected = zoomStart == index + 1,
onClick = { screenModel.preferences.zoomStart().set(index + 1) }, onClick = { screenModel.preferences.zoomStart.set(index + 1) },
label = { Text(stringResource(it)) }, label = { Text(stringResource(it)) },
) )
} }
} }
// SY --> // SY -->
val pageLayout by screenModel.preferences.pageLayout().collectAsState() val pageLayout by screenModel.preferences.pageLayout.collectAsState()
SettingsChipRow(SYMR.strings.page_layout) { SettingsChipRow(SYMR.strings.page_layout) {
ReaderPreferences.PageLayouts.mapIndexed { index, it -> ReaderPreferences.PageLayouts.mapIndexed { index, it ->
FilterChip( FilterChip(
selected = pageLayout == index, selected = pageLayout == index,
onClick = { screenModel.preferences.pageLayout().set(index) }, onClick = { screenModel.preferences.pageLayout.set(index) },
label = { Text(stringResource(it)) }, label = { Text(stringResource(it)) },
) )
} }
@@ -113,62 +113,62 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders), label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBorders(), pref = screenModel.preferences.cropBorders,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_landscape_zoom), label = stringResource(MR.strings.pref_landscape_zoom),
pref = screenModel.preferences.landscapeZoom(), pref = screenModel.preferences.landscapeZoom,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_navigate_pan), label = stringResource(MR.strings.pref_navigate_pan),
pref = screenModel.preferences.navigateToPan(), pref = screenModel.preferences.navigateToPan,
) )
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState() val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_split), label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitPaged(), pref = screenModel.preferences.dualPageSplitPaged,
) )
if (dualPageSplitPaged) { if (dualPageSplitPaged) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_invert), label = stringResource(MR.strings.pref_dual_page_invert),
pref = screenModel.preferences.dualPageInvertPaged(), pref = screenModel.preferences.dualPageInvertPaged,
) )
} }
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState() val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate), label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFit(), pref = screenModel.preferences.dualPageRotateToFit,
) )
if (dualPageRotateToFit) { if (dualPageRotateToFit) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert), label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvert(), pref = screenModel.preferences.dualPageRotateToFitInvert,
) )
} }
// SY --> // SY -->
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_transitions), label = stringResource(MR.strings.pref_page_transitions),
pref = screenModel.preferences.pageTransitionsPager(), pref = screenModel.preferences.pageTransitionsPager,
) )
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.invert_double_pages), label = stringResource(SYMR.strings.invert_double_pages),
pref = screenModel.preferences.invertDoublePages(), pref = screenModel.preferences.invertDoublePages,
) )
val centerMarginType by screenModel.preferences.centerMarginType().collectAsState() val centerMarginType by screenModel.preferences.centerMarginType.collectAsState()
SettingsChipRow(SYMR.strings.pref_center_margin) { SettingsChipRow(SYMR.strings.pref_center_margin) {
ReaderPreferences.CenterMarginTypes.mapIndexed { index, it -> ReaderPreferences.CenterMarginTypes.mapIndexed { index, it ->
FilterChip( FilterChip(
selected = centerMarginType == index, selected = centerMarginType == index,
onClick = { screenModel.preferences.centerMarginType().set(index) }, onClick = { screenModel.preferences.centerMarginType.set(index) },
label = { Text(stringResource(it)) }, label = { Text(stringResource(it)) },
) )
} }
@@ -182,77 +182,77 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
HeadingItem(MR.strings.webtoon_viewer) HeadingItem(MR.strings.webtoon_viewer)
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState() val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon.collectAsState()
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState() val webtoonNavInverted by screenModel.preferences.webtoonNavInverted.collectAsState()
TapZonesItems( TapZonesItems(
selected = navigationModeWebtoon, selected = navigationModeWebtoon,
onSelect = screenModel.preferences.navigationModeWebtoon()::set, onSelect = screenModel.preferences.navigationModeWebtoon::set,
invertMode = webtoonNavInverted, invertMode = webtoonNavInverted,
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set, onSelectInvertMode = screenModel.preferences.webtoonNavInverted::set,
) )
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState() val webtoonSidePadding by screenModel.preferences.webtoonSidePadding.collectAsState()
SliderItem( SliderItem(
value = webtoonSidePadding, value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX }, valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding), label = stringResource(MR.strings.pref_webtoon_side_padding),
valueString = numberFormat.format(webtoonSidePadding / 100f), valueString = numberFormat.format(webtoonSidePadding / 100f),
onChange = { onChange = {
screenModel.preferences.webtoonSidePadding().set(it) screenModel.preferences.webtoonSidePadding.set(it)
}, },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders), label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBordersWebtoon(), pref = screenModel.preferences.cropBordersWebtoon,
) )
// SY --> // SY -->
CheckboxItem( CheckboxItem(
label = stringResource(SYMR.strings.pref_smooth_scroll), label = stringResource(SYMR.strings.pref_smooth_scroll),
pref = screenModel.preferences.smoothAutoScroll(), pref = screenModel.preferences.smoothAutoScroll,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_transitions), label = stringResource(MR.strings.pref_page_transitions),
pref = screenModel.preferences.pageTransitionsWebtoon(), pref = screenModel.preferences.pageTransitionsWebtoon,
) )
// SY <-- // SY <--
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState() val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_split), label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitWebtoon(), pref = screenModel.preferences.dualPageSplitWebtoon,
) )
if (dualPageSplitWebtoon) { if (dualPageSplitWebtoon) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_invert), label = stringResource(MR.strings.pref_dual_page_invert),
pref = screenModel.preferences.dualPageInvertWebtoon(), pref = screenModel.preferences.dualPageInvertWebtoon,
) )
} }
val dualPageRotateToFitWebtoon by screenModel.preferences.dualPageRotateToFitWebtoon().collectAsState() val dualPageRotateToFitWebtoon by screenModel.preferences.dualPageRotateToFitWebtoon.collectAsState()
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate), label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFitWebtoon(), pref = screenModel.preferences.dualPageRotateToFitWebtoon,
) )
if (dualPageRotateToFitWebtoon) { if (dualPageRotateToFitWebtoon) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert), label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon(), pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon,
) )
} }
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_double_tap_zoom), label = stringResource(MR.strings.pref_double_tap_zoom),
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(), pref = screenModel.preferences.webtoonDoubleTapZoomEnabled,
) )
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_webtoon_disable_zoom_out), label = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
pref = screenModel.preferences.webtoonDisableZoomOut(), pref = screenModel.preferences.webtoonDisableZoomOut,
) )
} }
@@ -263,7 +263,7 @@ private fun ColumnScope.WebtoonWithGapsViewerSettings(screenModel: ReaderSetting
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders), label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBordersContinuousVertical(), pref = screenModel.preferences.cropBordersContinuousVertical,
) )
} }
// SY <-- // SY <--
@@ -1,10 +1,11 @@
package eu.kanade.presentation.theme package eu.kanade.presentation.theme
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.AppTheme
@@ -34,8 +35,8 @@ fun TachiyomiTheme(
) { ) {
val uiPreferences = Injekt.get<UiPreferences>() val uiPreferences = Injekt.get<UiPreferences>()
BaseTachiyomiTheme( BaseTachiyomiTheme(
appTheme = appTheme ?: uiPreferences.appTheme().get(), appTheme = appTheme ?: uiPreferences.appTheme.get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled().get(), isAmoled = amoled ?: uiPreferences.themeDarkAmoled.get(),
content = content, content = content,
) )
} }
@@ -53,26 +54,36 @@ private fun BaseTachiyomiTheme(
isAmoled: Boolean, isAmoled: Boolean,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
MaterialTheme( val context = LocalContext.current
colorScheme = getThemeColorScheme(appTheme, isAmoled), val isDark = isSystemInDarkTheme()
MaterialExpressiveTheme(
colorScheme = remember(appTheme, isDark, isAmoled) {
getThemeColorScheme(
context = context,
appTheme = appTheme,
isDark = isDark,
isAmoled = isAmoled,
)
},
content = content, content = content,
) )
} }
@Composable
@ReadOnlyComposable
private fun getThemeColorScheme( private fun getThemeColorScheme(
context: Context,
appTheme: AppTheme, appTheme: AppTheme,
isDark: Boolean,
isAmoled: Boolean, isAmoled: Boolean,
): ColorScheme { ): ColorScheme {
val colorScheme = if (appTheme == AppTheme.MONET) { val colorScheme = if (appTheme == AppTheme.MONET) {
MonetColorScheme(LocalContext.current) MonetColorScheme(context)
} else { } else {
colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme) colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme)
} }
return colorScheme.getColorScheme( return colorScheme.getColorScheme(
isSystemInDarkTheme(), isDark = isDark,
isAmoled, isAmoled = isAmoled,
overrideDarkSurfaceContainers = appTheme != AppTheme.MONET,
) )
} }
@@ -14,16 +14,25 @@ internal abstract class BaseColorScheme {
private val surfaceContainerHigh = Color(0xFF131313) private val surfaceContainerHigh = Color(0xFF131313)
private val surfaceContainerHighest = Color(0xFF1B1B1B) private val surfaceContainerHighest = Color(0xFF1B1B1B)
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { fun getColorScheme(
isDark: Boolean,
isAmoled: Boolean,
overrideDarkSurfaceContainers: Boolean,
): ColorScheme {
if (!isDark) return lightScheme if (!isDark) return lightScheme
if (!isAmoled) return darkScheme if (!isAmoled) return darkScheme
return darkScheme.copy( val amoledScheme = darkScheme.copy(
background = Color.Black, background = Color.Black,
onBackground = Color.White, onBackground = Color.White,
surface = Color.Black, surface = Color.Black,
onSurface = Color.White, onSurface = Color.White,
)
if (!overrideDarkSurfaceContainers) return amoledScheme
return amoledScheme.copy(
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget) surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
surfaceContainerLowest = surfaceContainer, surfaceContainerLowest = surfaceContainer,
surfaceContainerLow = surfaceContainer, surfaceContainerLow = surfaceContainer,
@@ -1,22 +1,17 @@
package eu.kanade.presentation.theme.colorscheme package eu.kanade.presentation.theme.colorscheme
import android.annotation.SuppressLint
import android.app.UiModeManager
import android.app.WallpaperManager import android.app.WallpaperManager
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.content.getSystemService import com.materialkolor.PaletteStyle
import com.google.android.material.color.utilities.Hct import com.materialkolor.dynamiccolor.ColorSpec
import com.google.android.material.color.utilities.MaterialDynamicColors import com.materialkolor.ktx.DynamicScheme
import com.google.android.material.color.utilities.QuantizerCelebi import com.materialkolor.toColorScheme
import com.google.android.material.color.utilities.SchemeContent
import com.google.android.material.color.utilities.Score
internal class MonetColorScheme(context: Context) : BaseColorScheme() { internal class MonetColorScheme(context: Context) : BaseColorScheme() {
@@ -28,7 +23,7 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
?.primaryColor ?.primaryColor
?.toArgb() ?.toArgb()
if (seed != null) { if (seed != null) {
MonetCompatColorScheme(context, seed) MonetCompatColorScheme(Color(seed))
} else { } else {
TachiyomiColorScheme TachiyomiColorScheme
} }
@@ -41,19 +36,6 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
override val lightScheme override val lightScheme
get() = monet.lightScheme get() = monet.lightScheme
companion object {
@Suppress("Unused")
@SuppressLint("RestrictedApi")
fun extractSeedColorFromImage(bitmap: Bitmap): Int? {
val width = bitmap.width
val height = bitmap.height
val bitmapPixels = IntArray(width * height)
bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height)
return Score.score(QuantizerCelebi.quantize(bitmapPixels, 128), 1, 0)[0]
.takeIf { it != 0 } // Don't take fallback color
}
}
} }
@RequiresApi(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S)
@@ -62,64 +44,19 @@ private class MonetSystemColorScheme(context: Context) : BaseColorScheme() {
override val darkScheme = dynamicDarkColorScheme(context) override val darkScheme = dynamicDarkColorScheme(context)
} }
private class MonetCompatColorScheme(context: Context, seed: Int) : BaseColorScheme() { internal class MonetCompatColorScheme(seed: Color) : BaseColorScheme() {
override val lightScheme = generateColorSchemeFromSeed(seed = seed, dark = false)
override val lightScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = false) override val darkScheme = generateColorSchemeFromSeed(seed = seed, dark = true)
override val darkScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = true)
companion object { companion object {
private fun Int.toComposeColor(): Color = Color(this) fun generateColorSchemeFromSeed(seed: Color, dark: Boolean): ColorScheme {
return DynamicScheme(
@SuppressLint("PrivateResource", "RestrictedApi") seedColor = seed,
private fun generateColorSchemeFromSeed(context: Context, seed: Int, dark: Boolean): ColorScheme { isDark = dark,
val scheme = SchemeContent( specVersion = ColorSpec.SpecVersion.SPEC_2025,
Hct.fromInt(seed), style = PaletteStyle.Expressive,
dark,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.getSystemService<UiModeManager>()?.contrast?.toDouble() ?: 0.0
} else {
0.0
},
)
val dynamicColors = MaterialDynamicColors()
return ColorScheme(
primary = dynamicColors.primary().getArgb(scheme).toComposeColor(),
onPrimary = dynamicColors.onPrimary().getArgb(scheme).toComposeColor(),
primaryContainer = dynamicColors.primaryContainer().getArgb(scheme).toComposeColor(),
onPrimaryContainer = dynamicColors.onPrimaryContainer().getArgb(scheme).toComposeColor(),
inversePrimary = dynamicColors.inversePrimary().getArgb(scheme).toComposeColor(),
secondary = dynamicColors.secondary().getArgb(scheme).toComposeColor(),
onSecondary = dynamicColors.onSecondary().getArgb(scheme).toComposeColor(),
secondaryContainer = dynamicColors.secondaryContainer().getArgb(scheme).toComposeColor(),
onSecondaryContainer = dynamicColors.onSecondaryContainer().getArgb(scheme).toComposeColor(),
tertiary = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
onTertiary = dynamicColors.onTertiary().getArgb(scheme).toComposeColor(),
tertiaryContainer = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
onTertiaryContainer = dynamicColors.onTertiaryContainer().getArgb(scheme).toComposeColor(),
background = dynamicColors.background().getArgb(scheme).toComposeColor(),
onBackground = dynamicColors.onBackground().getArgb(scheme).toComposeColor(),
surface = dynamicColors.surface().getArgb(scheme).toComposeColor(),
onSurface = dynamicColors.onSurface().getArgb(scheme).toComposeColor(),
surfaceVariant = dynamicColors.surfaceVariant().getArgb(scheme).toComposeColor(),
onSurfaceVariant = dynamicColors.onSurfaceVariant().getArgb(scheme).toComposeColor(),
surfaceTint = dynamicColors.surfaceTint().getArgb(scheme).toComposeColor(),
inverseSurface = dynamicColors.inverseSurface().getArgb(scheme).toComposeColor(),
inverseOnSurface = dynamicColors.inverseOnSurface().getArgb(scheme).toComposeColor(),
error = dynamicColors.error().getArgb(scheme).toComposeColor(),
onError = dynamicColors.onError().getArgb(scheme).toComposeColor(),
errorContainer = dynamicColors.errorContainer().getArgb(scheme).toComposeColor(),
onErrorContainer = dynamicColors.onErrorContainer().getArgb(scheme).toComposeColor(),
outline = dynamicColors.outline().getArgb(scheme).toComposeColor(),
outlineVariant = dynamicColors.outlineVariant().getArgb(scheme).toComposeColor(),
scrim = Color.Black,
surfaceBright = dynamicColors.surfaceBright().getArgb(scheme).toComposeColor(),
surfaceDim = dynamicColors.surfaceDim().getArgb(scheme).toComposeColor(),
surfaceContainer = dynamicColors.surfaceContainer().getArgb(scheme).toComposeColor(),
surfaceContainerHigh = dynamicColors.surfaceContainerHigh().getArgb(scheme).toComposeColor(),
surfaceContainerHighest = dynamicColors.surfaceContainerHighest().getArgb(scheme).toComposeColor(),
surfaceContainerLow = dynamicColors.surfaceContainerLow().getArgb(scheme).toComposeColor(),
surfaceContainerLowest = dynamicColors.surfaceContainerLowest().getArgb(scheme).toComposeColor(),
) )
.toColorScheme(isAmoled = false)
} }
} }
} }
@@ -1,5 +1,6 @@
package eu.kanade.presentation.track package eu.kanade.presentation.track
import android.content.ClipData
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -47,6 +48,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -55,11 +57,11 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.platform.Clipboard
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.platform.toClipEntry
import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.intl.Locale
@@ -73,6 +75,7 @@ import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import kotlinx.coroutines.launch
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@@ -240,7 +243,7 @@ private fun SearchResultItem(
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val clipboardManager: ClipboardManager = LocalClipboardManager.current val clipboard: Clipboard = LocalClipboard.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
@@ -248,6 +251,7 @@ private fun SearchResultItem(
val shape = RoundedCornerShape(16.dp) val shape = RoundedCornerShape(16.dp)
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
var dropDownMenuExpanded by remember { mutableStateOf(false) } var dropDownMenuExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -295,7 +299,13 @@ private fun SearchResultItem(
expanded = dropDownMenuExpanded, expanded = dropDownMenuExpanded,
onCollapseMenu = { dropDownMenuExpanded = false }, onCollapseMenu = { dropDownMenuExpanded = false },
onCopyName = { onCopyName = {
clipboardManager.setText(AnnotatedString(trackSearch.title)) scope.launch {
val clipEntry = ClipData.newPlainText(
trackSearch.title,
trackSearch.title,
).toClipEntry()
clipboard.setClipEntry(clipEntry)
}
}, },
onOpenInBrowser = { onOpenInBrowser = {
val url = trackSearch.tracking_url val url = trackSearch.tracking_url
@@ -0,0 +1,111 @@
package eu.kanade.presentation.updates
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.updates.service.UpdatesPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.SettingsItemsPaddings
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
@Composable
fun UpdatesFilterDialog(
onDismissRequest: () -> Unit,
screenModel: UpdatesSettingsScreenModel,
) {
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = persistentListOf(
stringResource(MR.strings.action_filter),
),
) {
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
FilterSheet(screenModel = screenModel)
}
}
}
@Composable
private fun ColumnScope.FilterSheet(
screenModel: UpdatesSettingsScreenModel,
) {
val filterDownloaded by screenModel.updatesPreferences.filterDownloaded.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_downloaded),
state = filterDownloaded,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) },
)
val filterUnread by screenModel.updatesPreferences.filterUnread.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_unread),
state = filterUnread,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnread) },
)
val filterStarted by screenModel.updatesPreferences.filterStarted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_started),
state = filterStarted,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) },
)
val filterBookmarked by screenModel.updatesPreferences.filterBookmarked.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) },
)
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators.collectAsState()
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators.getAndSet { !it }
Row(
modifier = Modifier
.clickable { toggleScanlatorFilter() }
.fillMaxWidth()
.padding(horizontal = SettingsItemsPaddings.Horizontal),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(MR.strings.action_filter_excluded_scanlators),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyMedium,
)
Switch(
checked = filterExcludedScanlators,
onCheckedChange = { toggleScanlatorFilter() },
)
}
}
@@ -5,9 +5,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CalendarMonth import androidx.compose.material.icons.outlined.CalendarMonth
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
@@ -37,6 +40,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.theme.active
import java.time.LocalDate import java.time.LocalDate
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -57,8 +61,10 @@ fun UpdateScreen(
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit, onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit, onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit, onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
onOpenChapter: (UpdatesItem) -> Unit, onOpenChapter: (UpdatesItem) -> Unit,
onFilterClicked: () -> Unit,
hasActiveFilters: Boolean,
) { ) {
BackHandler(enabled = state.selectionMode) { BackHandler(enabled = state.selectionMode) {
onSelectAll(false) onSelectAll(false)
@@ -69,6 +75,8 @@ fun UpdateScreen(
UpdatesAppBar( UpdatesAppBar(
onCalendarClicked = { onCalendarClicked() }, onCalendarClicked = { onCalendarClicked() },
onUpdateLibrary = { onUpdateLibrary() }, onUpdateLibrary = { onUpdateLibrary() },
onFilterClicked = { onFilterClicked() },
hasFilters = hasActiveFilters,
actionModeCounter = state.selected.size, actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) }, onSelectAll = { onSelectAll(true) },
onInvertSelection = { onInvertSelection() }, onInvertSelection = { onInvertSelection() },
@@ -139,6 +147,8 @@ fun UpdateScreen(
private fun UpdatesAppBar( private fun UpdatesAppBar(
onCalendarClicked: () -> Unit, onCalendarClicked: () -> Unit,
onUpdateLibrary: () -> Unit, onUpdateLibrary: () -> Unit,
onFilterClicked: () -> Unit,
hasFilters: Boolean,
// For action mode // For action mode
actionModeCounter: Int, actionModeCounter: Int,
onSelectAll: () -> Unit, onSelectAll: () -> Unit,
@@ -153,6 +163,12 @@ private fun UpdatesAppBar(
actions = { actions = {
AppBarActions( AppBarActions(
persistentListOf( persistentListOf(
AppBar.Action(
title = stringResource(MR.strings.action_filter),
icon = Icons.Outlined.FilterList,
iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current,
onClick = onFilterClicked,
),
AppBar.Action( AppBar.Action(
title = stringResource(MR.strings.action_view_upcoming), title = stringResource(MR.strings.action_view_upcoming),
icon = Icons.Outlined.CalendarMonth, icon = Icons.Outlined.CalendarMonth,
@@ -72,7 +72,7 @@ internal fun LazyListScope.updatesUiItems(
// SY --> // SY -->
preserveReadingPosition: Boolean, preserveReadingPosition: Boolean,
// SY <-- // SY <--
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit, onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
onClickCover: (UpdatesItem) -> Unit, onClickCover: (UpdatesItem) -> Unit,
onClickUpdate: (UpdatesItem) -> Unit, onClickUpdate: (UpdatesItem) -> Unit,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit, onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
@@ -120,11 +120,11 @@ internal fun LazyListScope.updatesUiItems(
) )
}, },
onLongClick = { onLongClick = {
onUpdateSelected(updatesItem, !updatesItem.selected, true, true) onUpdateSelected(updatesItem, !updatesItem.selected, true)
}, },
onClick = { onClick = {
when { when {
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false) selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, false)
else -> onClickUpdate(updatesItem) else -> onClickUpdate(updatesItem)
} }
}, },
@@ -9,21 +9,21 @@ import tachiyomi.domain.source.model.SourceNotInstalledException
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import java.net.UnknownHostException import java.net.UnknownHostException
context(Context) context(context: Context)
val Throwable.formattedMessage: String val Throwable.formattedMessage: String
get() { get() {
when (this) { when (this) {
is HttpException -> return stringResource(MR.strings.exception_http, code) is HttpException -> return context.stringResource(MR.strings.exception_http, code)
is UnknownHostException -> { is UnknownHostException -> {
return if (!isOnline()) { return if (!context.isOnline()) {
stringResource(MR.strings.exception_offline) context.stringResource(MR.strings.exception_offline)
} else { } else {
stringResource(MR.strings.exception_unknown_host, message ?: "") context.stringResource(MR.strings.exception_unknown_host, message ?: "")
} }
} }
is NoResultsException -> return stringResource(MR.strings.no_results_found) is NoResultsException -> return context.stringResource(MR.strings.no_results_found)
is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error) is SourceNotInstalledException -> return context.stringResource(MR.strings.loader_not_implemented_error)
} }
return when (val className = this::class.simpleName) { return when (val className = this::class.simpleName) {
"Exception", "IOException" -> message ?: className "Exception", "IOException" -> message ?: className
@@ -4,5 +4,7 @@ import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
// https://issuetracker.google.com/352584409 // https://issuetracker.google.com/352584409
context(LazyItemScope) context(itemScope: LazyItemScope)
fun Modifier.animateItemFastScroll() = this.animateItem(fadeInSpec = null, fadeOutSpec = null) fun Modifier.animateItemFastScroll() = with(itemScope) {
this@animateItemFastScroll.animateItem(fadeInSpec = null, fadeOutSpec = null)
}
@@ -80,7 +80,7 @@ fun EhLoginWebViewScreen(
) )
is LoadingState.Loading -> { is LoadingState.Loading -> {
val animatedProgress by animateFloatAsState( val animatedProgress by animateFloatAsState(
(loadingState as? LoadingState.Loading)?.progress ?: 1f, loadingState.progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
label = "webview_loading", label = "webview_loading",
) )
@@ -3,6 +3,8 @@ package eu.kanade.presentation.webview
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Message import android.os.Message
import android.webkit.JsPromptResult
import android.webkit.JsResult
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
@@ -20,6 +22,7 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -95,6 +98,11 @@ fun WebViewScreenContent(
var currentUrl by remember { mutableStateOf(url) } var currentUrl by remember { mutableStateOf(url) }
var showCloudflareHelp by remember { mutableStateOf(false) } var showCloudflareHelp by remember { mutableStateOf(false) }
var isActive by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
onDispose { isActive = false }
}
val webClient = remember { val webClient = remember {
object : AccompanistWebViewClient() { object : AccompanistWebViewClient() {
@@ -163,6 +171,36 @@ fun WebViewScreenContent(
} }
return false return false
} }
override fun onJsAlert(view: WebView, url: String?, message: String?, result: JsResult): Boolean {
if (!isActive) {
result.confirm()
return true
}
return super.onJsAlert(view, url, message, result)
}
override fun onJsConfirm(view: WebView, url: String?, message: String?, result: JsResult): Boolean {
if (!isActive) {
result.cancel()
return true
}
return super.onJsConfirm(view, url, message, result)
}
override fun onJsPrompt(
view: WebView,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult,
): Boolean {
if (!isActive) {
result.cancel()
return true
}
return super.onJsPrompt(view, url, message, defaultValue, result)
}
} }
} }
@@ -273,7 +311,7 @@ fun WebViewScreenContent(
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),
) )
is LoadingState.Loading -> LinearProgressIndicator( is LoadingState.Loading -> LinearProgressIndicator(
progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f }, progress = { loadingState.progress },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),
+8 -8
View File
@@ -141,7 +141,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val scope = ProcessLifecycleOwner.get().lifecycleScope val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled // Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes() basePreferences.incognitoMode.changes()
.onEach { enabled -> .onEach { enabled ->
if (enabled) { if (enabled) {
disableIncognitoReceiver.register() disableIncognitoReceiver.register()
@@ -169,25 +169,25 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
} }
.launchIn(scope) .launchIn(scope)
privacyPreferences.analytics() privacyPreferences.analytics
.changes() .changes()
.onEach(FirebaseConfig::setAnalyticsEnabled) .onEach(FirebaseConfig::setAnalyticsEnabled)
.launchIn(scope) .launchIn(scope)
privacyPreferences.crashlytics() privacyPreferences.crashlytics
.changes() .changes()
.onEach(FirebaseConfig::setCrashlyticsEnabled) .onEach(FirebaseConfig::setCrashlyticsEnabled)
.launchIn(scope) .launchIn(scope)
basePreferences.hardwareBitmapThreshold().let { preference -> basePreferences.hardwareBitmapThreshold.let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT) if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
} }
basePreferences.hardwareBitmapThreshold().changes() basePreferences.hardwareBitmapThreshold.changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it } .onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope) .launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get()) setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode.get())
// Updates widget update // Updates widget update
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) } WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
@@ -256,7 +256,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
crossfade((300 * this@App.animatorDurationScale).toInt()) crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(DeviceUtil.isLowRamDevice(this@App)) allowRgb565(DeviceUtil.isLowRamDevice(this@App))
if (networkPreferences.verboseLogging().get()) logger(DebugLogger()) if (networkPreferences.verboseLogging.get()) logger(DebugLogger())
// Coil spawns a new thread for every image load by default // Coil spawns a new thread for every image load by default
fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8)) fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
@@ -376,7 +376,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
private var registered = false private var registered = false
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
basePreferences.incognitoMode().set(false) basePreferences.incognitoMode.set(false)
} }
fun register() { fun register() {
@@ -101,7 +101,7 @@ class BackupNotifier(private val context: Context) {
} }
setContentTitle(contentTitle) setContentTitle(contentTitle)
if (!preferences.hideNotificationContent().get()) { if (!preferences.hideNotificationContent.get()) {
setContentText(content) setContentText(content)
} }
@@ -89,7 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
fun setupTask(context: Context, prefInterval: Int? = null) { fun setupTask(context: Context, prefInterval: Int? = null) {
val backupPreferences = Injekt.get<BackupPreferences>() val backupPreferences = Injekt.get<BackupPreferences>()
val interval = prefInterval ?: backupPreferences.backupInterval().get() val interval = prefInterval ?: backupPreferences.backupInterval.get()
if (interval > 0) { if (interval > 0) {
val constraints = Constraints( val constraints = Constraints(
requiresBatteryNotLow = true, requiresBatteryNotLow = true,
@@ -124,7 +124,7 @@ class BackupCreator(
BackupFileValidator(context).validate(fileUri) BackupFileValidator(context).validate(fileUri)
if (isAutoBackup) { if (isAutoBackup) {
backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli()) backupPreferences.lastAutoBackupTimestamp.set(Instant.now().toEpochMilli())
} }
return fileUri.toString() return fileUri.toString()
@@ -13,12 +13,18 @@ class BackupCategory(
@ProtoNumber(100) var flags: Long = 0, @ProtoNumber(100) var flags: Long = 0,
// SY specific values // SY specific values
/*@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),*/ /*@ProtoNumber(600) var mangaOrder: List<Long> = emptyList(),*/
@ProtoNumber(601) var version: Long = 0,
@ProtoNumber(602) var uid: Long = 0,
@ProtoNumber(603) var lastModifiedAt: Long = 0,
) { ) {
fun toCategory(id: Long) = Category( fun toCategory(id: Long) = Category(
id = id, id = id,
name = this@BackupCategory.name, name = this@BackupCategory.name,
flags = this@BackupCategory.flags, flags = this@BackupCategory.flags,
order = this@BackupCategory.order, order = this@BackupCategory.order,
version = this@BackupCategory.version,
uid = this@BackupCategory.uid,
lastModifiedAt = this@BackupCategory.lastModifiedAt,
/*mangaOrder = this@BackupCategory.mangaOrder*/ /*mangaOrder = this@BackupCategory.mangaOrder*/
) )
} }
@@ -29,5 +35,8 @@ val backupCategoryMapper = { category: Category ->
name = category.name, name = category.name,
order = category.order, order = category.order,
flags = category.flags, flags = category.flags,
version = category.version,
uid = category.uid,
lastModifiedAt = category.lastModifiedAt,
) )
} }
@@ -17,22 +17,65 @@ class CategoriesRestorer(
if (backupCategories.isNotEmpty()) { if (backupCategories.isNotEmpty()) {
val dbCategories = getCategories.await() val dbCategories = getCategories.await()
val dbCategoriesByName = dbCategories.associateBy { it.name } val dbCategoriesByName = dbCategories.associateBy { it.name }
// SY -->
val dbCategoriesByUid = dbCategories.associateBy { it.uid } // Map by UID
// SY <--
var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0 var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0
val categories = backupCategories val categories = backupCategories
.sortedBy { it.order } .sortedBy { it.order }
.map { // SY -->
val dbCategory = dbCategoriesByName[it.name] .map { backupCategory ->
if (dbCategory != null) return@map dbCategory var dbCategory = if (backupCategory.uid != 0L) {
val order = nextOrder++ dbCategoriesByUid[backupCategory.uid]
handler.awaitOneExecutable { } else {
categoriesQueries.insert(it.name, order, it.flags) null
categoriesQueries.selectLastInsertedRowId()
}
.let { id -> it.toCategory(id).copy(order = order) }
} }
libraryPreferences.categorizedDisplaySettings().set( if (dbCategory == null) {
dbCategory = dbCategoriesByName[backupCategory.name]
}
if (dbCategory != null) {
handler.await {
categoriesQueries.update(
name = backupCategory.name,
order = backupCategory.order,
flags = backupCategory.flags,
version = backupCategory.version,
uid = if (backupCategory.uid != 0L) backupCategory.uid else dbCategory.uid,
last_modified_at = backupCategory.lastModifiedAt,
isSyncing = 1,
categoryId = dbCategory.id,
)
}
return@map dbCategory
}
val order = nextOrder++
handler.awaitOneExecutable {
categoriesQueries.insert(
backupCategory.name,
order,
backupCategory.flags,
backupCategory.version,
backupCategory.uid,
backupCategory.lastModifiedAt,
)
categoriesQueries.selectLastInsertedRowId()
}
.let { id -> backupCategory.toCategory(id).copy(order = order) }
}
// SY <--
// SY -->
handler.await {
categoriesQueries.resetIsSyncing()
}
// SY <--
libraryPreferences.categorizedDisplaySettings.set(
(dbCategories + categories) (dbCategories + categories)
.distinctBy { it.flags } .distinctBy { it.flags }
.size > 1, .size > 1,
@@ -43,10 +43,10 @@ class ChapterCache(
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = CoroutineScope(Job() + Dispatchers.Main)
/** Cache class used for cache management. */ /** Cache class used for cache management. */
private var diskCache = setupDiskCache(readerPreferences.cacheSize().get().toLong()) private var diskCache = setupDiskCache(readerPreferences.cacheSize.get().toLong())
init { init {
readerPreferences.cacheSize().changes() readerPreferences.cacheSize.changes()
.drop(1) .drop(1)
.onEach { .onEach {
// Save old cache for destruction later // Save old cache for destruction later
@@ -56,7 +56,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
var networkCheck = checkNetworkState( var networkCheck = checkNetworkState(
applicationContext.activeNetworkState(), applicationContext.activeNetworkState(),
downloadPreferences.downloadOnlyOverWifi().get(), downloadPreferences.downloadOnlyOverWifi.get(),
) )
var active = networkCheck && downloadManager.downloaderStart() var active = networkCheck && downloadManager.downloaderStart()
@@ -69,7 +69,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
coroutineScope { coroutineScope {
combineTransform( combineTransform(
applicationContext.networkStateFlow(), applicationContext.networkStateFlow(),
downloadPreferences.downloadOnlyOverWifi().changes(), downloadPreferences.downloadOnlyOverWifi.changes(),
transform = { a, b -> emit(checkNetworkState(a, b)) }, transform = { a, b -> emit(checkNetworkState(a, b)) },
) )
.onEach { networkCheck = it } .onEach { networkCheck = it }
@@ -470,7 +470,7 @@ class DownloadManager(
private suspend fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> { private suspend fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
// Retrieve the categories that are set to exclude from being deleted on read // Retrieve the categories that are set to exclude from being deleted on read
val categoriesToExclude = downloadPreferences.removeExcludeCategories().get().map(String::toLong) val categoriesToExclude = downloadPreferences.removeExcludeCategories.get().map(String::toLong)
val categoriesForManga = getCategories.await(manga.id) val categoriesForManga = getCategories.await(manga.id)
.map { it.id } .map { it.id }
@@ -481,7 +481,7 @@ class DownloadManager(
chapters chapters
} }
return if (!downloadPreferences.removeBookmarkedChapters().get()) { return if (!downloadPreferences.removeBookmarkedChapters.get()) {
filteredCategoryManga.filterNot { it.bookmark } filteredCategoryManga.filterNot { it.bookmark }
} else { } else {
filteredCategoryManga filteredCategoryManga
@@ -96,7 +96,7 @@ internal class DownloadNotifier(private val context: Context) {
download.pages!!.size, download.pages!!.size,
) )
if (preferences.hideNotificationContent().get()) { if (preferences.hideNotificationContent.get()) {
setContentTitle(downloadingProgressText) setContentTitle(downloadingProgressText)
setContentText(null) setContentText(null)
} else { } else {
@@ -164,7 +164,7 @@ class DownloadProvider(
fun getSourceDirName(source: Source): String { fun getSourceDirName(source: Source): String {
return DiskUtil.buildValidFilename( return DiskUtil.buildValidFilename(
source.toString(), source.toString(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(), disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
) )
} }
@@ -176,7 +176,7 @@ class DownloadProvider(
fun getMangaDirName(mangaTitle: String): String { fun getMangaDirName(mangaTitle: String): String {
return DiskUtil.buildValidFilename( return DiskUtil.buildValidFilename(
mangaTitle, mangaTitle,
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(), disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
) )
} }
@@ -191,8 +191,8 @@ class DownloadProvider(
chapterName: String, chapterName: String,
chapterScanlator: String?, chapterScanlator: String?,
chapterUrl: String, chapterUrl: String,
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames().get(), disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames.get(),
includeChapterUrlHash: Boolean = downloadPreferences.includeChapterUrlHash().get(), includeChapterUrlHash: Boolean = downloadPreferences.includeChapterUrlHash.get(),
): String { ): String {
var dirName = sanitizeChapterName(chapterName) var dirName = sanitizeChapterName(chapterName)
if (!chapterScanlator.isNullOrBlank()) { if (!chapterScanlator.isNullOrBlank()) {
@@ -235,8 +235,8 @@ class DownloadProvider(
chapterName, chapterName,
chapterScanlator, chapterScanlator,
chapterUrl, chapterUrl,
!libraryPreferences.disallowNonAsciiFilenames().get(), !libraryPreferences.disallowNonAsciiFilenames.get(),
!downloadPreferences.includeChapterUrlHash().get(), !downloadPreferences.includeChapterUrlHash.get(),
) )
return buildList(2) { return buildList(2) {
@@ -203,7 +203,7 @@ class Downloader(
downloaderJob = scope.launch { downloaderJob = scope.launch {
val activeDownloadsFlow = combine( val activeDownloadsFlow = combine(
queueState, queueState,
downloadPreferences.parallelSourceLimit().changes(), downloadPreferences.parallelSourceLimit.changes(),
) { a, b -> a to b }.transformLatest { (queue, parallelCount) -> ) { a, b -> a to b }.transformLatest { (queue, parallelCount) ->
while (true) { while (true) {
val activeDownloads = queue.asSequence() val activeDownloads = queue.asSequence()
@@ -380,7 +380,7 @@ class Downloader(
reIndexedPages reIndexedPages
} }
val dataSaver = if (sourcePreferences.dataSaverDownloader().get()) { val dataSaver = if (sourcePreferences.dataSaverDownloader.get()) {
DataSaver(download.source, sourcePreferences) DataSaver(download.source, sourcePreferences)
} else { } else {
DataSaver.NoOp DataSaver.NoOp
@@ -394,7 +394,7 @@ class Downloader(
download.status = Download.State.DOWNLOADING download.status = Download.State.DOWNLOADING
// Start downloading images, consider we can have downloaded images already // Start downloading images, consider we can have downloaded images already
pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit().get()) { page -> pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit.get()) { page ->
flow { flow {
// Fetch image URL if necessary // Fetch image URL if necessary
if (page.imageUrl.isNullOrEmpty()) { if (page.imageUrl.isNullOrEmpty()) {
@@ -431,7 +431,7 @@ class Downloader(
) )
// Only rename the directory if it's downloaded // Only rename the directory if it's downloaded
if (downloadPreferences.saveChaptersAsCBZ().get()) { if (downloadPreferences.saveChaptersAsCBZ.get()) {
archiveChapter(mangaDir, chapterDirname, tmpDir) archiveChapter(mangaDir, chapterDirname, tmpDir)
} else { } else {
tmpDir.renameTo(chapterDirname) tmpDir.renameTo(chapterDirname)
@@ -579,7 +579,7 @@ class Downloader(
} }
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
if (!downloadPreferences.splitTallImages().get()) return if (!downloadPreferences.splitTallImages.get()) return
try { try {
val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number) val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number)

Some files were not shown because too many files have changed in this diff Show More