Compare commits

...
88 Commits
Author SHA1 Message Date
AntsyLich 7a917968e3 Release v0.19.9 2026-04-11 14:17:30 +06:00
AntsyLich 5f548243e1 Revert "Remove usage of deprecated LocalBroadcastManager (#3215)"
This reverts commit b201ed163e.
2026-04-11 14:13:15 +06:00
AntsyLich 34d22e9495 Revert "Update dependency com.google.android.material:material to v1.13.0 (#3104)"
This reverts commit 1712b9cdc5.
2026-04-11 14:13:10 +06:00
AntsyLich bc4fc765b2 Release v0.19.8 2026-04-11 12:00:00 +06:00
253a560b20 Translations update from Hosted Weblate (#3222)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
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/zh_Hans/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Bartolumiu <bartolumiu@tr25.es>
Co-authored-by: Dexroneum <Rozhenkov69@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: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: ndeerova <ovandoerik@gmail.com>
2026-04-11 11:50:41 +06:00
Mend RenovateandAntsyLich eb3d2c6676 Update dependency com.google.firebase:firebase-bom to v34.12.0 (#3216)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-04-10 01:18:21 +00:00
Mend Renovate b1c6994977 Update dependency com.google.firebase.crashlytics to v3.0.7 (#3219) 2026-04-10 07:07:41 +06:00
Mend Renovate 2990762df9 Update aboutLibraries to v14.0.1 (#3217) 2026-04-10 07:06:37 +06:00
MajorTanya 8e6af6492b Fix AniList user list item fetch erroring (#3220)
The countryOfOrigin field was not included in the findLibManga query
so it is not returned by AniList. Defaulting to an empty string here
should be fine since neither countryOfOrigin nor format are shown in
the findLibManga consumers (i.e. the tracker sheet of a given library
entry).
2026-04-10 07:05:25 +06:00
AntsyLich c4a606112a Slightly tweak paragraph 3 of donation popup 2026-04-10 02:38:16 +06:00
+6 230dc6fd05 Translations update from Hosted Weblate (#3141)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pl/
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-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
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/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
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
Translation: Mihon/Mihon Plurals

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ram <abhyram@tutamail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Victoria Niedzielska <royal.victoria@proton.me>
Co-authored-by: ZenVinny <atdenada@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: on9686 <on9686@gmail.com>
2026-04-10 02:28:15 +06:00
AntsyLich e4b7002909 Add a one time popup asking for donation from long time users (#3203)
Also remove the empty "Updated to X.Y.Z" popup after app update
2026-04-09 20:08:20 +00:00
Mend Renovate 02c6427823 Update kotlinx.serialization to v1.11.0 (#3218) 2026-04-10 02:06:43 +06:00
AntsyLich 50b0ccb928 Add dedicated "Support Us" screen (#3200) 2026-04-09 19:56:24 +00:00
Mend Renovate 1712b9cdc5 Update dependency com.google.android.material:material to v1.13.0 (#3104) 2026-04-09 23:34:03 +06:00
AntsyLich b201ed163e Remove usage of deprecated LocalBroadcastManager (#3215) 2026-04-09 23:24:13 +06:00
Mend Renovate 3521ccfb4f Update moko.resources to v0.26.3 (#3213) 2026-04-09 22:58:44 +06:00
Mend Renovate b7cdfb1b30 Update aboutLibraries to v14 (major) (#3195)
Update aboutLibraries to v14
2026-04-09 10:26:25 +06:00
Mend Renovate 4b1ae0526b Update dependency androidx.compose:compose-bom-beta to v2026.04.00 (#3205) 2026-04-09 10:19:03 +06:00
Ananda Umamil 378e734eed Show AniList publishing type based on country of origin (#3100) 2026-04-09 01:45:59 +00:00
Mend Renovate ed3b5305f1 Update dependency androidx.annotation:annotation to v1.10.0 (#3204) 2026-04-08 20:38:00 +00:00
Leodyver SemillaandAntsyLich cfc69d8d0b Fix app trying to split long strip when not needed (#3121)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-04-08 20:27:44 +00:00
Mend Renovate 2a1859d7d7 Update moko.resources to v0.26.2 (#3202) 2026-04-08 09:41:04 +00:00
AntsyLich 8a2a2b1f36 Make "Support Us" more prominent on More tab (#3196) 2026-04-08 01:27:40 +06:00
Mend Renovate af04276bed Update dependency io.kotest:kotest-assertions-core to v6.1.11 (#3185) 2026-04-08 00:36:33 +06:00
ArthurKunandAntsyLich 25d4bf5e2f Add gradle.properties to build-logic synced with root gradle.properties (#3159)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-31 05:40:06 +06:00
MajorTanya d75db63258 Run tests before compiling APK in CI (#3174) 2026-03-30 23:17:26 +00:00
MajorTanya 52fc38e965 Run SQLDelight database migration verification task in CI (#3170) 2026-03-31 05:15:32 +06:00
Mend Renovate 580863e891 Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.1 (#3173) 2026-03-30 22:52:48 +00:00
Mend Renovate 85eb539c4d Update dependency io.kotest:kotest-assertions-core to v6.1.10 (#3172) 2026-03-31 04:42:10 +06:00
Leodyver SemillaandAntsyLich 09ee38bb7a Don't prompt users to enable Google Play services if disabled or unavailable (#3152)
* fix(telemetry): prevent Google Play Services spam notifications

Check for Google Play Services availability before initializing Firebase
to prevent system notifications when GPS is disabled or uninstalled.

- Add GPS availability check using GoogleApiAvailability
- Wrap Firebase initialization in try-catch for additional safety
- Skip Firebase initialization gracefully when GPS unavailable
- Add proper logging for debugging purposes
- Add core.common dependency to telemetry module for logcat support

Fixes #3135

* docs: add changelog entry for Google Play Services notification fix

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-26 09:35:16 +00:00
MajorTanya af4f17efc5 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.
2026-03-26 15:22:37 +06:00
Mend Renovate fa197a4ab2 Update dependency androidx.compose:compose-bom-beta to v2026.03.01 (#3147) 2026-03-26 14:36:15 +06:00
Mend Renovate c3243882b1 Update r0adkll/sign-android-release digest to a391126 (#3145) 2026-03-26 14:34:25 +06:00
Mend Renovate a938708e1f Update dependency androidx.work:work-runtime to v2.11.2 (#3146) 2026-03-26 14:34:09 +06:00
Christos 2e635ff2ce 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-03-26 14:28:11 +06:00
AntsyLich 209805359e Merge tag 'v0.19.7' 2026-03-25 23:58:00 +06:00
AntsyLich eb5b762515 Revert "Update android.gradle to v9 (major) (#3105)"
This reverts commit 34afdabcfe.
2026-03-25 23:57:35 +06:00
Mend Renovate 7c5034275f Update dependency io.kotest:kotest-assertions-core to v6.1.9 (#3137) 2026-03-25 15:43:04 +06:00
Mend Renovate d75a125b26 Update dependency io.kotest:kotest-assertions-core to v6.1.8 (#3132) 2026-03-24 14:10:52 +06:00
AntsyLich 54d2c7b9e0 Release v0.19.7 2026-03-23 22:31:36 +06:00
MajorTanya 8b9d0a89c8 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-03-23 22:22:53 +06:00
AntsyLich 3bde967291 Release v0.19.6 2026-03-23 21:25:08 +06:00
AntsyLich b40f1d3975 Fix app crash on startup on some Android TV
Why do you guys even exist
2026-03-23 21:12:30 +06:00
AntsyLich 42daa3f432 Fix occasional crash when mass installing/uninstalling extension using PackageManager 2026-03-23 20:18:00 +06:00
AntsyLich f8e82b9322 Potentially fix 'database is locked' crash 2026-03-23 20:18:00 +06:00
AntsyLich cb0e329716 Fix wrong exception being caught after 8c480c6355 migration
`android.database.sqlite.SQLiteException` instead of `android.database.SQLException`
2026-03-23 18:05:09 +06:00
Mend RenovateandAntsyLich 34afdabcfe Update android.gradle to v9 (major) (#3105)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-22 19:02:55 +06:00
MajorTanya bc32308327 Address CancellableContinuation.resume deprecation (#3115)
According to the source docs, this has been a warning since
kotlinx.coroutines 1.9.0.
2026-03-21 20:17:35 +00:00
Mend Renovate c95269afd7 Update Gradle to v9 (#2333) 2026-03-21 19:49:57 +00:00
AntsyLich 4af8f1eab0 Cleanup and rework build logic (#3113) 2026-03-22 01:34:45 +06:00
+2 2366fbb4b3 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>
2026-03-22 00:39:16 +06:00
AntsyLich e930f38608 Merge and cleanup version catalogs (#3103) 2026-03-20 21:15:40 +06:00
AntsyLich b58d2a5d4b Release v0.19.5 2026-03-20 15:32:32 +06:00
AntsyLich d3dcb7fe4b Revert "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)"
This reverts commit 22d5c9d9f4.
2026-03-20 15:28:29 +06:00
AntsyLich 56b70ef529 Revert "Fix cache invalidation isn't done at startup (#2970)"
This reverts commit e6cc38de06.
2026-03-20 15:25:27 +06:00
Mend Renovate 5cf305b620 Update dependency com.google.firebase:firebase-bom to v34.11.0 (#3094) 2026-03-20 15:00:27 +06:00
AntsyLich 87665736a9 Replace preference getter functions with properties (#3091) 2026-03-20 14:57:01 +06:00
Leodyver SemillaandAntsyLich 2f9edb551f Fix extension actions disappearing after installing and uninstalling in same session (#3049)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-19 10:48:16 +00:00
AntsyLich d4b88b13b8 Make retry in reader redownload image (#3089) 2026-03-19 10:40:49 +00:00
Leodyver SemillaandAntsyLich 99edfbe4a8 Fix WebView JavaScript dialogs popup after screen is closed (#3041)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-19 16:01:35 +06:00
AntsyLich fa7155896c Update CHANGELOG.md 2026-03-19 15:31:57 +06:00
Leodyver SemillaandAntsyLich 51fb9c0b76 MangaUpdates API content-type heade (#3021)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-19 08:45:11 +00:00
Leodyver Semilla c495753315 Fix tracker-induced duplicate key crash in duplicate detection (#3040) 2026-03-19 08:24:58 +00:00
Mend Renovate 11ced33fdb Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.4.0 (#3086) 2026-03-19 14:13:04 +06:00
AntsyLich 8c480c6355 Switch to AndroidX bundled sqlite driver (#3082) 2026-03-18 16:08:40 +06:00
Mend Renovate e9ca71d837 Update softprops/action-gh-release action to v2.6.1 (#3072) 2026-03-18 15:11:55 +06:00
Mend Renovate e4091c0160 Update paging.version to v3.4.2 (#3063) 2026-03-18 15:11:28 +06:00
Mend Renovate a2d22a7d55 Update kotlin monorepo to v2.3.20 (#3074) 2026-03-18 13:16:56 +06:00
Mend Renovate db22517339 Update sqldelight to v2.3.2 (#3077) 2026-03-18 12:57:23 +06:00
MajorTanya 33faa5a356 Address bundleOf deprecation (#3073) 2026-03-18 12:56:10 +06:00
Mend RenovateandAntsyLich 8c5ae44f51 Update sqldelight to v2.3.1 (#3071)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-03-16 05:53:43 +00:00
Mend Renovate 575d7e8b89 Update dependency io.kotest:kotest-assertions-core to v6.1.7 (#3062) 2026-03-16 11:43:17 +06:00
Mend Renovate 8c00b44eda Update moko to v0.26.1 (#3068) 2026-03-16 11:42:16 +06:00
Mend Renovate 77a86d486f Update dependency com.squareup.okio:okio to v3.17.0 (#3070) 2026-03-15 13:34:22 +06:00
Mend Renovate 4b379576f4 Update softprops/action-gh-release action to v2.5.3 (#3064) 2026-03-15 12:41:04 +06:00
Mend Renovate be88ce199c Update dependency androidx.activity:activity-compose to v1.13.0 (#3065) 2026-03-15 12:39:34 +06:00
Mend Renovate 32b8ef6240 Update dependency androidx.core:core-ktx to v1.18.0 (#3067) 2026-03-15 12:38:51 +06:00
Mend Renovate f82b8300b0 Update dependency androidx.compose:compose-bom to v2026.03.00 (#3066) 2026-03-15 12:37:51 +06:00
AntsyLich ce5820b496 Bump workflows JDK to 21 (#3053) 2026-03-14 12:01:56 +06:00
AntsyLich f6b2684323 Add installation id for feature flags (#3052) 2026-03-10 23:53:11 +06:00
Mend Renovate 0d768af3d0 Update dependency androidx.compose:compose-bom to v2026.02.01 (#3009) 2026-03-06 12:30:53 +06:00
Mend Renovate 08f0bcc0d8 Update dependency com.google.firebase:firebase-bom to v34.10.0 (#3006) 2026-03-06 12:30:42 +06:00
Mend Renovate a3c45a05b8 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.3.0 (#3029) 2026-03-06 12:28:45 +06:00
Mend Renovate 6926914385 Update dependency com.materialkolor:material-kolor to v5.0.0-alpha07 (#3024) 2026-03-06 12:28:14 +06:00
Mend Renovate f3d5491a7d Update actions/dependency-review-action action to v4.9.0 (#3036) 2026-03-06 12:27:42 +06:00
AntsyLich 81871a3469 Reapply "Fix cache invalidation isn't done at startup (#2970)"
This reverts commit d219c5e3bb.
2026-02-26 18:43:03 +06:00
AntsyLich 0024278f4b Reapply "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)"
This reverts commit 1d7c838ae6.
2026-02-26 18:42:54 +06:00
312 changed files with 3749 additions and 2179 deletions
+1
View File
@@ -0,0 +1 @@
21
+1 -1
View File
@@ -30,7 +30,7 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to version **[0.19.4](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.9](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+2 -2
View File
@@ -52,7 +52,7 @@ body:
label: Mihon version
description: You can find your Mihon version in **More → About**.
placeholder: |
Example: "0.19.4"
Example: "0.19.9"
validations:
required: true
@@ -95,7 +95,7 @@ body:
required: true
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.19.4](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.9](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
+8 -5
View File
@@ -30,12 +30,12 @@ jobs:
- name: Dependency Review
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
- name: Set up JDK
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
@@ -44,13 +44,13 @@ jobs:
- name: Check code format
run: ./gradlew spotlessCheck
- name: Build app
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Run unit tests
id: unit_tests
run: ./gradlew testReleaseUnitTest
- name: Verify SQLDelight migrations
run: ./gradlew verifySqlDelightMigration
- name: Upload test report
if: steps.unit_tests.outcome == 'failure'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
@@ -58,6 +58,9 @@ jobs:
name: test-report-${{ github.sha }}
path: app/build/reports/tests/testReleaseUnitTest
- name: Build app
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Upload APK
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
+5 -5
View File
@@ -34,7 +34,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
@@ -44,7 +44,7 @@ jobs:
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Sign APK
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
uses: r0adkll/sign-android-release@a391126403df92c6a0a3988a829dd47f79b89ed7
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
@@ -88,7 +88,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
@@ -100,7 +100,7 @@ jobs:
run: ./gradlew assembleFoss -Penable-updater
- name: Sign APK
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
uses: r0adkll/sign-android-release@a391126403df92c6a0a3988a829dd47f79b89ed7
with:
releaseDirectory: app/build/outputs/apk/foss
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
@@ -143,7 +143,7 @@ jobs:
mihon-foss
- name: Create GitHub Release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
tag_name: ${{ needs.get_tag.outputs.tag }}
name: Mihon ${{ needs.get_tag.outputs.tag }}
+52 -3
View File
@@ -12,6 +12,50 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
## [Unreleased]
## [v0.19.9] - 2026-04-11
### Fixed
- Regression with installing/updating extension ([@AntsyLich](https://github.com/AntsyLich))
## [v0.19.8] - 2026-04-11
### Added
- Add dedicated "Support Us" screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3200](https://github.com/mihonapp/mihon/pull/3200))
- Add a one time popup asking for donation from long time users ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
### Changed
- Don't prompt users to enable Google Play services if disabled or unavailable ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3152](https://github.com/mihonapp/mihon/pull/3152))
- Make "Support Us" more prominent in More screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3196](https://github.com/mihonapp/mihon/pull/3196))
### Improved
- Show informative error when trying to add unapproved titles to list on MAL ([@MajorTanya](https://github.com/MajorTanya)) ([#3155](https://github.com/mihonapp/mihon/pull/3155))
- Show AniList publishing type based on country of origin ([@zweimach](https://github.com/zweimach)) ([#3100](https://github.com/mihonapp/mihon/pull/3100))
### Fixed
- Fix app trying to split long strip when not needed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3121](https://github.com/mihonapp/mihon/pull/3121))
### Removed
- Remove the empty "Updated to X.Y.Z" popup after app update ([@AntsyLich](https://github.com/AntsyLich)) ([#3203](https://github.com/mihonapp/mihon/pull/3203))
## [v0.19.7] - 2026-03-23
Same as v0.19.6
## [v0.19.6] - 2026-03-23
### Fixed
- Fix app crashing when trying to add extension repo with existing signature ([@AntsyLich](https://github.com/AntsyLich)) ([`cb0e329`](https://github.com/mihonapp/mihon/commit/cb0e329))
- Potentially fix 'database is locked' crash ([@AntsyLich](https://github.com/AntsyLich)) ([`f8e82b9`](https://github.com/mihonapp/mihon/commit/f8e82b9))
- Fix occasional crash when mass installing/uninstalling extension using `PackageManager` ([@AntsyLich](https://github.com/AntsyLich)) ([`42daa3f`](https://github.com/mihonapp/mihon/commit/42daa3f))
- Fix app crash on startup on some Android TV ([@AntsyLich](https://github.com/AntsyLich)) ([`b40f1d3`](https://github.com/mihonapp/mihon/commit/b40f1d3))
## [v0.19.5] - 2026-03-20
### Changed
- Retry in reader now redownloads image ([@AntsyLich](https://github.com/AntsyLich)) ([#3089](https://github.com/mihonapp/mihon/pull/3089))
### Fixed
- Fix performance regression introduced in v0.19.4 ([@AntsyLich](https://github.com/AntsyLich)) ([#3082](https://github.com/mihonapp/mihon/pull/3082))
- Fix duplicate key crash in duplicate detection ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3040](https://github.com/mihonapp/mihon/pull/3040))
- Fix MangaUpdates HTTP 4XX errors ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3021](https://github.com/mihonapp/mihon/pull/3021))
- Fix WebView JavaScript dialogs popup after screen is closed ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3041](https://github.com/mihonapp/mihon/pull/3041))
- Fix extension actions disappearing after installing and uninstalling in same session ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3049](https://github.com/mihonapp/mihon/pull/3049))
## [v0.19.4] - 2026-02-25
### Added
- Automatically remove downloads on Suwayomi after reading, configurable via extension settings ([@cpiber](https://github.com/cpiber)) ([#2673](https://github.com/mihonapp/mihon/pull/2673))
@@ -26,7 +70,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Optimize MAL search queries ([@MajorTanya](https://github.com/MajorTanya)) ([#2832](https://github.com/mihonapp/mihon/pull/2832))
- Reword download reindexing message to avoid confusion ([@MajorTanya](https://github.com/MajorTanya)) ([#2874](https://github.com/mihonapp/mihon/pull/2874))
- Optimize tracked library filter ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2977](https://github.com/mihonapp/mihon/pull/2977))
- Utilize tracker for library duplicate detection ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2977](https://github.com/mihonapp/mihon/pull/2978))
- Utilize tracker for library duplicate detection ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2978](https://github.com/mihonapp/mihon/pull/2978))
### Changed
- Update tracker icons ([@AntsyLich](https://github.com/AntsyLich)) ([#2773](https://github.com/mihonapp/mihon/pull/2773))
@@ -46,7 +90,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Fix migration's selected sources order not preserved ([@AntsyLich](https://github.com/AntsyLich)) ([#2993](https://github.com/mihonapp/mihon/pull/2993))
- Fix migration dialog not showing for consecutive prompts from the same screen ([@AntsyLich](https://github.com/AntsyLich)) ([#2994](https://github.com/mihonapp/mihon/pull/2994))
- Fix extension install/update stuck at pending ([@AntsyLich](https://github.com/AntsyLich)) ([#3000](https://github.com/mihonapp/mihon/pull/3000))
- Fix crash when downloading multiple chapters with Arabic locale ([@MajorTanya](https://github.com/MajorTanya)) ([#2676](https://github.com/mihonapp/mihon/pull/2676))
- Fix crash when downloading multiple chapters with Arabic locale ([@MajorTanya](https://github.com/MajorTanya)) ([#2676](https://github.com/mihonapp/mihon/pull/2676))
### Other
- Enable logcat logging on stable and debug builds without enabling verbose logging ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2836](https://github.com/mihonapp/mihon/pull/2836))
@@ -484,7 +528,12 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.4...main
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.9...main
[v0.19.9]: https://github.com/mihonapp/mihon/compare/v0.19.8...v0.19.9
[v0.19.8]: https://github.com/mihonapp/mihon/compare/v0.19.7...v0.19.8
[v0.19.7]: https://github.com/mihonapp/mihon/compare/v0.19.6...v0.19.7
[v0.19.6]: https://github.com/mihonapp/mihon/compare/v0.19.5...v0.19.6
[v0.19.5]: https://github.com/mihonapp/mihon/compare/v0.19.4...v0.19.5
[v0.19.4]: https://github.com/mihonapp/mihon/compare/v0.19.3...v0.19.4
[v0.19.3]: https://github.com/mihonapp/mihon/compare/v0.19.2...v0.19.3
[v0.19.2]: https://github.com/mihonapp/mihon/compare/v0.19.1...v0.19.2
+75 -64
View File
@@ -1,14 +1,16 @@
import mihon.buildlogic.Config
import mihon.buildlogic.getBuildTime
import mihon.buildlogic.getCommitCount
import mihon.buildlogic.getGitSha
import mihon.gradle.Config
import mihon.gradle.getBuildTime
import mihon.gradle.getLatestCommitCount
import mihon.gradle.getLatestCommitSha
import mihon.gradle.tasks.ReplaceShortcutsPlaceholderTask
plugins {
id("mihon.android.application")
id("mihon.android.application.compose")
id("com.github.zellius.shortcut-helper")
kotlin("plugin.serialization")
alias(mihonx.plugins.android.application)
alias(mihonx.plugins.compose)
alias(mihonx.plugins.spotless)
alias(libs.plugins.aboutLibraries)
alias(libs.plugins.kotlin.serialization)
}
if (Config.includeTelemetry) {
@@ -18,20 +20,18 @@ if (Config.includeTelemetry) {
}
}
shortcutHelper.setFilePath("./shortcuts.xml")
android {
namespace = "eu.kanade.tachiyomi"
defaultConfig {
applicationId = "app.mihon"
versionCode = 17
versionName = "0.19.4"
versionCode = 22
versionName = "0.19.9"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
buildConfigField("String", "COMMIT_COUNT", "\"${getLatestCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getLatestCommitSha()}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}")
buildConfigField("boolean", "UPDATER_ENABLED", "${Config.enableUpdater}")
@@ -41,7 +41,7 @@ android {
buildTypes {
val debug by getting {
applicationIdSuffix = ".dev"
versionNameSuffix = "-${getCommitCount()}"
versionNameSuffix = "-${getLatestCommitCount()}"
isPseudoLocalesEnabled = true
}
val release by getting {
@@ -50,7 +50,7 @@ android {
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = true)}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = true)}\"")
}
val commonMatchingFallbacks = listOf(release.name)
@@ -72,7 +72,7 @@ android {
matchingFallbacks.addAll(commonMatchingFallbacks)
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
}
create("benchmark") {
initWith(release)
@@ -185,91 +185,89 @@ dependencies {
implementation(projects.telemetry)
// Compose
implementation(compose.activity)
implementation(compose.foundation)
implementation(compose.material3.core)
implementation(compose.material.icons)
implementation(compose.animation)
implementation(compose.animation.graphics)
debugImplementation(compose.ui.tooling)
implementation(compose.ui.tooling.preview)
implementation(compose.ui.util)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.materialIcons)
implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.animationGraphics)
debugImplementation(libs.androidx.compose.uiTooling)
implementation(libs.androidx.compose.uiToolingPreview)
implementation(libs.androidx.compose.uiUtil)
implementation(androidx.interpolator)
implementation(libs.androidx.interpolator)
implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(libs.androidx.sqlite.bundled)
implementation(kotlinx.reflect)
implementation(kotlinx.immutables)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.collections.immutable)
implementation(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines)
implementation(libs.bundles.kotlinx.coroutines)
// AndroidX libraries
implementation(androidx.annotation)
implementation(androidx.appcompat)
implementation(androidx.biometricktx)
implementation(androidx.constraintlayout)
implementation(androidx.corektx)
implementation(androidx.splashscreen)
implementation(androidx.recyclerview)
implementation(androidx.viewpager)
implementation(androidx.profileinstaller)
implementation(libs.androidx.annotation)
implementation(libs.androidx.appCompat)
implementation(libs.androidx.biometric)
implementation(libs.androidx.constraintLayout)
implementation(libs.androidx.core)
implementation(libs.androidx.coreSplashScreen)
implementation(libs.androidx.recyclerView)
implementation(libs.androidx.viewPager)
implementation(libs.androidx.profileInstaller)
implementation(androidx.bundles.lifecycle)
implementation(libs.bundles.androidx.lifecycle)
// Job scheduling
implementation(androidx.workmanager)
implementation(libs.androidx.work)
// RxJava
implementation(libs.rxjava)
implementation(libs.rxJava)
// Networking
implementation(libs.bundles.okhttp)
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)
implementation(kotlinx.bundles.serialization)
implementation(libs.bundles.serialization)
// HTML parser
implementation(libs.jsoup)
// Disk
implementation(libs.disklrucache)
implementation(libs.diskLruCache)
implementation(libs.unifile)
// Preferences
implementation(libs.preferencektx)
implementation(libs.androidx.preference)
// Dependency injection
implementation(libs.injekt)
// Image loading
implementation(platform(libs.coil.bom))
implementation(libs.bundles.coil)
implementation(libs.subsamplingscaleimageview) {
implementation(libs.subsamplingScaleImageView) {
exclude(module = "image-decoder")
}
implementation(libs.image.decoder)
// UI libraries
implementation(libs.material)
implementation(libs.flexible.adapter.core)
implementation(libs.photoview)
implementation(libs.directionalviewpager) {
implementation(libs.flexibleAdapter)
implementation(libs.photoView)
implementation(libs.directionalViewPager) {
exclude(group = "androidx.viewpager", module = "viewpager")
}
implementation(libs.richeditor.compose)
implementation(libs.composeRichEditor)
implementation(libs.aboutLibraries.compose)
implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion)
implementation(libs.composeMaterialMotion)
implementation(libs.swipe)
implementation(libs.compose.webview)
implementation(libs.compose.grid)
implementation(libs.composeWebview)
implementation(libs.composeGrid)
implementation(libs.reorderable)
implementation(libs.bundles.markdown)
implementation(libs.materialKolor)
@@ -288,13 +286,26 @@ dependencies {
testRuntimeOnly(libs.junit.platform.launcher)
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android)
implementation(libs.leakcanary.plumber)
// debugImplementation(libs.leakCanary.android)
implementation(libs.leakCanary.plumber)
testImplementation(kotlinx.coroutines.test)
testImplementation(libs.kotlinx.coroutines.test)
}
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")) {
// Only excluding in standard flavor because this breaks
// Layout Inspector's Compose tree
@@ -304,6 +315,6 @@ androidComponents {
buildscript {
dependencies {
classpath(kotlinx.gradle)
classpath(libs.kotlin.gradle)
}
}
@@ -9,19 +9,22 @@ import tachiyomi.i18n.MR
class BasePreferences(
val context: Context,
private val preferenceStore: PreferenceStore,
preferenceStore: PreferenceStore,
) {
fun downloadedOnly() = preferenceStore.getBoolean(
val downloadedOnly: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("pref_downloaded_only"),
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) {
LEGACY(MR.strings.ext_installer_legacy, true),
@@ -30,9 +33,22 @@ class BasePreferences(
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"), "")
val donationCampaignShown: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("donation_campaign_shown"),
false,
)
}
@@ -46,7 +46,7 @@ class SetReadStatus(
return@withNonCancellableContext Result.InternalError(e)
}
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
if (read && downloadPreferences.removeAfterMarkedAsRead.get()) {
chaptersToUpdate
.groupBy { it.mangaId }
.forEach { (mangaId, chapters) ->
@@ -171,7 +171,7 @@ class SyncChaptersWithSource(
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { 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)
// 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>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
extensionManager.availableExtensionsFlow,
) { enabledLanguage, availableExtensions ->
availableExtensions
@@ -15,7 +15,7 @@ class GetExtensionSources(
val isMultiLangSingleSource =
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
extension.sources
@@ -13,10 +13,10 @@ class GetExtensionsByType(
) {
fun subscribe(): Flow<Extensions> {
val showNsfwSources = preferences.showNsfwSource().get()
val showNsfwSources = preferences.showNsfwSource.get()
return combine(
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow,
@@ -14,11 +14,11 @@ class TrustExtension(
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
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) {
preferences.trustedExtensions().getAndSet { exts ->
preferences.trustedExtensions.getAndSet { exts ->
// Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
@@ -27,6 +27,6 @@ class TrustExtension(
}
fun revokeAll() {
preferences.trustedExtensions().delete()
preferences.trustedExtensions.delete()
}
}
@@ -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
val title =
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles().get())) {
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles.get())) {
remoteTitle
} else {
null
@@ -22,7 +22,7 @@ val Manga.readerOrientation: Long
val Manga.downloadedFilter: TriState
get() {
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS
if (Injekt.get<BasePreferences>().downloadedOnly.get()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
@@ -17,10 +17,10 @@ class GetEnabledSources(
fun subscribe(): Flow<List<Source>> {
return combine(
preferences.pinnedSources().changes(),
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.lastUsedSource().changes(),
preferences.pinnedSources.changes(),
preferences.enabledLanguages.changes(),
preferences.disabledSources.changes(),
preferences.lastUsedSource.changes(),
repository.getSources(),
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
sources
@@ -13,19 +13,19 @@ class GetIncognitoState(
private val extensionManager: ExtensionManager,
) {
fun await(sourceId: Long?): Boolean {
if (basePreferences.incognitoMode().get()) return true
if (basePreferences.incognitoMode.get()) return true
if (sourceId == null) 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> {
if (sourceId == null) return basePreferences.incognitoMode().changes()
if (sourceId == null) return basePreferences.incognitoMode.changes()
return combine(
basePreferences.incognitoMode().changes(),
sourcePreferences.incognitoExtensions().changes(),
basePreferences.incognitoMode.changes(),
sourcePreferences.incognitoExtensions.changes(),
extensionManager.getExtensionPackageAsFlow(sourceId),
) { incognito, incognitoExtensions, extensionPackage ->
incognito || (extensionPackage in incognitoExtensions)
@@ -15,8 +15,8 @@ class GetLanguagesWithSources(
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.enabledLanguages.changes(),
preferences.disabledSources.changes(),
repository.getOnlineSources(),
) { enabledLanguage, disabledSource, onlineSources ->
val sortedSources = onlineSources.sortedWith(
@@ -16,8 +16,8 @@ class GetSourcesWithFavoriteCount(
fun subscribe(): Flow<List<Pair<Source, Long>>> {
return combine(
preferences.migrationSortingDirection().changes(),
preferences.migrationSortingMode().changes(),
preferences.migrationSortingDirection.changes(),
preferences.migrationSortingMode.changes(),
repository.getSourcesWithFavoriteCount(),
) { direction, mode, list ->
list
@@ -7,8 +7,8 @@ class SetMigrateSorting(
) {
fun await(mode: Mode, direction: Direction) {
preferences.migrationSortingMode().set(mode)
preferences.migrationSortingDirection().set(direction)
preferences.migrationSortingMode.set(mode)
preferences.migrationSortingDirection.set(direction)
}
enum class Mode {
@@ -7,7 +7,7 @@ class ToggleIncognito(
private val preferences: SourcePreferences,
) {
fun await(extensions: String, enable: Boolean) {
preferences.incognitoExtensions().getAndSet {
preferences.incognitoExtensions.getAndSet {
if (enable) it.plus(extensions) else it.minus(extensions)
}
}
@@ -8,8 +8,8 @@ class ToggleLanguage(
) {
fun await(language: String) {
val isEnabled = language in preferences.enabledLanguages().get()
preferences.enabledLanguages().getAndSet { enabled ->
val isEnabled = language in preferences.enabledLanguages.get()
preferences.enabledLanguages.getAndSet { enabled ->
if (isEnabled) enabled.minus(language) else enabled.plus(language)
}
}
@@ -13,19 +13,19 @@ class ToggleSource(
}
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")
}
}
fun await(sourceIds: List<Long>, enable: Boolean) {
val transformedSourceIds = sourceIds.map { it.toString() }
preferences.disabledSources().getAndSet { disabled ->
preferences.disabledSources.getAndSet { disabled ->
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
}
}
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) {
val isPinned = source.id.toString() in preferences.pinnedSources().get()
preferences.pinnedSources().getAndSet { pinned ->
val isPinned = source.id.toString() in preferences.pinnedSources.get()
preferences.pinnedSources.getAndSet { pinned ->
if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
}
}
@@ -10,68 +10,80 @@ import tachiyomi.core.common.preference.getLongArray
import tachiyomi.domain.library.model.LibraryDisplayMode
class SourcePreferences(
private val preferenceStore: PreferenceStore,
preferenceStore: PreferenceStore,
) {
fun sourceDisplayMode() = preferenceStore.getObjectFromString(
val sourceDisplayMode: Preference<LibraryDisplayMode> = preferenceStore.getObjectFromString(
"pref_display_mode_catalogue",
LibraryDisplayMode.default,
LibraryDisplayMode.Serializer::serialize,
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"),
-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",
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"),
emptySet(),
)
fun globalSearchFilterState() = preferenceStore.getBoolean(
val globalSearchFilterState: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("has_filters_toggle_state"),
false,
)
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",
defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) },
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,
)
}
@@ -34,11 +34,11 @@ class TrackPreferences(
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",
AutoTrackState.ALWAYS,
)
@@ -5,6 +5,7 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
import java.time.format.DateTimeFormatter
@@ -12,12 +13,12 @@ import java.time.format.FormatStyle
import java.util.Locale
class UiPreferences(
private val preferenceStore: PreferenceStore,
preferenceStore: PreferenceStore,
) {
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
val themeMode: Preference<ThemeMode> = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
fun appTheme() = preferenceStore.getEnum(
val appTheme: Preference<AppTheme> = preferenceStore.getEnum(
"pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET
@@ -26,15 +27,15 @@ 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)
companion object {
fun dateFormat(format: String): DateTimeFormatter = when (format) {
@@ -28,7 +28,7 @@ fun NavigatorAdaptiveSheet(
content = { sheetNavigator ->
AdaptiveSheet(
onDismissRequest = onDismissRequest,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
enableImplicitDismiss = enableSwipeDismiss(sheetNavigator),
) {
ScreenTransition(
navigator = sheetNavigator,
@@ -63,7 +63,7 @@ fun NavigatorAdaptiveSheet(
fun AdaptiveSheet(
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
enableSwipeDismiss: Boolean = true,
enableImplicitDismiss: Boolean = true,
content: @Composable () -> Unit,
) {
val isTabletUi = isTabletUi()
@@ -74,7 +74,7 @@ fun AdaptiveSheet(
) {
AdaptiveSheetImpl(
isTabletUi = isTabletUi,
enableSwipeDismiss = enableSwipeDismiss,
enableImplicitDismiss = enableImplicitDismiss,
onDismissRequest = onDismissRequest,
modifier = modifier,
) {
@@ -33,8 +33,8 @@ fun relativeDateText(
val context = LocalContext.current
val preferences = remember { Injekt.get<UiPreferences>() }
val relativeTime = remember { preferences.relativeTime().get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
val relativeTime = remember { preferences.relativeTime.get() }
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat.get()) }
return localDate?.toRelativeString(
context = context,
@@ -78,9 +78,9 @@ fun LibrarySettingsDialog(
private fun ColumnScope.FilterPage(
screenModel: LibrarySettingsScreenModel,
) {
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_downloaded),
@@ -92,25 +92,25 @@ private fun ColumnScope.FilterPage(
enabled = !downloadedOnly,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
)
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_unread),
state = filterUnread,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
)
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_started),
state = filterStarted,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
)
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
)
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.completed),
state = filterCompleted,
@@ -118,7 +118,7 @@ private fun ColumnScope.FilterPage(
)
// TODO: re-enable when custom intervals are ready for stable
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState()
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_interval_custom),
state = filterIntervalCustom,
@@ -229,7 +229,7 @@ private val displayModes = listOf(
private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel,
) {
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
SettingsChipRow(MR.strings.action_display_mode) {
displayModes.map { (titleRes, mode) ->
FilterChip(
@@ -244,9 +244,9 @@ private fun ColumnScope.DisplayPage(
val configuration = LocalConfiguration.current
val columnPreference = remember {
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenModel.libraryPreferences.landscapeColumns()
screenModel.libraryPreferences.landscapeColumns
} else {
screenModel.libraryPreferences.portraitColumns()
screenModel.libraryPreferences.portraitColumns
}
}
@@ -268,32 +268,32 @@ private fun ColumnScope.DisplayPage(
HeadingItem(MR.strings.overlay_header)
CheckboxItem(
label = stringResource(MR.strings.action_display_download_badge),
pref = screenModel.libraryPreferences.downloadBadge(),
pref = screenModel.libraryPreferences.downloadBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_unread_badge),
pref = screenModel.libraryPreferences.unreadBadge(),
pref = screenModel.libraryPreferences.unreadBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_local_badge),
pref = screenModel.libraryPreferences.localBadge(),
pref = screenModel.libraryPreferences.localBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_language_badge),
pref = screenModel.libraryPreferences.languageBadge(),
pref = screenModel.libraryPreferences.languageBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_show_continue_reading_button),
pref = screenModel.libraryPreferences.showContinueReadingButton(),
pref = screenModel.libraryPreferences.showContinueReadingButton,
)
HeadingItem(MR.strings.tabs_header)
CheckboxItem(
label = stringResource(MR.strings.action_display_show_tabs),
pref = screenModel.libraryPreferences.categoryTabs(),
pref = screenModel.libraryPreferences.categoryTabs,
)
CheckboxItem(
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(
onDismissRequest = onDismissRequest,
@@ -618,7 +618,7 @@ private fun MangaSummary(
modifier: Modifier = Modifier,
) {
val preferences = remember { Injekt.get<UiPreferences>() }
val loadImages = remember { preferences.imagesInDescription().get() }
val loadImages = remember { preferences.imagesInDescription.get() }
val animProgress by animateFloatAsState(
targetValue = if (expanded) 1f else 0f,
label = "summary",
@@ -1,6 +1,7 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -15,7 +16,9 @@ import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R
@Composable
fun LogoHeader() {
fun LogoHeader(
iconPadding: PaddingValues = PaddingValues(),
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
@@ -25,7 +28,7 @@ fun LogoHeader() {
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface,
modifier = Modifier
.padding(vertical = 56.dp)
.padding(iconPadding)
.size(64.dp),
)
@@ -1,10 +1,10 @@
package eu.kanade.presentation.more
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.filled.VolunteerActivism
import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.Info
@@ -13,10 +13,10 @@ import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Storage
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.tachiyomi.R
@@ -40,16 +40,17 @@ fun MoreScreen(
onClickStats: () -> Unit,
onClickDataAndStorage: () -> Unit,
onClickSettings: () -> Unit,
onClickSupport: () -> Unit,
onClickAbout: () -> Unit,
) {
val uriHandler = LocalUriHandler.current
Scaffold { contentPadding ->
ScrollbarLazyColumn(
modifier = Modifier.padding(contentPadding),
) {
ScrollbarLazyColumn(contentPadding = contentPadding) {
item {
LogoHeader()
LogoHeader(
iconPadding = PaddingValues(vertical = 32.dp),
)
}
item {
SwitchPreferenceWidget(
@@ -132,6 +133,13 @@ fun MoreScreen(
onPreferenceClick = onClickSettings,
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.label_support_us),
icon = Icons.Default.VolunteerActivism,
onPreferenceClick = onClickSupport,
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.pref_category_about),
@@ -146,13 +154,6 @@ fun MoreScreen(
onPreferenceClick = { uriHandler.openUri(Constants.URL_HELP) },
)
}
item {
TextPreferenceWidget(
title = stringResource(MR.strings.label_donate),
icon = Icons.Outlined.AttachMoney,
onPreferenceClick = { uriHandler.openUri(Constants.URL_DONATE) },
)
}
}
}
}
@@ -125,7 +125,7 @@ internal class PermissionStep : OnboardingStep {
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
val crashlyticsPref = privacyPreferences.crashlytics()
val crashlyticsPref = privacyPreferences.crashlytics
val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
@@ -134,7 +134,7 @@ internal class PermissionStep : OnboardingStep {
onToggleChange = crashlyticsPref::set,
)
val analyticsPref = privacyPreferences.analytics()
val analyticsPref = privacyPreferences.analytics
val analytics by analyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
@@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
internal class StorageStep : OnboardingStep {
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory
private var _isComplete by mutableStateOf(false)
@@ -19,13 +19,13 @@ internal class ThemeStep : OnboardingStep {
@Composable
override fun Content() {
val themeModePref = uiPreferences.themeMode()
val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState()
Column {
@@ -27,6 +27,7 @@ sealed class Preference {
override val title: String,
override val subtitle: String? = null,
override val enabled: Boolean = true,
val widget: @Composable (() -> Unit)? = null,
val onClick: (() -> Unit)? = null,
) : PreferenceItem<String, Unit>() {
override val icon: ImageVector? = null
@@ -147,6 +147,7 @@ internal fun PreferenceItem(
title = item.title,
subtitle = item.subtitle,
icon = item.icon,
widget = item.widget,
onPreferenceClick = item.onClick,
)
}
@@ -99,7 +99,7 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = networkPreferences.verboseLogging(),
preference = networkPreferences.verboseLogging,
title = stringResource(MR.strings.pref_verbose_logging),
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
onValueChanged = {
@@ -204,7 +204,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val context = LocalContext.current
val networkHelper = remember { Injekt.get<NetworkHelper>() }
val userAgentPref = networkPreferences.defaultUserAgent()
val userAgentPref = networkPreferences.defaultUserAgent
val userAgent by userAgentPref.collectAsState()
return Preference.PreferenceGroup(
@@ -238,7 +238,7 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = networkPreferences.dohProvider(),
preference = networkPreferences.dohProvider,
entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled),
PREF_DOH_CLOUDFLARE to "Cloudflare",
@@ -319,12 +319,12 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.updateMangaTitles(),
preference = libraryPreferences.updateMangaTitles,
title = stringResource(MR.strings.pref_update_library_manga_titles),
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.disallowNonAsciiFilenames(),
preference = libraryPreferences.disallowNonAsciiFilenames,
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
),
@@ -343,14 +343,14 @@ object SettingsAdvancedScreen : SearchableSettings {
uri?.let {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
basePreferences.displayProfile().set(uri.toString())
basePreferences.displayProfile.set(uri.toString())
}
}
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = basePreferences.hardwareBitmapThreshold(),
preference = basePreferences.hardwareBitmapThreshold,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option ->
val display = if (index == 0) {
@@ -370,13 +370,13 @@ object SettingsAdvancedScreen : SearchableSettings {
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
),
Preference.PreferenceItem.SwitchPreference(
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
preference = basePreferences.alwaysDecodeLongStripWithSSIV,
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(),
subtitle = basePreferences.displayProfile.get(),
onClick = {
chooseColorProfile.launch(arrayOf("*/*"))
},
@@ -391,7 +391,7 @@ object SettingsAdvancedScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val extensionInstallerPref = basePreferences.extensionInstaller()
val extensionInstallerPref = basePreferences.extensionInstaller
var shizukuMissing by rememberSaveable { mutableStateOf(false) }
val trustExtension = remember { Injekt.get<TrustExtension>() }
@@ -50,13 +50,13 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val themeModePref = uiPreferences.themeMode()
val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState()
return Preference.PreferenceGroup(
@@ -103,7 +103,7 @@ object SettingsAppearanceScreen : SearchableSettings {
val now = remember { LocalDate.now() }
val dateFormat by uiPreferences.dateFormat().collectAsState()
val dateFormat by uiPreferences.dateFormat.collectAsState()
val formattedNow = remember(dateFormat) {
UiPreferences.dateFormat(dateFormat).format(now)
}
@@ -116,7 +116,7 @@ object SettingsAppearanceScreen : SearchableSettings {
onClick = { navigator.push(AppLanguageScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.tabletUiMode(),
preference = uiPreferences.tabletUiMode,
entries = TabletUiMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
@@ -127,7 +127,7 @@ object SettingsAppearanceScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.dateFormat(),
preference = uiPreferences.dateFormat,
entries = DateFormats
.associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now)
@@ -137,7 +137,7 @@ object SettingsAppearanceScreen : SearchableSettings {
title = stringResource(MR.strings.pref_date_format),
),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.relativeTime(),
preference = uiPreferences.relativeTime,
title = stringResource(MR.strings.pref_relative_format),
subtitle = stringResource(
MR.strings.pref_relative_format_summary,
@@ -146,7 +146,7 @@ object SettingsAppearanceScreen : SearchableSettings {
),
),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.imagesInDescription(),
preference = uiPreferences.imagesInDescription,
title = stringResource(MR.strings.pref_display_images_description),
),
),
@@ -43,7 +43,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.label_sources),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.hideInLibraryItems(),
preference = sourcePreferences.hideInLibraryItems,
title = stringResource(MR.strings.pref_hide_in_library_items),
),
Preference.PreferenceItem.TextPreference(
@@ -59,7 +59,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_nsfw_content),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.showNsfwSource(),
preference = sourcePreferences.showNsfwSource,
title = stringResource(MR.strings.pref_show_nsfw_source),
subtitle = stringResource(MR.strings.requires_app_restart),
onValueChanged = {
@@ -167,11 +167,11 @@ object SettingsDataScreen : SearchableSettings {
storagePreferences: StoragePreferences,
): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory)
return Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_storage_location),
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
subtitle = storageLocationText(storagePreferences.baseStorageDirectory),
onClick = {
try {
pickStorageLocation.launch(null)
@@ -187,7 +187,7 @@ object SettingsDataScreen : SearchableSettings {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp.collectAsState()
val chooseBackup = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() {
@@ -254,7 +254,7 @@ object SettingsDataScreen : SearchableSettings {
// Automatic backups
Preference.PreferenceItem.ListPreference(
preference = backupPreferences.backupInterval(),
preference = backupPreferences.backupInterval,
entries = persistentMapOf(
0 to stringResource(MR.strings.off),
6 to stringResource(MR.strings.update_6hour),
@@ -321,7 +321,7 @@ object SettingsDataScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoClearChapterCache(),
preference = libraryPreferences.autoClearChapterCache,
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
),
),
@@ -37,19 +37,19 @@ object SettingsDownloadScreen : SearchableSettings {
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState()
val parallelSourceLimit by downloadPreferences.parallelSourceLimit.collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit.collectAsState()
return listOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.downloadOnlyOverWifi(),
preference = downloadPreferences.downloadOnlyOverWifi,
title = stringResource(MR.strings.connected_to_wifi),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.saveChaptersAsCBZ(),
preference = downloadPreferences.saveChaptersAsCBZ,
title = stringResource(MR.strings.save_chapter_as_cbz),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.splitTallImages(),
preference = downloadPreferences.splitTallImages,
title = stringResource(MR.strings.split_tall_images),
subtitle = stringResource(MR.strings.split_tall_images_summary),
),
@@ -57,14 +57,14 @@ object SettingsDownloadScreen : SearchableSettings {
value = parallelSourceLimit,
valueRange = 1..10,
title = stringResource(MR.strings.pref_download_concurrent_sources),
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) },
onValueChanged = { downloadPreferences.parallelSourceLimit.set(it) },
),
Preference.PreferenceItem.SliderPreference(
value = parallelPageLimit,
valueRange = 1..15,
title = stringResource(MR.strings.pref_download_concurrent_pages),
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) },
onValueChanged = { downloadPreferences.parallelPageLimit.set(it) },
),
getDeleteChaptersGroup(
downloadPreferences = downloadPreferences,
@@ -87,11 +87,11 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_delete_chapters),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeAfterMarkedAsRead(),
preference = downloadPreferences.removeAfterMarkedAsRead,
title = stringResource(MR.strings.pref_remove_after_marked_as_read),
),
Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.removeAfterReadSlots(),
preference = downloadPreferences.removeAfterReadSlots,
entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled),
0 to stringResource(MR.strings.last_read_chapter),
@@ -103,7 +103,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_remove_after_read),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeBookmarkedChapters(),
preference = downloadPreferences.removeBookmarkedChapters,
title = stringResource(MR.strings.pref_remove_bookmarked_chapters),
),
getExcludedCategoriesPreference(
@@ -120,7 +120,7 @@ object SettingsDownloadScreen : SearchableSettings {
categories: () -> List<Category>,
): Preference.PreferenceItem.MultiSelectListPreference {
return Preference.PreferenceItem.MultiSelectListPreference(
preference = downloadPreferences.removeExcludeCategories(),
preference = downloadPreferences.removeExcludeCategories,
entries = categories()
.associate { it.id.toString() to it.visualName }
.toImmutableMap(),
@@ -133,10 +133,10 @@ object SettingsDownloadScreen : SearchableSettings {
downloadPreferences: DownloadPreferences,
allCategories: List<Category>,
): Preference.PreferenceGroup {
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude
val downloadNewChapters by downloadNewChaptersPref.collectAsState()
@@ -194,7 +194,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.download_ahead),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.autoDownloadWhileReading(),
preference = downloadPreferences.autoDownloadWhileReading,
entries = listOf(0, 2, 3, 5, 10)
.associateWith {
if (it == 0) {
@@ -73,7 +73,7 @@ object SettingsLibraryScreen : SearchableSettings {
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
// For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
val ids = listOf(libraryPreferences.defaultCategory.defaultValue()) +
allCategories.fastMap { it.id.toInt() }
val labels = listOf(stringResource(MR.strings.default_category_summary)) +
allCategories.fastMap { it.visualName }
@@ -91,12 +91,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { navigator.push(CategoryScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.defaultCategory(),
preference = libraryPreferences.defaultCategory,
entries = ids.zip(labels).toMap().toImmutableMap(),
title = stringResource(MR.strings.default_category),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.categorizedDisplaySettings(),
preference = libraryPreferences.categorizedDisplaySettings,
title = stringResource(MR.strings.categorized_display_settings),
onValueChanged = {
if (!it) {
@@ -118,9 +118,9 @@ object SettingsLibraryScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval()
val autoUpdateCategoriesPref = libraryPreferences.updateCategories()
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude()
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval
val autoUpdateCategoriesPref = libraryPreferences.updateCategories
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude
val autoUpdateInterval by autoUpdateIntervalPref.collectAsState()
@@ -164,7 +164,7 @@ object SettingsLibraryScreen : SearchableSettings {
},
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateDeviceRestrictions(),
preference = libraryPreferences.autoUpdateDeviceRestrictions,
entries = persistentMapOf(
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
@@ -189,12 +189,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { showCategoriesDialog = true },
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoUpdateMetadata(),
preference = libraryPreferences.autoUpdateMetadata,
title = stringResource(MR.strings.pref_library_update_refresh_metadata),
subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary),
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateMangaRestrictions(),
preference = libraryPreferences.autoUpdateMangaRestrictions,
entries = persistentMapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
@@ -204,7 +204,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_library_update_smart_update),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.newShowUpdatesCount(),
preference = libraryPreferences.newShowUpdatesCount,
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
),
),
@@ -219,7 +219,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_behavior),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToStartAction(),
preference = libraryPreferences.swipeToStartAction,
entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
@@ -233,7 +233,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_start),
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToEndAction(),
preference = libraryPreferences.swipeToEndAction,
entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
@@ -247,7 +247,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_end),
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.markDuplicateReadChapterAsRead(),
preference = libraryPreferences.markDuplicateReadChapterAsRead,
entries = persistentMapOf(
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
@@ -257,7 +257,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_mark_duplicate_read_chapter_read),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.hideMissingChapters(),
preference = libraryPreferences.hideMissingChapters,
title = stringResource(MR.strings.pref_hide_missing_chapter_indicators),
),
),
@@ -33,14 +33,14 @@ object SettingsReaderScreen : SearchableSettings {
return listOf(
Preference.PreferenceItem.ListPreference(
preference = readerPref.defaultReadingMode(),
preference = readerPref.defaultReadingMode,
entries = ReadingMode.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(),
title = stringResource(MR.strings.pref_viewer_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPref.doubleTapAnimSpeed(),
preference = readerPref.doubleTapAnimSpeed,
entries = persistentMapOf(
1 to stringResource(MR.strings.double_tap_anim_speed_0),
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
@@ -49,17 +49,17 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_double_tap_anim_speed),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showReadingMode(),
preference = readerPref.showReadingMode,
title = stringResource(MR.strings.pref_show_reading_mode),
subtitle = stringResource(MR.strings.pref_show_reading_mode_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showNavigationOverlayOnStart(),
preference = readerPref.showNavigationOverlayOnStart,
title = stringResource(MR.strings.pref_show_navigation_mode),
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.pageTransitions(),
preference = readerPref.pageTransitions,
title = stringResource(MR.strings.pref_page_transitions),
),
getDisplayGroup(readerPreferences = readerPref),
@@ -74,20 +74,20 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreenPref = readerPreferences.fullscreen()
val fullscreenPref = readerPreferences.fullscreen
val fullscreen by fullscreenPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.defaultOrientationType(),
preference = readerPreferences.defaultOrientationType,
entries = ReaderOrientation.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(),
title = stringResource(MR.strings.pref_rotation_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerTheme(),
preference = readerPreferences.readerTheme,
entries = persistentMapOf(
1 to stringResource(MR.strings.black_background),
2 to stringResource(MR.strings.gray_background),
@@ -101,16 +101,16 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.drawUnderCutout(),
preference = readerPreferences.drawUnderCutout,
title = stringResource(MR.strings.pref_cutout_short),
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.keepScreenOn(),
preference = readerPreferences.keepScreenOn,
title = stringResource(MR.strings.pref_keep_screen_on),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.showPageNumber(),
preference = readerPreferences.showPageNumber,
title = stringResource(MR.strings.pref_show_page_number),
),
),
@@ -119,21 +119,21 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
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 flashIntervalPref = readerPreferences.flashPageInterval()
val flashIntervalPref = readerPreferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor()
val flashColorPref = readerPreferences.flashColor
return Preference.PreferenceGroup(
title = "E-Ink",
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.flashOnPageChange(),
preference = readerPreferences.flashOnPageChange,
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
@@ -174,19 +174,19 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_reading),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipRead(),
preference = readerPreferences.skipRead,
title = stringResource(MR.strings.pref_skip_read_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipFiltered(),
preference = readerPreferences.skipFiltered,
title = stringResource(MR.strings.pref_skip_filtered_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipDupe(),
preference = readerPreferences.skipDupe,
title = stringResource(MR.strings.pref_skip_dupe_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.alwaysShowChapterTransition(),
preference = readerPreferences.alwaysShowChapterTransition,
title = stringResource(MR.strings.pref_always_show_chapter_transition),
),
),
@@ -195,10 +195,10 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getPagedGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val navModePref = readerPreferences.navigationModePager()
val imageScaleTypePref = readerPreferences.imageScaleType()
val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
val rotateToFitPref = readerPreferences.dualPageRotateToFit()
val navModePref = readerPreferences.navigationModePager
val imageScaleTypePref = readerPreferences.imageScaleType
val dualPageSplitPref = readerPreferences.dualPageSplitPaged
val rotateToFitPref = readerPreferences.dualPageRotateToFit
val navMode by navModePref.collectAsState()
val imageScaleType by imageScaleTypePref.collectAsState()
@@ -217,7 +217,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.pagerNavInverted(),
preference = readerPreferences.pagerNavInverted,
entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -238,7 +238,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_image_scale_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.zoomStart(),
preference = readerPreferences.zoomStart,
entries = ReaderPreferences.ZoomStart
.mapIndexed { index, it -> index + 1 to stringResource(it) }
.toMap()
@@ -246,16 +246,16 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_zoom_start),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBorders(),
preference = readerPreferences.cropBorders,
title = stringResource(MR.strings.pref_crop_borders),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.landscapeZoom(),
preference = readerPreferences.landscapeZoom,
title = stringResource(MR.strings.pref_landscape_zoom),
enabled = imageScaleType == 1,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.navigateToPan(),
preference = readerPreferences.navigateToPan,
title = stringResource(MR.strings.pref_navigate_pan),
enabled = navMode != 5,
),
@@ -268,7 +268,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertPaged(),
preference = readerPreferences.dualPageInvertPaged,
title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit,
@@ -282,7 +282,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvert(),
preference = readerPreferences.dualPageRotateToFitInvert,
title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit,
),
@@ -294,10 +294,10 @@ object SettingsReaderScreen : SearchableSettings {
private fun getWebtoonGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val numberFormat = remember { NumberFormat.getPercentInstance() }
val navModePref = readerPreferences.navigationModeWebtoon()
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon()
val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon()
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding()
val navModePref = readerPreferences.navigationModeWebtoon
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon
val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding
val navMode by navModePref.collectAsState()
val dualPageSplit by dualPageSplitPref.collectAsState()
@@ -316,7 +316,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.webtoonNavInverted(),
preference = readerPreferences.webtoonNavInverted,
entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -338,7 +338,7 @@ object SettingsReaderScreen : SearchableSettings {
onValueChanged = { webtoonSidePaddingPref.set(it) },
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(),
preference = readerPreferences.readerHideThreshold,
entries = persistentMapOf(
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
@@ -348,7 +348,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_hide_threshold),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBordersWebtoon(),
preference = readerPreferences.cropBordersWebtoon,
title = stringResource(MR.strings.pref_crop_borders),
),
Preference.PreferenceItem.SwitchPreference(
@@ -360,7 +360,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertWebtoon(),
preference = readerPreferences.dualPageInvertWebtoon,
title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit,
@@ -374,16 +374,16 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvertWebtoon(),
preference = readerPreferences.dualPageRotateToFitInvertWebtoon,
title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDoubleTapZoomEnabled(),
preference = readerPreferences.webtoonDoubleTapZoomEnabled,
title = stringResource(MR.strings.pref_double_tap_zoom),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDisableZoomOut(),
preference = readerPreferences.webtoonDisableZoomOut,
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
),
),
@@ -392,7 +392,7 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys()
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys
val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_reader_navigation),
@@ -402,7 +402,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_read_with_volume_keys),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithVolumeKeysInverted(),
preference = readerPreferences.readWithVolumeKeysInverted,
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
enabled = readWithVolumeKeys,
),
@@ -416,11 +416,11 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_reader_actions),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithLongTap(),
preference = readerPreferences.readWithLongTap,
title = stringResource(MR.strings.pref_read_with_long_tap),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.folderPerManga(),
preference = readerPreferences.folderPerManga,
title = stringResource(MR.strings.pref_create_folder_per_manga),
subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary),
),
@@ -45,7 +45,7 @@ object SettingsSecurityScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuthPref = securityPreferences.useAuthenticator
val useAuth by useAuthPref.collectAsState()
return Preference.PreferenceGroup(
@@ -62,7 +62,7 @@ object SettingsSecurityScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = securityPreferences.lockAppAfter(),
preference = securityPreferences.lockAppAfter,
entries = LockAfterValues
.associateWith {
when (it) {
@@ -82,11 +82,11 @@ object SettingsSecurityScreen : SearchableSettings {
),
Preference.PreferenceItem.SwitchPreference(
preference = securityPreferences.hideNotificationContent(),
preference = securityPreferences.hideNotificationContent,
title = stringResource(MR.strings.hide_notification_content),
),
Preference.PreferenceItem.ListPreference(
preference = securityPreferences.secureScreen(),
preference = securityPreferences.secureScreen,
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
@@ -105,12 +105,12 @@ object SettingsSecurityScreen : SearchableSettings {
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.crashlytics(),
preference = privacyPreferences.crashlytics,
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
),
Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.analytics(),
preference = privacyPreferences.analytics,
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
),
@@ -90,7 +90,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@@ -128,11 +128,11 @@ object SettingsTrackingScreen : SearchableSettings {
return listOf(
Preference.PreferenceItem.SwitchPreference(
preference = trackPreferences.autoUpdateTrack(),
preference = trackPreferences.autoUpdateTrack,
title = stringResource(MR.strings.pref_auto_update_manga_sync),
),
Preference.PreferenceItem.ListPreference(
preference = trackPreferences.autoUpdateTrackOnMarkRead(),
preference = trackPreferences.autoUpdateTrackOnMarkRead,
entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
@@ -3,6 +3,7 @@ package eu.kanade.presentation.more.settings.screen.about
import android.content.Context
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.updaterEnabled
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
@@ -85,7 +87,9 @@ object AboutScreen : Screen() {
contentPadding = contentPadding,
) {
item {
LogoHeader()
LogoHeader(
iconPadding = PaddingValues(vertical = 56.dp),
)
}
item {
@@ -176,7 +180,7 @@ object AboutScreen : Screen() {
LinkIcon(
label = "Discord",
icon = CustomIcons.Discord,
url = "https://discord.gg/mihon",
url = Constants.URL_DISCORD,
)
LinkIcon(
label = "X",
@@ -275,7 +279,7 @@ object AboutScreen : Screen() {
)
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
Injekt.get<UiPreferences>().dateFormat.get(),
),
)
} catch (e: Exception) {
@@ -1,24 +1,38 @@
package eu.kanade.presentation.more.settings.screen.debug
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.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.profileinstaller.ProfileVerifier
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import mihon.core.common.FeatureFlags
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DebugInfoScreen : Screen() {
@@ -47,6 +61,12 @@ class DebugInfoScreen : Screen() {
@Composable
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(
title = "App info",
preferenceItems = persistentListOf(
@@ -58,6 +78,28 @@ class DebugInfoScreen : Screen() {
title = "Build time",
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(),
Preference.PreferenceItem.TextPreference(
title = "WebView version",
@@ -156,7 +156,7 @@ class WorkerInfoScreen : Screen() {
)
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
Injekt.get<UiPreferences>().dateFormat.get(),
),
)
appendLine("Next scheduled run: $timestamp")
@@ -24,10 +24,10 @@ class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis()
internal val flashMode = readerPreferences.flashColor()
internal val flashMillis = readerPreferences.flashDurationMillis
internal val flashMode = readerPreferences.flashColor
internal val flashIntervalPref = readerPreferences.flashPageInterval()
internal val flashIntervalPref = readerPreferences.flashPageInterval
// Internal State for Flash
private var flashInterval = flashIntervalPref.get()
@@ -22,10 +22,10 @@ import tachiyomi.presentation.core.util.collectAsState
@Composable
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
val customBrightness by screenModel.preferences.customBrightness.collectAsState()
CheckboxItem(
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.
*/
if (customBrightness) {
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
val customBrightnessValue by screenModel.preferences.customBrightnessValue.collectAsState()
SliderItem(
value = customBrightnessValue,
valueRange = -75..100,
steps = 0,
label = stringResource(MR.strings.pref_custom_brightness),
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
onChange = { screenModel.preferences.customBrightnessValue.set(it) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
}
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
val colorFilter by screenModel.preferences.colorFilter.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_custom_color_filter),
pref = screenModel.preferences.colorFilter(),
pref = screenModel.preferences.colorFilter,
)
if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
val colorFilterValue by screenModel.preferences.colorFilterValue.collectAsState()
SliderItem(
value = colorFilterValue.red,
valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_r_value),
onChange = { newRValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newRValue, RED_MASK, 16)
}
},
@@ -71,7 +71,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_g_value),
onChange = { newGValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newGValue, GREEN_MASK, 8)
}
},
@@ -83,7 +83,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_b_value),
onChange = { newBValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newBValue, BLUE_MASK, 0)
}
},
@@ -95,19 +95,19 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_a_value),
onChange = { newAValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newAValue, ALPHA_MASK, 24)
}
},
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) {
ColorFilterMode.mapIndexed { index, it ->
FilterChip(
selected = colorFilterMode == index,
onClick = { screenModel.preferences.colorFilterMode().set(index) },
onClick = { screenModel.preferences.colorFilterMode.set(index) },
label = { Text(stringResource(it.first)) },
)
}
@@ -116,11 +116,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
CheckboxItem(
label = stringResource(MR.strings.pref_grayscale),
pref = screenModel.preferences.grayscale(),
pref = screenModel.preferences.grayscale,
)
CheckboxItem(
label = stringResource(MR.strings.pref_inverted_colors),
pref = screenModel.preferences.invertedColors(),
pref = screenModel.preferences.invertedColors,
)
}
@@ -33,24 +33,24 @@ private val flashColors = listOf(
@Composable
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 flashIntervalPref = screenModel.preferences.flashPageInterval()
val flashIntervalPref = screenModel.preferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor()
val flashColorPref = screenModel.preferences.flashColor
val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) ->
FilterChip(
selected = readerTheme == value,
onClick = { screenModel.preferences.readerTheme().set(value) },
onClick = { screenModel.preferences.readerTheme.set(value) },
label = { Text(stringResource(labelRes)) },
)
}
@@ -58,45 +58,45 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
CheckboxItem(
label = stringResource(MR.strings.pref_show_page_number),
pref = screenModel.preferences.showPageNumber(),
pref = screenModel.preferences.showPageNumber,
)
CheckboxItem(
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) {
CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.drawUnderCutout(),
pref = screenModel.preferences.drawUnderCutout,
)
}
CheckboxItem(
label = stringResource(MR.strings.pref_keep_screen_on),
pref = screenModel.preferences.keepScreenOn(),
pref = screenModel.preferences.keepScreenOn,
)
CheckboxItem(
label = stringResource(MR.strings.pref_read_with_long_tap),
pref = screenModel.preferences.readWithLongTap(),
pref = screenModel.preferences.readWithLongTap,
)
CheckboxItem(
label = stringResource(MR.strings.pref_always_show_chapter_transition),
pref = screenModel.preferences.alwaysShowChapterTransition(),
pref = screenModel.preferences.alwaysShowChapterTransition,
)
CheckboxItem(
label = stringResource(MR.strings.pref_page_transitions),
pref = screenModel.preferences.pageTransitions(),
pref = screenModel.preferences.pageTransitions,
)
CheckboxItem(
label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(),
pref = screenModel.preferences.flashOnPageChange,
)
if (flashPageState) {
SliderItem(
@@ -63,32 +63,32 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
HeadingItem(MR.strings.pager_viewer)
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
val navigationModePager by screenModel.preferences.navigationModePager.collectAsState()
val pagerNavInverted by screenModel.preferences.pagerNavInverted.collectAsState()
TapZonesItems(
selected = navigationModePager,
onSelect = screenModel.preferences.navigationModePager()::set,
onSelect = screenModel.preferences.navigationModePager::set,
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) {
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
FilterChip(
selected = imageScaleType == index + 1,
onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
onClick = { screenModel.preferences.imageScaleType.set(index + 1) },
label = { Text(stringResource(it)) },
)
}
}
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
val zoomStart by screenModel.preferences.zoomStart.collectAsState()
SettingsChipRow(MR.strings.pref_zoom_start) {
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
FilterChip(
selected = zoomStart == index + 1,
onClick = { screenModel.preferences.zoomStart().set(index + 1) },
onClick = { screenModel.preferences.zoomStart.set(index + 1) },
label = { Text(stringResource(it)) },
)
}
@@ -96,42 +96,42 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBorders(),
pref = screenModel.preferences.cropBorders,
)
CheckboxItem(
label = stringResource(MR.strings.pref_landscape_zoom),
pref = screenModel.preferences.landscapeZoom(),
pref = screenModel.preferences.landscapeZoom,
)
CheckboxItem(
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(
label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitPaged(),
pref = screenModel.preferences.dualPageSplitPaged,
)
if (dualPageSplitPaged) {
CheckboxItem(
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(
label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFit(),
pref = screenModel.preferences.dualPageRotateToFit,
)
if (dualPageRotateToFit) {
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvert(),
pref = screenModel.preferences.dualPageRotateToFitInvert,
)
}
}
@@ -142,65 +142,65 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
HeadingItem(MR.strings.webtoon_viewer)
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon.collectAsState()
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted.collectAsState()
TapZonesItems(
selected = navigationModeWebtoon,
onSelect = screenModel.preferences.navigationModeWebtoon()::set,
onSelect = screenModel.preferences.navigationModeWebtoon::set,
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(
value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding),
valueString = numberFormat.format(webtoonSidePadding / 100f),
onChange = {
screenModel.preferences.webtoonSidePadding().set(it)
screenModel.preferences.webtoonSidePadding.set(it)
},
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBordersWebtoon(),
pref = screenModel.preferences.cropBordersWebtoon,
)
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitWebtoon(),
pref = screenModel.preferences.dualPageSplitWebtoon,
)
if (dualPageSplitWebtoon) {
CheckboxItem(
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(
label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFitWebtoon(),
pref = screenModel.preferences.dualPageRotateToFitWebtoon,
)
if (dualPageRotateToFitWebtoon) {
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon(),
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon,
)
}
CheckboxItem(
label = stringResource(MR.strings.pref_double_tap_zoom),
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled,
)
CheckboxItem(
label = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
pref = screenModel.preferences.webtoonDisableZoomOut(),
pref = screenModel.preferences.webtoonDisableZoomOut,
)
}
@@ -35,8 +35,8 @@ fun TachiyomiTheme(
) {
val uiPreferences = Injekt.get<UiPreferences>()
BaseTachiyomiTheme(
appTheme = appTheme ?: uiPreferences.appTheme().get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled().get(),
appTheme = appTheme ?: uiPreferences.appTheme.get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled.get(),
content = content,
)
}
@@ -55,28 +55,28 @@ fun UpdatesFilterDialog(
private fun ColumnScope.FilterSheet(
screenModel: UpdatesSettingsScreenModel,
) {
val filterDownloaded by screenModel.updatesPreferences.filterDownloaded().collectAsState()
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()
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()
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()
val filterBookmarked by screenModel.updatesPreferences.filterBookmarked.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked,
@@ -85,9 +85,9 @@ private fun ColumnScope.FilterSheet(
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators().collectAsState()
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators.collectAsState()
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators().getAndSet { !it }
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators.getAndSet { !it }
Row(
modifier = Modifier
@@ -3,6 +3,8 @@ package eu.kanade.presentation.webview
import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.os.Message
import android.webkit.JsPromptResult
import android.webkit.JsResult
import android.webkit.WebResourceRequest
import android.webkit.WebView
import androidx.activity.compose.BackHandler
@@ -20,6 +22,7 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
@@ -95,6 +98,11 @@ fun WebViewScreenContent(
var currentUrl by remember { mutableStateOf(url) }
var showCloudflareHelp by remember { mutableStateOf(false) }
var isActive by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
onDispose { isActive = false }
}
val webClient = remember {
object : AccompanistWebViewClient() {
@@ -163,6 +171,36 @@ fun WebViewScreenContent(
}
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)
}
}
}
+9 -9
View File
@@ -107,7 +107,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes()
basePreferences.incognitoMode.changes()
.onEach { enabled ->
if (enabled) {
disableIncognitoReceiver.register()
@@ -135,32 +135,32 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
}
.launchIn(scope)
privacyPreferences.analytics()
privacyPreferences.analytics
.changes()
.onEach(TelemetryConfig::setAnalyticsEnabled)
.launchIn(scope)
privacyPreferences.crashlytics()
privacyPreferences.crashlytics
.changes()
.onEach(TelemetryConfig::setCrashlyticsEnabled)
.launchIn(scope)
basePreferences.hardwareBitmapThreshold().let { preference ->
basePreferences.hardwareBitmapThreshold.let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
}
basePreferences.hardwareBitmapThreshold().changes()
basePreferences.hardwareBitmapThreshold.changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode.get())
// Updates widget update
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
if (!LogcatLogger.isInstalled) {
val minLogPriority = when {
networkPreferences.verboseLogging().get() -> LogPriority.VERBOSE
networkPreferences.verboseLogging.get() -> LogPriority.VERBOSE
BuildConfig.DEBUG -> LogPriority.DEBUG
else -> LogPriority.INFO
}
@@ -211,7 +211,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
crossfade((300 * this@App.animatorDurationScale).toInt())
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
fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
@@ -256,7 +256,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
private var registered = false
override fun onReceive(context: Context, intent: Intent) {
basePreferences.incognitoMode().set(false)
basePreferences.incognitoMode.set(false)
}
fun register() {
@@ -101,7 +101,7 @@ class BackupNotifier(private val context: Context) {
}
setContentTitle(contentTitle)
if (!preferences.hideNotificationContent().get()) {
if (!preferences.hideNotificationContent.get()) {
setContentText(content)
}
@@ -89,7 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
fun setupTask(context: Context, prefInterval: Int? = null) {
val backupPreferences = Injekt.get<BackupPreferences>()
val interval = prefInterval ?: backupPreferences.backupInterval().get()
val interval = prefInterval ?: backupPreferences.backupInterval.get()
if (interval > 0) {
val constraints = Constraints(
requiresBatteryNotLow = true,
@@ -108,7 +108,7 @@ class BackupCreator(
BackupFileValidator(context).validate(fileUri)
if (isAutoBackup) {
backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli())
backupPreferences.lastAutoBackupTimestamp.set(Instant.now().toEpochMilli())
}
return fileUri.toString()
@@ -32,7 +32,7 @@ class CategoriesRestorer(
.let { id -> it.toCategory(id).copy(order = order) }
}
libraryPreferences.categorizedDisplaySettings().set(
libraryPreferences.categorizedDisplaySettings.set(
(dbCategories + categories)
.distinctBy { it.flags }
.size > 1,
@@ -56,7 +56,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
override suspend fun doWork(): Result {
var networkCheck = checkNetworkState(
applicationContext.activeNetworkState(),
downloadPreferences.downloadOnlyOverWifi().get(),
downloadPreferences.downloadOnlyOverWifi.get(),
)
var active = networkCheck && downloadManager.downloaderStart()
@@ -69,7 +69,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
coroutineScope {
combineTransform(
applicationContext.networkStateFlow(),
downloadPreferences.downloadOnlyOverWifi().changes(),
downloadPreferences.downloadOnlyOverWifi.changes(),
transform = { a, b -> emit(checkNetworkState(a, b)) },
)
.onEach { networkCheck = it }
@@ -397,7 +397,7 @@ class DownloadManager(
private suspend fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
// 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)
.map { it.id }
@@ -408,7 +408,7 @@ class DownloadManager(
chapters
}
return if (!downloadPreferences.removeBookmarkedChapters().get()) {
return if (!downloadPreferences.removeBookmarkedChapters.get()) {
filteredCategoryManga.filterNot { it.bookmark }
} else {
filteredCategoryManga
@@ -96,7 +96,7 @@ internal class DownloadNotifier(private val context: Context) {
download.pages!!.size,
)
if (preferences.hideNotificationContent().get()) {
if (preferences.hideNotificationContent.get()) {
setContentTitle(downloadingProgressText)
setContentText(null)
} else {
@@ -136,7 +136,7 @@ class DownloadProvider(
fun getSourceDirName(source: Source): String {
return DiskUtil.buildValidFilename(
source.toString(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
)
}
@@ -148,7 +148,7 @@ class DownloadProvider(
fun getMangaDirName(mangaTitle: String): String {
return DiskUtil.buildValidFilename(
mangaTitle,
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
)
}
@@ -163,7 +163,7 @@ class DownloadProvider(
chapterName: String,
chapterScanlator: String?,
chapterUrl: String,
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames.get(),
): String {
var dirName = sanitizeChapterName(chapterName)
if (!chapterScanlator.isNullOrBlank()) {
@@ -206,7 +206,7 @@ class DownloadProvider(
chapterName,
chapterScanlator,
chapterUrl,
!libraryPreferences.disallowNonAsciiFilenames().get(),
!libraryPreferences.disallowNonAsciiFilenames.get(),
)
return buildList(2) {
@@ -193,7 +193,7 @@ class Downloader(
downloaderJob = scope.launch {
val activeDownloadsFlow = combine(
queueState,
downloadPreferences.parallelSourceLimit().changes(),
downloadPreferences.parallelSourceLimit.changes(),
) { a, b -> a to b }.transformLatest { (queue, parallelCount) ->
while (true) {
val activeDownloads = queue.asSequence()
@@ -369,7 +369,7 @@ class Downloader(
download.status = Download.State.DOWNLOADING
// 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 {
// Fetch image URL if necessary
if (page.imageUrl.isNullOrEmpty()) {
@@ -406,7 +406,7 @@ class Downloader(
)
// Only rename the directory if it's downloaded
if (downloadPreferences.saveChaptersAsCBZ().get()) {
if (downloadPreferences.saveChaptersAsCBZ.get()) {
archiveChapter(mangaDir, chapterDirname, tmpDir)
} else {
tmpDir.renameTo(chapterDirname)
@@ -545,7 +545,7 @@ class Downloader(
}
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
if (!downloadPreferences.splitTallImages().get()) return
if (!downloadPreferences.splitTallImages.get()) return
try {
val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number)
@@ -99,7 +99,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (tags.contains(WORK_NAME_AUTO)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
@@ -113,7 +113,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely()
libraryPreferences.lastUpdatedTimestamp().set(Instant.now().toEpochMilli())
libraryPreferences.lastUpdatedTimestamp.set(Instant.now().toEpochMilli())
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
addMangaToQueue(categoryId)
@@ -160,8 +160,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { categoryId in it.categories }
} else {
val includedCategories = libraryPreferences.updateCategories().get().map { it.toLong() }
val excludedCategories = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
val includedCategories = libraryPreferences.updateCategories.get().map { it.toLong() }
val excludedCategories = libraryPreferences.updateCategoriesExclude.get().map { it.toLong() }
libraryManga.filter {
val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
@@ -170,7 +170,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
}
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val restrictions = libraryPreferences.autoUpdateMangaRestrictions.get()
val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
val (_, fetchWindowUpperBound) = fetchInterval.getWindow(ZonedDateTime.now())
@@ -272,7 +272,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
hasDownloads.store(true)
}
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
libraryPreferences.newUpdatesCount.getAndSet { it + newChapters.size }
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.toTypedArray())
@@ -332,7 +332,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val source = sourceManager.getOrStub(manga.source)
// Update manga metadata if needed
if (libraryPreferences.autoUpdateMetadata().get()) {
if (libraryPreferences.autoUpdateMetadata.get()) {
val networkManga = source.getMangaDetails(manga.toSManga())
updateManga.awaitUpdateFromSource(manga, networkManga, manualFetch = false, coverCache)
}
@@ -423,9 +423,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
prefInterval: Int? = null,
) {
val preferences = Injekt.get<LibraryPreferences>()
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
val interval = prefInterval ?: preferences.autoUpdateInterval.get()
if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
@@ -98,7 +98,7 @@ class LibraryUpdateNotifier(
),
)
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
}
@@ -153,7 +153,7 @@ class LibraryUpdateNotifier(
Notifications.ID_LIBRARY_ERROR,
Notifications.CHANNEL_LIBRARY_ERROR,
) {
setContentTitle(context.stringResource(MR.strings.notification_update_error, failed))
setContentTitle(context.pluralStringResource(MR.plurals.notification_update_error, failed, failed))
setContentText(context.stringResource(MR.strings.action_show_errors))
setSmallIcon(R.drawable.ic_mihon)
@@ -173,7 +173,7 @@ class LibraryUpdateNotifier(
Notifications.CHANNEL_NEW_CHAPTERS,
) {
setContentTitle(context.stringResource(MR.strings.notification_new_chapters))
if (updates.size == 1 && !securityPreferences.hideNotificationContent().get()) {
if (updates.size == 1 && !securityPreferences.hideNotificationContent.get()) {
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
} else {
setContentText(
@@ -184,7 +184,7 @@ class LibraryUpdateNotifier(
),
)
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
setStyle(
NotificationCompat.BigTextStyle().bigText(
updates.joinToString("\n") {
@@ -208,7 +208,7 @@ class LibraryUpdateNotifier(
}
// Per-manga notification
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
launchUI {
context.notify(
updates.map { (manga, chapters) ->
@@ -201,7 +201,7 @@ class NotificationReceiver : BroadcastReceiver() {
val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) }
.map {
val chapter = it.copy(read = true)
if (downloadPreferences.removeAfterMarkedAsRead().get()) {
if (downloadPreferences.removeAfterMarkedAsRead.get()) {
val manga = getManga.await(mangaId)
if (manga != null) {
val source = sourceManager.get(manga.source)
@@ -43,7 +43,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
override val supportsPrivateTracking: Boolean = true
private val scorePreference = trackPreferences.anilistScoreType()
private val scorePreference = trackPreferences.anilistScoreType
init {
// If the preference is an int from APIv1, logout user to force using APIv2
@@ -160,6 +160,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|large
|}
|format
|countryOfOrigin
|status
|chapters
|description
@@ -17,7 +17,7 @@ fun Track.toApiStatus() = when (status) {
private val preferences: TrackPreferences by injectLazy()
fun DomainTrack.toApiScore(): String = when (preferences.anilistScoreType().get()) {
fun DomainTrack.toApiScore(): String = when (preferences.anilistScoreType.get()) {
// 10 point
"POINT_10" -> (score.toInt() / 10).toString()
// 100 point
@@ -14,13 +14,22 @@ data class ALSearchItem(
val chapters: Long?,
val averageScore: Int?,
val staff: ALStaff,
val countryOfOrigin: String = "",
) {
fun toALManga(): ALManga = ALManga(
remoteId = id,
title = title.userPreferred,
imageUrl = coverImage.large,
description = description,
format = format.replace("_", "-"),
format = if (format != "MANGA") {
format.replace("_", "-")
} else {
when (countryOfOrigin) {
"KR" -> "Manhwa"
"CN", "TW" -> "Manhua"
else -> "Manga"
}
},
publishingStatus = status ?: "",
startDateFuzzy = startDate.toEpochMilli(),
totalChapters = chapters ?: 0,
@@ -193,6 +193,6 @@ class MangaUpdatesApi(
companion object {
private const val BASE_URL = "https://api.mangaupdates.com"
private val CONTENT_TYPE = "application/vnd.api+json".toMediaType()
private val CONTENT_TYPE = "application/json".toMediaType()
}
}
@@ -12,7 +12,9 @@ import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser
import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.PkceUtil
@@ -122,8 +124,23 @@ class MyAnimeListApi(
.put(formBodyBuilder.build())
.build()
with(json) {
authClient.newCall(request)
.awaitSuccess()
val response = authClient
.newCall(request)
.await()
if (!response.isSuccessful) {
if (response.body.string().contains("invalid_content")) {
// MAL returns unapproved titles in search but does not allow adding them to the list
// returns 400 with this body: {"message":"Invalid content","error":"invalid_content"}
// These unapproved titles cannot be filtered out in search and are also returned by the
// endpoint we use for id prefix search
throw MALTitleNotApproved()
} else {
throw HttpException(response.code)
}
}
response
.parseAs<MALListItemStatus>()
.let { parseMangaItem(it, track) }
}
@@ -80,5 +80,6 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
}
}
class MALTitleNotApproved : IOException("MAL: This title can't be added because it is waiting for approval.")
class MALTokenRefreshFailed : IOException("MAL: Failed to refresh account token")
class MALTokenExpired : IOException("MAL: Login has expired")
@@ -1,14 +1,14 @@
package eu.kanade.tachiyomi.di
import android.app.Application
import android.os.Build
import androidx.core.content.ContextCompat
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import app.cash.sqldelight.db.SqlDriver
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDatabaseType
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteDriver
import com.eygraber.sqldelight.androidx.driver.FileProvider
import eu.kanade.domain.track.store.DelayedTrackingStore
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache
@@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.AndroidSourceManager
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import nl.adaptivity.xmlutil.XmlDeclMode
@@ -44,37 +43,31 @@ import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addSingleton
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
import java.lang.ref.WeakReference
private val lock = Any()
class AppModule(val app: Application) : InjektModule {
private var sqlDriverRef: WeakReference<SqlDriver>? = null
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)
addSingletonFactory<SqlDriver> {
AndroidSqliteDriver(
schema = Database.Schema,
context = app,
name = "tachiyomi.db",
factory = if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Support database inspector in Android Studio
FrameworkSQLiteOpenHelperFactory()
} else {
RequerySQLiteOpenHelperFactory()
},
callback = object : AndroidSqliteDriver.Callback(Database.Schema) {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
setPragma(db, "foreign_keys = ON")
setPragma(db, "journal_mode = WAL")
setPragma(db, "synchronous = NORMAL")
}
private fun setPragma(db: SupportSQLiteDatabase, pragma: String) {
val cursor = db.query("PRAGMA $pragma")
cursor.moveToFirst()
cursor.close()
}
},
)
synchronized(lock) {
sqlDriverRef?.get()?.let { return@synchronized it }
AndroidxSqliteDriver(
driver = BundledSQLiteDriver(),
databaseType = AndroidxSqliteDatabaseType.FileProvider(app, "tachiyomi.db"),
schema = Database.Schema,
configuration = AndroidxSqliteConfiguration(
isForeignKeyConstraintsEnabled = true,
),
)
.also { sqlDriverRef = WeakReference(it) }
}
}
addSingletonFactory {
Database(
@@ -32,7 +32,7 @@ class PreferenceModule(val app: Application) : InjektModule {
addSingletonFactory {
NetworkPreferences(
preferenceStore = get(),
verboseLogging = isDebugBuildType,
verboseLoggingDefault = isDebugBuildType,
)
}
addSingletonFactory {
@@ -76,7 +76,7 @@ class ExtensionManager(
ExtensionInstallReceiver(InstallationListener()).register(context)
}
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages.isSet()
fun getExtensionPackage(sourceId: Long): String? {
return installedExtensionsFlow.value.find { extension ->
@@ -171,12 +171,12 @@ class ExtensionManager(
.map(Extension.Available.Source::lang)
val deviceLanguage = Locale.getDefault().language
val defaultLanguages = preferences.enabledLanguages().defaultValue()
val defaultLanguages = preferences.enabledLanguages.defaultValue()
val languagesToEnable = availableLanguages.filter {
it != deviceLanguage && it.startsWith(deviceLanguage)
}
preferences.enabledLanguages().set(defaultLanguages + languagesToEnable)
preferences.enabledLanguages.set(defaultLanguages + languagesToEnable)
subLanguagesEnabledOnFirstRun = true
}
@@ -187,7 +187,7 @@ class ExtensionManager(
*/
private fun updatedInstalledExtensionsStatuses(availableExtensions: List<Extension.Available>) {
if (availableExtensions.isEmpty()) {
preferences.extensionUpdatesCount().set(0)
preferences.extensionUpdatesCount.set(0)
return
}
@@ -366,7 +366,7 @@ class ExtensionManager(
private fun updatePendingUpdatesCount() {
val pendingUpdateCount = installedExtensionMapFlow.value.values.count { it.hasUpdate }
preferences.extensionUpdatesCount().set(pendingUpdateCount)
preferences.extensionUpdatesCount.set(pendingUpdateCount)
if (pendingUpdateCount == 0) {
ExtensionUpdateNotifier(context).dismiss()
}
@@ -29,7 +29,7 @@ class ExtensionUpdateNotifier(
names.size,
),
)
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
val extNames = names.joinToString(", ")
setContentText(extNames)
setStyle(NotificationCompat.BigTextStyle().bigText(extNames))
@@ -106,8 +106,13 @@ class PackageInstallerInstaller(private val service: Service) : Installer(servic
override fun cancelEntry(entry: Entry): Boolean {
activeSession?.let { (activeEntry, sessionId) ->
if (activeEntry == entry) {
packageInstaller.abandonSession(sessionId)
return false
return try {
packageInstaller.abandonSession(sessionId)
false
} catch (_: SecurityException) {
// Highly likely the session has succeeded
true
}
}
}
return true
@@ -39,7 +39,7 @@ internal class ExtensionInstaller(
private val scope = CoroutineScope(Dispatchers.IO)
private val activeJobs = mutableMapOf<String, Job>()
private val activeSteps = mutableMapOf<Long, MutableStateFlow<InstallStep>>()
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller()
private val extensionInstaller = Injekt.get<BasePreferences>().extensionInstaller
private val httpClient: OkHttpClient = Injekt.get<NetworkHelper>().client
@@ -43,7 +43,7 @@ internal object ExtensionLoader {
private val preferences: SourcePreferences by injectLazy()
private val trustExtension: TrustExtension by injectLazy()
private val loadNsfwSource by lazy {
preferences.showNsfwSource().get()
preferences.showNsfwSource.get()
}
private const val EXTENSION_FEATURE = "tachiyomi.extension"
@@ -8,7 +8,7 @@ import uy.kohesive.injekt.api.get
fun Source.getNameForMangaInfo(): String {
val preferences = Injekt.get<SourcePreferences>()
val enabledLanguages = preferences.enabledLanguages().get()
val enabledLanguages = preferences.enabledLanguages.get()
.filterNot { it in listOf("all", "other") }
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = lang in enabledLanguages
@@ -33,14 +33,14 @@ interface SecureActivityDelegate {
fun onApplicationStopped() {
val preferences = Injekt.get<SecurityPreferences>()
if (!preferences.useAuthenticator().get()) return
if (!preferences.useAuthenticator.get()) return
if (!AuthenticatorUtil.isAuthenticating) {
// Return if app is closed in locked state
if (requireUnlock) return
// Save app close time if lock is delayed
if (preferences.lockAppAfter().get() > 0) {
preferences.lastAppClosed().set(System.currentTimeMillis())
if (preferences.lockAppAfter.get() > 0) {
preferences.lastAppClosed.set(System.currentTimeMillis())
}
}
}
@@ -50,13 +50,13 @@ interface SecureActivityDelegate {
*/
fun onApplicationStart() {
val preferences = Injekt.get<SecurityPreferences>()
if (!preferences.useAuthenticator().get()) return
if (!preferences.useAuthenticator.get()) return
val lastClosedPref = preferences.lastAppClosed()
val lastClosedPref = preferences.lastAppClosed
// `requireUnlock` can be true on process start or if app was closed in locked state
if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) {
requireUnlock = when (val lockDelay = preferences.lockAppAfter().get()) {
requireUnlock = when (val lockDelay = preferences.lockAppAfter.get()) {
-1 -> false // Never
0 -> true // Always
else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis()
@@ -93,8 +93,8 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
}
private fun setSecureScreen() {
val secureScreenFlow = securityPreferences.secureScreen().changes()
val incognitoModeFlow = preferences.incognitoMode().changes()
val secureScreenFlow = securityPreferences.secureScreen.changes()
val incognitoModeFlow = preferences.incognitoMode.changes()
combine(secureScreenFlow, incognitoModeFlow) { secureScreen, incognitoMode ->
secureScreen == SecurityPreferences.SecureScreenMode.ALWAYS ||
(secureScreen == SecurityPreferences.SecureScreenMode.INCOGNITO && incognitoMode)
@@ -104,7 +104,7 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
}
private fun setAppLock() {
if (!securityPreferences.useAuthenticator().get()) return
if (!securityPreferences.useAuthenticator.get()) return
if (activity.isAuthenticationSupported()) {
if (!SecureActivityDelegate.requireUnlock) return
activity.startActivity(Intent(activity, UnlockActivity::class.java))
@@ -115,7 +115,7 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
activity.overridePendingTransition(0, 0)
}
} else {
securityPreferences.useAuthenticator().set(false)
securityPreferences.useAuthenticator.set(false)
}
}
}
@@ -23,7 +23,7 @@ interface ThemingDelegate {
class ThemingDelegateImpl : ThemingDelegate {
override fun applyAppTheme(activity: Activity) {
val uiPreferences = Injekt.get<UiPreferences>()
ThemingDelegate.getThemeResIds(uiPreferences.appTheme().get(), uiPreferences.themeDarkAmoled().get())
ThemingDelegate.getThemeResIds(uiPreferences.appTheme.get(), uiPreferences.themeDarkAmoled.get())
.forEach(activity::setTheme)
}
}
@@ -37,7 +37,7 @@ class ExtensionFilterScreenModel(
screenModelScope.launch {
combine(
getExtensionLanguages.subscribe(),
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
) { a, b -> a to b }
.catch { throwable ->
logcat(LogPriority.ERROR, throwable)
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO
@@ -97,11 +98,11 @@ class ExtensionsScreenModel(
screenModelScope.launchIO { findAvailableExtensions() }
preferences.extensionUpdatesCount().changes()
preferences.extensionUpdatesCount.changes()
.onEach { mutableState.update { state -> state.copy(updates = it) } }
.launchIn(screenModelScope)
basePreferences.extensionInstaller().changes()
basePreferences.extensionInstaller.changes()
.onEach { mutableState.update { state -> state.copy(installer = it) } }
.launchIn(screenModelScope)
}
@@ -180,6 +181,7 @@ class ExtensionsScreenModel(
private suspend fun Flow<InstallStep>.collectToInstallUpdate(extension: Extension) =
this
.onEach { installStep -> addDownloadState(extension, installStep) }
.takeWhile { installStep -> installStep != InstallStep.Installed }
.onCompletion { removeDownloadState(extension) }
.collect()
@@ -86,7 +86,7 @@ class ExtensionDetailsScreenModel(
}
}
launch {
preferences.incognitoExtensions()
preferences.incognitoExtensions
.changes()
.map { pkgName in it }
.distinctUntilChanged()
@@ -16,7 +16,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
@@ -167,7 +166,9 @@ class SourcePreferencesFragment : PreferenceFragmentCompat() {
fun getInstance(sourceId: Long): SourcePreferencesFragment {
return SourcePreferencesFragment().apply {
arguments = bundleOf(SOURCE_ID to sourceId)
arguments = Bundle().apply {
putLong(SOURCE_ID, sourceId)
}
}
}
}
@@ -19,7 +19,7 @@ class MigrateSearchScreenModel(
private val sourcePreferences: SourcePreferences = Injekt.get(),
) : SearchScreenModel() {
private val migrationSources by lazy { sourcePreferences.migrationSources().get() }
private val migrationSources by lazy { sourcePreferences.migrationSources.get() }
override val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
compareBy<CatalogueSource>(
@@ -49,11 +49,11 @@ class MigrateSourceScreenModel(
}
}
preferences.migrationSortingDirection().changes()
preferences.migrationSortingDirection.changes()
.onEach { mutableState.update { state -> state.copy(sortingDirection = it) } }
.launchIn(screenModelScope)
preferences.migrationSortingMode().changes()
preferences.migrationSortingMode.changes()
.onEach { mutableState.update { state -> state.copy(sortingMode = it) } }
.launchIn(screenModelScope)
}
@@ -28,8 +28,8 @@ class SourcesFilterScreenModel(
screenModelScope.launch {
combine(
getLanguagesWithSources.subscribe(),
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.enabledLanguages.changes(),
preferences.disabledSources.changes(),
) { a, b, c -> Triple(a, b, c) }
.catch { throwable ->
mutableState.update {
@@ -71,7 +71,7 @@ class BrowseSourceScreenModel(
private val getIncognitoState: GetIncognitoState = Injekt.get(),
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
var displayMode by sourcePreferences.sourceDisplayMode().asState(screenModelScope)
var displayMode by sourcePreferences.sourceDisplayMode.asState(screenModelScope)
val source = sourceManager.getOrStub(sourceId)
@@ -95,14 +95,14 @@ class BrowseSourceScreenModel(
}
if (!getIncognitoState.await(source.id)) {
sourcePreferences.lastUsedSource().set(source.id)
sourcePreferences.lastUsedSource.set(source.id)
}
}
/**
* Flow of Pager flow tied to [State.listing]
*/
private val hideInLibraryItems = sourcePreferences.hideInLibraryItems().get()
private val hideInLibraryItems = sourcePreferences.hideInLibraryItems.get()
val mangaPagerFlowFlow = state.map { it.listing }
.distinctUntilChanged()
.map { listing ->
@@ -123,9 +123,9 @@ class BrowseSourceScreenModel(
fun getColumnsPreference(orientation: Int): GridCells {
val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE
val columns = if (isLandscape) {
libraryPreferences.landscapeColumns()
libraryPreferences.landscapeColumns
} else {
libraryPreferences.portraitColumns()
libraryPreferences.portraitColumns
}.get()
return if (columns == 0) GridCells.Adaptive(128.dp) else GridCells.Fixed(columns)
}
@@ -241,7 +241,7 @@ class BrowseSourceScreenModel(
fun addFavorite(manga: Manga) {
screenModelScope.launch {
val categories = getCategories()
val defaultCategoryId = libraryPreferences.defaultCategory().get()
val defaultCategoryId = libraryPreferences.defaultCategory.get()
val defaultCategory = categories.find { it.id == defaultCategoryId.toLong() }
when {
@@ -47,9 +47,9 @@ abstract class SearchScreenModel(
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
private var searchJob: Job? = null
private val enabledLanguages = sourcePreferences.enabledLanguages().get()
private val disabledSources = sourcePreferences.disabledSources().get()
protected val pinnedSources = sourcePreferences.pinnedSources().get()
private val enabledLanguages = sourcePreferences.enabledLanguages.get()
private val disabledSources = sourcePreferences.disabledSources.get()
protected val pinnedSources = sourcePreferences.pinnedSources.get()
private var lastQuery: String? = null
private var lastSourceFilter: SourceFilter? = null
@@ -66,7 +66,7 @@ abstract class SearchScreenModel(
init {
screenModelScope.launch {
preferences.globalSearchFilterState().changes().collectLatest { state ->
preferences.globalSearchFilterState.changes().collectLatest { state ->
mutableState.update { it.copy(onlyShowHasResults = state) }
}
}
@@ -119,7 +119,7 @@ abstract class SearchScreenModel(
}
fun toggleFilterResults() {
preferences.globalSearchFilterState().toggle()
preferences.globalSearchFilterState.toggle()
}
fun search() {
@@ -189,7 +189,7 @@ class HistoryScreenModel(
screenModelScope.launchIO {
// Move to default category if applicable
val categories = getCategories()
val defaultCategoryId = libraryPreferences.defaultCategory().get().toLong()
val defaultCategoryId = libraryPreferences.defaultCategory.get().toLong()
val defaultCategory = categories.find { it.id == defaultCategoryId }
when {
@@ -242,8 +242,8 @@ object HomeScreen : Screen() {
val count by produceState(initialValue = 0) {
val pref = Injekt.get<LibraryPreferences>()
combine(
pref.newShowUpdatesCount().changes(),
pref.newUpdatesCount().changes(),
pref.newShowUpdatesCount.changes(),
pref.newUpdatesCount.changes(),
) { show, count -> if (show) count else 0 }
.collectLatest { value = it }
}
@@ -263,7 +263,7 @@ object HomeScreen : Screen() {
}
BrowseTab::class.isInstance(tab) -> {
val count by produceState(initialValue = 0) {
Injekt.get<SourcePreferences>().extensionUpdatesCount().changes()
Injekt.get<SourcePreferences>().extensionUpdatesCount.changes()
.collectLatest { value = it }
}
if (count > 0) {

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