Compare commits

..
325 Commits
Author SHA1 Message Date
AntsyLich 19f1d00fcb Release v0.20.0 2026-06-27 05:58:04 +06:00
Mend Renovate 8c099aca90 Update Gradle to v9.6.1 (#3475) 2026-06-26 23:29:54 +06:00
AntsyLich 6c6a07c0c0 Ensure app shortcuts use correct color scheme on preview builds (#3473) 2026-06-26 14:25:19 +06:00
AntsyLich 8e323e30de Update store decoding to account for new CONTENT_WARNING_UNSPECIFIED (#3472) 2026-06-26 07:24:53 +00:00
Mend Renovate 91ccc0ca40 Update actions/setup-java action to v5.4.0 (#3470) 2026-06-26 08:48:57 +06:00
AntsyLich 735cea35f9 Ensure all tachiyomix required dependencies are present (#3465) 2026-06-23 18:19:27 +00:00
AntsyLich 41265cce32 Query for new tachiyomix metadata from extensions (#3464) 2026-06-23 18:04:44 +00:00
J. FronnyandAntsyLich 176a5409b2 Add option to open app settings from system settings (#3180)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-23 23:24:43 +06:00
NGB-Was-TakenandAntsyLich f23170bfaa Update app shortcuts to use app icon color scheme (#2835)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-23 23:12:57 +06:00
Leodyver SemillaandAntsyLich bc7f7e70a1 Prevent random FileNotFoundException when reading chapters (#3154)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-23 16:44:27 +00:00
xenitaneandAntsyLich a1b5ea811a Tweak Catppuccin color scheme for clarity (#3436)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-23 16:40:02 +00:00
+21 9d8d70dc61 Translations update from Hosted Weblate (#3227)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kmr/
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/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Abdullah Al-Shamiry (Ace) <its.shamiry@gmail.com>
Co-authored-by: Adam Havránek <adamhavra@seznam.cz>
Co-authored-by: Davi Oliveira <davikson13@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hasanur Rahman Biplob <iam.biplob@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <167056923+itsmechinmoy@users.noreply.github.com>
Co-authored-by: Jon C. <picujon@users.noreply.hosted.weblate.org>
Co-authored-by: Lucas Correia <anicetoclucas@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Ngo Lam Bao An <onionandsalad@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Romb38 <r.barbier38@standouda.fr>
Co-authored-by: Sahal <sahaltp369@gmail.com>
Co-authored-by: ScratchBuild <foobarbuzz@gmail.com>
Co-authored-by: Sebastian Mihai Crap <sebastiancrap@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Sixten Lund <arbitraryindices@users.noreply.hosted.weblate.org>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Teashrock <kajitsu22@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Co-authored-by: icarus suraci <mhmtylmz112000@gmail.com>
Co-authored-by: on9686 <on9686@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: uaevuon <newget64@gmail.com>
Co-authored-by: ابْنُ السَدِيمِ <amarlubs2@gmail.com>
2026-06-23 22:22:31 +06:00
Luca AuerandAntsyLich 06497622f6 Batch database operations during backup restore for improved performance (#3267)
* perf(backup): batch database operations during restore

Reduce overhead by chunking manga and repo restoration into
transactions of 100 entries. Categories now restored in a single
transaction. Optimized DatabaseHandler to reuse existing transaction
contexts instead of creating redundant nested ones.

Changed TransactionElement to internal to allow AndroidDatabaseHandler
to check transaction state from the same package.

* fix(backup): make restore progress and errors thread-safe

Use AtomicInteger for progress and synchronizedList for errors to
prevent race conditions during concurrent restoration coroutines.

* perf(backup): update progress notification only once per chunk

Reduce UI/IPC overhead by moving notification updates out of the inner
loop, triggering them only after each 100-item transaction chunk.

Reduced on my emulator from 46 seconds to 27

* fix: remove invalid awaitAsOne() call on insert() return type

categoriesQueries.insert() returns Long (the inserted row ID), not an
ExecutableQuery<T>, so awaitAsOne() is not applicable.

* fix(feedback): Use `CopyOnWriteArrayList` and kotlin `AtomicInt`, add a changelog entry

Note that using kotlin `AtomicInt` instead of java `AtomicInteger` requires opting into an experimental API.

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-23 19:45:25 +06:00
AntsyLich c397d657d1 Remove BrotliInterceptor from default client (#3458) 2026-06-23 19:40:02 +06:00
Mend Renovate 6f99c7b970 Update markdown to v0.43.0 (#3462) 2026-06-23 19:39:13 +06:00
AntsyLich 1cb38a1c33 Bump version code 2026-06-22 00:51:22 +06:00
AntsyLich b3e190c627 Refactor baseline profiles generation and regenerate (#3434) 2026-06-22 00:50:24 +06:00
AntsyLich 3bb734dde7 Swap store content rating for warning and move to extension level 2026-06-22 00:37:39 +06:00
AntsyLich 5f5a034112 Allow extension stores to have separate extension list file (#3454) 2026-06-21 18:26:38 +00:00
AntsyLich eb5f57a48c Simplify extension store decoding and add gzip compression support (#3453) 2026-06-21 15:08:52 +00:00
AntsyLich 903add50c9 Address gradle 9.6 deprecations (#3452) 2026-06-21 12:42:26 +00:00
Mend Renovate c7e782c179 Update markdown to v0.42.0 (#3447) 2026-06-20 23:42:08 +06:00
Mend Renovate 53724fd561 Update softprops/action-gh-release action to v3.0.1 (#3444) 2026-06-20 23:40:32 +06:00
Mend Renovate 5f80403c5d Update GitHub Actions to v7 (#3438) 2026-06-19 13:09:41 +06:00
Mend Renovate d4ad76d576 Update kotest.assertions to v6.2.1 (#3437) 2026-06-19 13:08:42 +06:00
Mend Renovate 46b5cce007 Update Gradle to v9.6.0 (#3439) 2026-06-19 13:08:20 +06:00
AntsyLich 80541831bb Revert "Use app scoped CoroutineScope (#3403)"
This reverts commit 509eee5dfb.

Fixes #3429
2026-06-18 17:40:01 +06:00
AntsyLich ee6a9876ec Fix uploaded apk reference in build workflow (#3435) 2026-06-18 01:20:43 +06:00
Mend RenovateandAntsyLich df37d81dfb Update androidx.lifecycle to v2.11.0 (#3433)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-17 19:11:07 +00:00
Mend Renovate e99a50f612 Update androidx.compose.bom to v2026.06.00 (#3432) 2026-06-18 01:04:06 +06:00
AntsyLich 50d0e8ae0a Only try to read signing data in release workflow (#3431) 2026-06-17 18:34:00 +00:00
AntsyLich 6552ffe315 Sign APK with AGP 2026-06-17 02:40:35 +06:00
Mend Renovate 381eb07ec9 Update google.services to v4.5.0 (#3422) 2026-06-16 19:40:09 +00:00
Mend Renovate 3eafa16c64 Update firebase.bom to v34.15.0 (#3419) 2026-06-17 01:23:55 +06:00
Mend Renovate 672ff416c5 Update actions/setup-java to v5.3.0 (#3420) 2026-06-16 17:40:59 +00:00
Mend Renovate 16376f509c Update GitHub Actions (major) (#3004) 2026-06-16 23:33:21 +06:00
Mend Renovate 56fde24532 Update spotless to v8.7.0 (#3416) 2026-06-16 23:25:28 +06:00
Mend Renovate 518d2ab7df Update kotest.assertions to v6.2.0 (#3415) 2026-06-16 23:24:55 +06:00
AntsyLich 082bd66673 Remove redundant call to updateMangaFromRemote in manga screen 2026-06-16 20:03:07 +06:00
AntsyLich 5880f4b0bd Ensure chapter order is retained when passed to source.getMangaUpdate 2026-06-16 20:01:47 +06:00
AntsyLich d156496cfa Update num_repos string to use store 2026-06-16 19:38:10 +06:00
AntsyLich eb80ab410e Ensure memo is correctly passed around when transforming classes 2026-06-16 19:05:52 +06:00
AntsyLich 5896d2c939 Fix compatibility with older backups
Fixes #3413
2026-06-16 03:18:12 +06:00
AntsyLich e3610cbe2c Allow app to load TachiyomiX 1.6 extensions 2026-06-16 00:13:10 +06:00
AntsyLich 94cab02c1e Adapt model implementation for TachiyomiX 1.6 (#3412) 2026-06-15 17:32:39 +00:00
AntsyLich f00cac265f Remove text limit of manga notes (#3410) 2026-06-14 14:55:04 +00:00
MajorTanya aa6742bf3e Suppress more tachiyomix-related deprecations (#3409) 2026-06-14 20:47:11 +06:00
AntsyLich 4c37f4c764 Adapt source implementation for TachiyomiX 1.6 (#3243) 2026-06-13 20:34:31 +00:00
AntsyLich 430b13bb81 Adapt network implementation for TachiyomiX 1.6 (#3248) 2026-06-14 02:10:55 +06:00
AntsyLich c15f7bee85 Ensure old extension store is removed if index url changes (#3408) 2026-06-13 18:14:16 +00:00
AntsyLich 1a030420b1 Supress UnstableApiUsage in settings.gradle.kts 2026-06-13 23:57:35 +06:00
AntsyLich a6f0cec083 Use . toDrawable extension function in ImageUtil 2026-06-13 23:57:35 +06:00
AntsyLich 6ae79d255d Address various redundant constructs 2026-06-13 23:57:35 +06:00
AntsyLich 4b9974e20a Use duration overload for time related methods 2026-06-13 23:57:35 +06:00
AntsyLich 509eee5dfb Use app scoped CoroutineScope (#3403) 2026-06-12 17:14:29 +06:00
Mend Renovate 63a71fa447 Update coil to v3.5.0 (#3399) 2026-06-10 16:46:39 +00:00
AntsyLich d4327c8d48 Add auto migration support from legacy extension store index (#3398) 2026-06-10 22:29:27 +06:00
AntsyLich 5f1d76a73a Change the term "Obsolete" to "Orphaned" for extensions (#3383) 2026-06-10 13:53:01 +00:00
AntsyLich 35f6a607b8 Use our fork of sqldelight-androidx-driver (#3396) 2026-06-10 13:49:27 +00:00
Mend Renovate 378518c0fd Update firebase.bom to v34.14.1 (#3391) 2026-06-10 17:26:44 +06:00
Mend Renovate 69fb33887f Update okhttp monorepo to v5.4.0 (#3393) 2026-06-10 17:24:51 +06:00
AntsyLich f87c4d55e3 Use more sqldelight coroutines extensions 2026-06-07 18:01:17 +06:00
AntsyLich c612a6ca8d Extract badge data separately when forming LibraryItem (#3382) 2026-06-04 18:25:42 +00:00
AntsyLich a1ad33baef Remove redundant compiler argument for annotation default target 2026-06-05 00:08:21 +06:00
Mend RenovateandAntsyLich 8c27bc07fc Update androidx.core to v1.19.0 (#3379)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-04 16:27:41 +00:00
AntsyLich 95bc599ac0 Add paddings to vertical chapter navigator 2026-06-04 10:07:35 +06:00
AntsyLich cd27b10750 Drop kotlinx-collections-immutable usage (#3380) 2026-06-03 20:45:24 +00:00
Mend Renovate 7f8851de6a Update androidx.compose.bom to v2026.05.02 (#3378) 2026-06-03 20:05:01 +00:00
AntsyLich a330258f6a Add vertical chapter navigator for long strip mode (#3377) 2026-06-03 19:54:52 +00:00
AntsyLich d28c605a89 Remove redundant compiler argument for context parameter 2026-06-04 00:12:30 +06:00
Mend Renovate 87c1675882 Update kotlin monorepo to v2.4.0 (#3375) 2026-06-03 22:20:18 +06:00
AntsyLich b446d2a6b1 Change extension repo to extension store and add support for newer extension index format (#3349) 2026-06-03 06:35:44 +06:00
YohanesandAntsyLich 748211c19b Add missing outlineVariant color to Nord theme (#3184)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-02 22:57:59 +00:00
IDika31 1fd02d0c82 Use RECEIVER_NOT_EXPORTED for ShizukuInstaller broadcast receiver (#3356) 2026-06-02 20:43:42 +00:00
Mend RenovateandAntsyLich 4951f0bddd Update markdown to v0.41.0 (#3194)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-06-02 20:38:53 +00:00
Mend Renovate d391566832 Update GitHub Actions to v6.0.3 (#3374) 2026-06-03 02:21:44 +06:00
Mend Renovate 0b564f1f20 Update androidx.paging to v3.5.0 (#3301) 2026-05-30 16:10:05 +06:00
Mend Renovate b639e44a03 Update dependency io.mockk:mockk to v1.14.11 (#3363) 2026-05-30 16:08:14 +06:00
Mend Renovate f68c1bcc9e Update dependency com.google.firebase:firebase-bom to v34.14.0 (#3362) 2026-05-30 16:07:50 +06:00
Mend Renovate 4fabe9b405 Update spotless to v8.6.0 (#3357) 2026-05-30 16:07:10 +06:00
Mend Renovate ae3aa7926a Update aboutLibraries to v14.2.1 (#3343) 2026-05-25 14:15:12 +06:00
Mend Renovate 82338d7d64 Update dependency androidx.compose:compose-bom-alpha to v2026.05.01 (#3334) 2026-05-22 14:50:44 +06:00
Mend Renovate 3b94df6108 Update dependency org.junit.jupiter:junit-jupiter to v6.1.0 (#3335) 2026-05-22 14:49:38 +06:00
Mend Renovate 5bb92a75c7 Update spotless to v8.5.1 (#3322) 2026-05-22 14:49:15 +06:00
Mend Renovate 48179083f1 Update Gradle to v9.5.1 (#3315) 2026-05-14 22:33:12 +06:00
AntsyLich b4a3ec0b32 Fix chapters and categories not being added to database
Closes #3278
Fixes #3291
2026-05-12 02:04:58 +06:00
Mend Renovate cb2a0ea570 Update dependency com.mohamedrejeb.richeditor:richeditor-compose to v1.0.0-rc14 (#3271) 2026-05-12 01:29:31 +06:00
AntsyLich 9c3519046f Remove redundant KGP classpath in :app 2026-05-08 22:54:02 +06:00
Mend Renovate da9b5d9b9d Update dependency com.eygraber:sqldelight-androidx-driver to v0.2.0 (#3292) 2026-05-08 22:53:11 +06:00
Mend Renovate 09d3e16183 Update aboutLibraries to v14.2.0 (#3283) 2026-05-08 22:52:17 +06:00
Mend Renovate cfefa51296 Update dependency com.google.firebase:firebase-bom to v34.13.0 (#3303) 2026-05-08 22:49:00 +06:00
Mend Renovate b65825b3ca Update dependency androidx.compose:compose-bom-alpha to v2026.05.00 (#3302) 2026-05-08 22:48:08 +06:00
Mend Renovate 981b16094a Update kotlinx-coroutines monorepo to v1.11.0 (#3305) 2026-05-08 22:39:25 +06:00
Mend Renovate bb877e4b26 Update moko.resources to v0.26.4 (#3297) 2026-05-06 18:01:53 +06:00
Mend Renovate 5b8adfbdb0 Update android.gradle to v9.2.1 (#3294) 2026-05-06 17:50:41 +06:00
Mend Renovate 9678b8fd79 Update Gradle to v9.5.0 (#3274) 2026-04-28 23:30:39 +06:00
AntsyLich 8e0c911f93 Update sqldelight-androidx-driver and remove transaction management (#3270)
`sqldelight-androidx-driver` has it's own transaction management
2026-04-27 18:24:30 +06:00
AntsyLich a2f3ec94aa Reapply "Update android.gradle to v9 (major) (#3105)" (#3148) 2026-04-25 12:06:41 +06:00
AntsyLich 38ccd7d5fc Update dependency image-decoder to e03b81e18a (#3262) 2026-04-25 01:17:32 +00:00
Mend Renovate 4faf89fc6f Update dependency androidx.compose:compose-bom-beta to v2026.04.01 (#3256) 2026-04-24 19:38:45 +06:00
Mend Renovate 5b32a990a2 Update dependency sh.calvin.reorderable:reorderable to v3.1.0 (#3251) 2026-04-24 00:54:21 +06:00
Mend Renovate cb6daa5e4c Update kotlin monorepo to v2.3.21 (#3258) 2026-04-24 00:53:40 +06:00
Mend Renovate d68347f5fb Update softprops/action-gh-release action to v2.6.2 (#3230) 2026-04-20 17:55:44 +06:00
Mend Renovate e676c8e6e1 Update dependency com.gradleup.tapmoc:tapmoc-gradle-plugin to v0.4.2 (#3232) 2026-04-20 17:55:23 +06:00
Mend Renovate 34fa9138d9 Update dependency org.jsoup:jsoup to v1.22.2 (#3245) 2026-04-20 17:49:30 +06:00
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
AntsyLich f9f938f5af Release v0.19.4 2026-02-26 18:27:20 +06:00
AntsyLich 1d7c838ae6 Revert "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)"
This reverts commit  22d5c9d9f4
2026-02-26 18:25:56 +06:00
AntsyLich d219c5e3bb Revert "Fix cache invalidation isn't done at startup (#2970)"
This reverts commit e6cc38de06.
2026-02-26 18:25:56 +06:00
cc3f640faa Translations update from Hosted Weblate (#2997)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon

Co-authored-by: Filip Jaruška <filip.jaruska@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2026-02-26 18:24:45 +06:00
AntsyLichandp 84265febf3 Fix extension install/update stuck at pending (#3000)
Co-authored-by: p
2026-02-26 01:58:38 +00:00
Mend Renovate 171a06a8ba Update dependency io.kotest:kotest-assertions-core to v6.1.4 (#2998) 2026-02-26 07:51:00 +06:00
565379779f Translations update from Hosted Weblate (#2980)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translation: Mihon/Mihon

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: KraXen <dpelech1@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
2026-02-25 19:13:17 +06:00
AntsyLichandjeremiejig 0394714274 Run automatic library updates even when connected to a VPN (#2996)
Co-authored-by: jeremiejig <3978761+jeremiejig@users.noreply.github.com>
2026-02-25 13:05:30 +00:00
Cuong-TranandAntsyLich 75b445fa8f Going back now first clears search query on browse extension tab (#2906)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-02-25 12:41:45 +00:00
AntsyLich 0cc724108b Add all pages of adjacent chapters in the UI instead of only the first or last three (#2995) 2026-02-25 12:28:53 +00:00
Mend Renovate 1e6c35d887 Update gradle/actions action to v5.0.2 (#2990) 2026-02-25 18:22:10 +06:00
Mend Renovate 5b85084d0a Update dependency io.coil-kt.coil3:coil-bom to v3.4.0 (#2992) 2026-02-25 18:21:55 +06:00
AntsyLich 8b2d35f306 Fix migration dialog not showing for consecutive prompts from the same screen (#2994) 2026-02-25 11:15:13 +00:00
AntsyLich 47816d4b21 Fix migration's selected sources order not preserved (#2993) 2026-02-25 17:04:24 +06:00
NarwhalHornsandAntsyLich 89bbdb17fb Utilize tracker for library duplicate detection (#2978)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-02-21 10:26:36 +00:00
NarwhalHorns 3c6f0f1697 Add option for bookmarked chapters to download dropdown (#2891) 2026-02-21 16:14:55 +06:00
ab214526c6 Optimize tracked library filter (#2977)
Co-authored-by: NarwhalHorns <onefailedgamer@gamil.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-02-20 16:22:46 +00:00
Luca Auer e6cc38de06 Fix cache invalidation isn't done at startup (#2970) 2026-02-20 21:09:00 +06:00
+12 7c7d35e2c6 Translations update from Hosted Weblate (#2843)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon

Co-authored-by: Ai Ai Ecchi <aiaiecchi9og@gmail.com>
Co-authored-by: Alexandre Dhooge <alexandre.dhooge@zaclys.net>
Co-authored-by: Cuong Tran <cuongtran.tm@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hualiang <642615676@qq.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Throw Away <throwawayacc4gulshan@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: clukki <nguyenhuuminh16911@gmail.com>
Co-authored-by: momoehab <momoehab@gmail.com>
Co-authored-by: yumekon <konrad.nowicki91@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2026-02-20 20:35:25 +06:00
Cuong-Tran 9867c160f7 Remove redundant userSelected from selection methods (#2976) 2026-02-20 20:22:05 +06:00
Cuong-Tran 20c899f2cd Don't wrap an intent-chooser inside another intent-chooser (#2921) 2026-02-20 20:20:13 +06:00
Mend Renovate 3f64529412 Update actions/dependency-review-action action to v4.8.3 (#2975) 2026-02-20 20:18:28 +06:00
Mend Renovate 27749fc583 Update moko to v0.26.0 (#2967) 2026-02-15 15:57:44 +00:00
Mend Renovate 2248bdac54 Update dependency com.google.firebase:firebase-bom to v34.9.0 (#2964) 2026-02-15 21:48:42 +06:00
Mend Renovate 42c7329a9e Update dependency org.junit.jupiter:junit-jupiter to v6.0.3 (#2963) 2026-02-15 21:48:08 +06:00
Luca Auer 22d5c9d9f4 Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955) 2026-02-15 21:47:31 +06:00
Mend Renovate 5be5a4e819 Update dependency androidx.compose:compose-bom to v2026.02.00 (#2962) 2026-02-15 20:37:53 +06:00
Mend Renovate b924d582f4 Update dependency androidx.activity:activity-compose to v1.12.4 (#2959) 2026-02-15 20:36:33 +06:00
Mend Renovate 6f601a18c5 Update kotlin monorepo to v2.3.10 (#2960) 2026-02-15 20:36:17 +06:00
Mend Renovate 1ff9a5625c Update paging.version to v3.4.1 (#2961) 2026-02-15 20:36:02 +06:00
Luca Auer cae9fbf321 Add missing indexes to improve database query performance (#2950) 2026-02-15 00:43:31 +06:00
Mend Renovate d17976c910 Update dependency io.kotest:kotest-assertions-core to v6.1.3 (#2939) 2026-02-09 06:32:07 +06:00
Mend Renovate b609166702 Update dependency com.materialkolor:material-kolor to v5.0.0-alpha06 (#2938) 2026-02-09 06:31:53 +06:00
MajorTanya cf93afab45 Add src:local search alias for Local Source (#2928) 2026-02-06 01:15:34 +06:00
MajorTanya b543bc089a Clean up some build warnings (#2929)
* Replace deprecated rememberPlainTooltipPositionProvider

* Remove superfluous when branch

This when is marked as exhaustive.

* Replace deprecated LibrariesContainer call

AboutLibraries now wants us to produce the libraries ourselves.

* Replace deprecated ClipboardManager with Clipboard

Clipboard uses suspend functions, hence the coroutine scope addition.

* Use multi-dollar strs to simplify GraphQL queries

These have been available since Kotlin 2.1.

* Remove various redundant casts & conversions

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

* Replace deprecated MenuAnchorType

Literally just a typealias for ExposedDropdownMenuAnchorType anyway.

* OptimizeNonSkippingGroups is enabled by default

* Suppress shadowing warning

This is explicitly intentional according to the KDocs.

* Migrate Context Receivers to Context Parameters

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

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

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

* Fix expression bodies with explicit return

Naming conflict resolved by aliasing.

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

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

* Suppress deprecation of non-AutoMirrored icons

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

* Explicitly opt-in to new annotation default rules

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

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

Warning message was the following:

    This annotation is currently applied to the value parameter only, but in the future it will also be applied to field.
    - To opt in to applying to both value parameter and field, add '-Xannotation-default-target=param-property' to your compiler arguments.
    - To keep applying to the value parameter only, use the '@param:' annotation target.
2026-02-05 11:29:21 +06:00
MajorTanya 4e4bdffdf3 Add "src:" prefix to search by source ID (#2927)
Allows users to search for the exact source ID in their library.

Similar to the Browse > Migrate screen, but filtered in the Library,
where users can take all the usual actions.

Could be used in the future to change the search behaviour of tapping
the source name in the title info view to search by the ID with this
prefix.
2026-02-03 15:54:22 +06:00
Mend Renovate 100cea0757 Update dependency androidx.compose:compose-bom to v2026.01.01 (#2913) 2026-02-02 12:33:28 +06:00
Mend Renovate 86344e20d3 Update gradle/actions action to v5.0.1 (#2912) 2026-02-02 12:31:47 +06:00
Mend Renovate e092b4208a Update dependency androidx.work:work-runtime to v2.11.1 (#2914) 2026-02-02 12:31:29 +06:00
Mend Renovate 1bf4eff931 Update paging.version to v3.4.0 (#2916) 2026-02-02 12:28:25 +06:00
Mend Renovate b70edfac58 Update dependency androidx.activity:activity-compose to v1.12.3 (#2917) 2026-02-02 12:27:47 +06:00
Mend Renovate 9c0eebb55c Update markdown to v0.39.2 (#2923) 2026-02-02 12:27:27 +06:00
Mend Renovate f6f1d13add Update dependency io.kotest:kotest-assertions-core to v6.1.2 (#2908) 2026-01-28 07:30:38 +06:00
Mend Renovate 31a9ff261e Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.1 (#2909) 2026-01-28 07:30:23 +06:00
Mend Renovate c0a1203541 Update dependency io.mockk:mockk to v1.14.9 (#2904) 2026-01-27 01:24:06 +06:00
Cuong-Tran 82ffc8efa6 Fix Add Repo input not taking up the full dialog width (#2816) 2026-01-24 19:50:15 +06:00
Mend Renovate d6e17b04c3 Update serialization.version to v1.10.0 (#2879) 2026-01-24 19:48:43 +06:00
Mend Renovate e6c6700351 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.0 (#2892) 2026-01-24 19:42:37 +06:00
Mend Renovate ede2f56bc6 Update dependency io.kotest:kotest-assertions-core to v6.1.1 (#2893) 2026-01-24 19:42:16 +06:00
Mend Renovate 2037ec4500 Update Gradle to v8.14.4 (#2894) 2026-01-24 19:41:59 +06:00
Cuong-Tran 533a578bdb Fix memoization in manga bottom action menus (#2886) 2026-01-24 19:41:18 +06:00
Mend Renovate fa966fcb97 Update GitHub Actions (#2884) 2026-01-24 19:38:36 +06:00
MajorTanya 05d90ea4d6 Reword download index message (#2874)
I'm tired of people thinking the current wording is an error. Improved wordings welcome, this was just my first decent guess.
2026-01-20 14:58:41 +00:00
Mend Renovate 49c4d08b22 Update dependency io.kotest:kotest-assertions-core to v6.1.0 (#2870) 2026-01-20 20:39:01 +06:00
MajorTanyaandDani bbe9aa8561 Add Filters to Updates screen (#2851)
* Add Filters to Updates screen

Behaves basically like the filters in the library:

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

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

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

* Move most filtering logic to SQL

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

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

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

* Add "Hide excluded scanlators" to update filters

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

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

---------

Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com>
2026-01-17 10:43:40 +00:00
Mend Renovate 13975d6f7e Update dependency com.google.firebase:firebase-bom to v34.8.0 (#2856) 2026-01-16 17:06:02 +06:00
Mend Renovate e659e90c26 Update dependency androidx.compose:compose-bom to v2026 (#2853) 2026-01-15 19:50:16 +06:00
Mend Renovate 99e6fa3c06 Update dependency com.materialkolor:material-kolor to v5.0.0-alpha05 (#2849) 2026-01-14 21:35:51 +06:00
Mend Renovate cf13012629 Update markdown to v0.39.1 (#2850) 2026-01-14 21:35:37 +06:00
AntsyLich 93b355a9ef Update donation link [skip ci] 2026-01-12 22:41:10 +06:00
NGB-Was-Taken a4f5a8184c Enable logcat logging on stable and debug builds without enabling verbose logging (#2836) 2026-01-07 22:22:52 +06:00
NGB-Was-Taken 4ce249c1a0 Fix crash when trying to install/update extensions while shizuku isn't running (#2837) 2026-01-07 22:17:53 +06:00
MajorTanya edcf84d902 Fix nullability of MAL authors breaking search (#2834)
One of these days I'll get through a tracker change without
nullability problems...
2026-01-07 19:30:44 +06:00
Constantin PiberandAntsyLich bd5c4d48f9 Add a small increment to chapter number before comparison to fix progress sync issues for Suwayomi (#2675)
Due to a `Float->Double->Float` conversion somewhere inside Mihon, the
tracker sees 2.1 as 2.0999999046325684, which means this filter ignores
the 2.1 chapter (which we just tried to mark as read). This small
epsilon is small enough to never bother any serious usage, but large
enough to ignore any such conversion errors.

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2026-01-07 08:36:37 +00:00
MajorTanya 51b3ab3fd1 Add authors/artists to MAL search results (#2833) 2026-01-07 08:16:56 +00:00
e6f72000ba Translations update from Hosted Weblate (#2806)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

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

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

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

I got to my "11x" improvement because on page of search results has 10
elements, and this change turns 11 (1+10 for results) requests into 1.
2026-01-07 13:59:42 +06:00
Mend Renovate 89c4e3bb39 Update dependency org.junit.jupiter:junit-jupiter to v6.0.2 (#2830) 2026-01-06 18:14:34 +00:00
Mend Renovate 47fe792ddc Update dependency org.jsoup:jsoup to v1.22.1 (#2826) 2026-01-07 00:04:38 +06:00
AntsyLichandCuong-Tran 906d6f3cdb Remember descriptionAnnotator across composition
Closes #2510

Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
2025-12-27 01:43:39 +06:00
AntsyLich e059190fab Cleanup extension screen search query predicate 2025-12-27 01:34:00 +06:00
AntsyLich a39b5a56e8 Switch to M3E ExtendedFloatingActionButton 2025-12-27 01:04:49 +06:00
AntsyLich 3e6afee13b Switch to MaterialExpressiveTheme 2025-12-27 00:50:03 +06:00
AntsyLich 9a11ec8ead Use materilalKolor for monet compat color scheme 2025-12-27 00:31:18 +06:00
Constantin PiberandAntsyLich 1263df9d41 Implement automatic removal of downloads on Suwayomi after reading, configurable via extension settings (#2673)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2025-12-20 12:54:17 +00:00
c96b6ae562 Translations update from Hosted Weblate (#2711)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hasanur Rahman Biplob <hrbiplob10@gmail.com>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: ابْنُ السَدِيمِ <amarlubs2@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
2025-12-20 18:51:10 +06:00
Mend Renovate 23c427cf60 Update dependency androidx.compose:compose-bom to v2025.12.01 (#2651) 2025-12-20 18:34:06 +06:00
Mend Renovate e3260d56f7 Update markdown to v0.39.0 (#2804) 2025-12-20 18:33:50 +06:00
Mend Renovate f37afbcec9 Update aboutlib.version to v13.2.1 (#2803) 2025-12-20 18:33:30 +06:00
MajorTanya 7a1c8a1b61 Fix pre-1970 upload date display in chapter list (#2779)
A user in #2777 was using the ComicInfo.xml Year/Month/Day fields to
indicate date of publication for some American comics, which often
predate the UNIX Epoch of 1970.

They were seeing "N/A" displays because this line of code discarded
date information for any time before Jan 1st, 1970.

The `toRelativeString` extension function used in the other
`relativeDateText` function already accounts for very distant dates
(anything >7 days away turns into full date, not relative, regardless
of setting, though disabling the relative timestamp setting
circumvents this with the same result). Removing this line should not
cause any issues as it is purely a display difference and the use case
of backdating comics to pre-1970 is worth it in my opinion.
2025-12-18 11:26:35 +00:00
Mend Renovate 532b5cf290 Update dependency androidx.activity:activity-compose to v1.12.2 (#2797) 2025-12-18 16:44:50 +06:00
Mend Renovate 3cb1b2e17a Update kotlin monorepo to v2.3.0 (#2794) 2025-12-16 23:10:37 +06:00
Luca AuerandAntsyLich 4c9cfd8da5 Minimize memory usage by reducing in-memory cover cache size (#2266)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2025-12-16 23:08:24 +06:00
Mend Renovate 2ecb70a590 Update GitHub Actions (#2786) 2025-12-13 14:59:27 +06:00
AntsyLich 2e0786f699 Fix reader not saving read duration when changing chapter (#2784) 2025-12-12 18:49:57 +00:00
Mend Renovate e7e4d9b6b3 Update dependency com.google.firebase:firebase-bom to v34.7.0 (#2782) 2025-12-13 00:22:54 +06:00
AntsyLich 5fe7dd9f06 Use AGP provided NDK and Build Tools version 2025-12-13 00:18:48 +06:00
Mend Renovate 4cb05cc738 Update dependency com.android.tools.build:gradle to v8.13.2 (#2780) 2025-12-11 20:56:36 +06:00
AntsyLich 876c3f951b Update tracker icons (#2773) 2025-12-10 15:19:46 +06:00
Mend Renovate 08a61a42e9 Update dependency androidx.activity:activity-compose to v1.12.1 (#2760) 2025-12-07 12:35:09 +06:00
Mend Renovate cadd36ad9a Update dependency io.mockk:mockk to v1.14.7 (#2771) 2025-12-07 12:34:59 +06:00
Mend Renovate 97327555b3 Update GitHub Actions (#2757) 2025-12-07 12:33:47 +06:00
Mend Renovate 5b88f88198 Update softprops/action-gh-release action to v2.5.0 (#2750) 2025-12-01 23:38:05 +06:00
Mend Renovate 556371e1c8 Update dependency io.kotest:kotest-assertions-core to v6.0.7 (#2749) 2025-12-01 23:37:56 +06:00
AntsyLich c222a28bd1 Cleanup BaseOAuthLoginActivity and TrackLoginActivity (#2748) 2025-11-30 12:17:25 +00:00
AntsyLich 7161bc2e82 Fix mass migration not using the same search queries as individual migration (#2736) 2025-11-23 18:05:30 +00:00
NGB-Was-TakenandAntsyLich c3d858a561 Fix shizuku installer not updating installed extensions (#2697)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2025-11-23 08:16:38 +00:00
Mend RenovateandAntsyLich 13552c5ffa Update dependency com.pinterest.ktlint:ktlint-cli to v1.8.0 (#2708)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
2025-11-23 07:55:54 +00:00
Mend Renovate e6ca458e1f Update dependency androidx.activity:activity-compose to v1.12.0 (#2725) 2025-11-23 13:31:50 +06:00
Mend Renovate 05c7df2ed3 Update moko to v0.25.2 (#2723) 2025-11-23 13:29:50 +06:00
Mend Renovate 4a3339a21f Update sqlite to v2.6.2 (#2724) 2025-11-23 13:29:28 +06:00
Mend Renovate 6f497ed031 Update lifecycle.version to v2.10.0 (#2726) 2025-11-23 13:29:06 +06:00
Mend Renovate 8b862c2e46 Update actions/checkout action to v6 (#2729) 2025-11-23 13:28:20 +06:00
Mend Renovate fc2c8c06a9 Update actions/checkout action to v5.0.1 (#2715) 2025-11-19 14:10:08 +06:00
Mend Renovate c17fc6792a Update dependency com.squareup.okio:okio to v3.16.4 (#2716) 2025-11-19 14:09:51 +06:00
Mend Renovate 9e27ae3d2b Update dependency io.kotest:kotest-assertions-core to v6.0.5 (#2717) 2025-11-19 14:09:34 +06:00
Mend Renovate 8b5dd39f1c Update okhttp monorepo to v5.3.2 (#2720) 2025-11-19 14:08:15 +06:00
Mend Renovate 8d81c94679 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.1.0 (#2721) 2025-11-19 14:07:57 +06:00
Mend Renovate e5a693f224 Update dependency com.google.firebase:firebase-bom to v34.6.0 (#2707) 2025-11-17 13:58:37 +06:00
Mend Renovate 7947e1bd6a Update okhttp monorepo to v5.3.1 (#2712) 2025-11-17 13:53:23 +06:00
Mend Renovate cab729c939 Update dependency com.squareup.okio:okio to v3.16.3 (#2709) 2025-11-15 13:27:50 +06:00
Mend Renovate f6a95a7416 Update dependency app.cash.sqldelight:sqlite-3-38-dialect to v2.2.1 (#2703) 2025-11-14 07:17:55 +00:00
32db0a5f4c Translations update from Hosted Weblate (#2687)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/am/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Anderhale <safwanistaken@gmail.com>
Co-authored-by: Ardiansyahset <ardiantozep@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>
2025-11-14 13:10:34 +06:00
NGB-Was-Taken 2ec67ac0c1 Stop tap zones from triggering when scrolling is stopped by tapping (#2680) 2025-11-14 13:10:19 +06:00
Mend Renovate c2bc95191b Update actions/dependency-review-action action to v4.8.2 (#2686)
Update GitHub Actions
2025-11-14 13:09:25 +06:00
Mend Renovate b1b79a63f0 Update sqldelight to v2.2.1 (#2704) 2025-11-14 13:07:51 +06:00
Mend Renovate ec99ab3aef Update dependency com.android.tools.build:gradle to v8.13.1 (#2685) 2025-11-11 14:29:40 +06:00
Mend Renovate 69e5a1aea7 Update softprops/action-gh-release action to v2.4.2 (#2683) 2025-11-11 14:28:42 +06:00
+2 84d620978b Translations update from Hosted Weblate (#2676)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/am/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/da/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/gl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/he/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sah/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sq/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/th/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
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: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com>
Co-authored-by: NGB-Was-Taken <myalternate34@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
2025-11-11 14:27:44 +06:00
AntsyLich 412815af06 Revert "Fix reader tap zones triggering after scrolling was stopped by the user" (#2670) 2025-11-07 13:07:14 +00:00
f7fb68692a Translations update from Hosted Weblate (#2656)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
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: Doministo <doministo@seznam.cz>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>
2025-11-07 19:03:52 +06:00
AntsyLich aa300cb53e Fix extra padding appearing in reader after user interactions (#2669) 2025-11-07 10:00:59 +00:00
Trevor Paley 855eea2ada Improve WebView multi-window UX (#2662)
- Navigation history for lower windows is preserved when a popup is opened
- Back gesture will close a popup window rather than the entire WebView activity when there is no previous page
- The leftmost close button closes the entire activity as before
- When a popup window is shown, a new button appears to close just that window
2025-11-07 15:26:04 +06:00
Mend Renovate f4703ed83a Update dependency androidx.core:core-splashscreen to v1.2.0 (#2661) 2025-11-07 15:22:41 +06:00
NGB-Was-Taken 506d51a007 Fix flaky migration tests (#2663) 2025-11-07 15:21:17 +06:00
NGB-Was-Taken 9f9155121c Upload test report as artifact on failure (#2664) 2025-11-07 15:20:32 +06:00
AntsyLich 282110ef21 Release v0.19.3 2025-11-04 13:05:40 +06:00
AntsyLich ace387f8bf Revert "Update dependency androidx.compose:compose-bom to v2025.10.01 (#2522)"
This reverts commit e8bdf58530.
2025-11-04 13:05:23 +06:00
5e428071c9 Translations update from Hosted Weblate (#2646)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
2025-11-04 13:04:46 +06:00
AntsyLich 0acd80dd95 Fix long strip reader not scrolling on consecutive taps (#2650) 2025-11-04 06:46:48 +00:00
bapeey bdb0ce4779 Fix WebView crash introduced in v0.19.2 (#2649) 2025-11-04 11:43:40 +06:00
Mend Renovate e8bdf58530 Update dependency androidx.compose:compose-bom to v2025.10.01 (#2522) 2025-11-03 10:52:45 +06:00
620 changed files with 74850 additions and 46129 deletions
+1
View File
@@ -29,3 +29,4 @@ ktlint_standard_function-expression-body = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_type-parameter-comment = disabled
ktlint_standard_blank-line-between-when-conditions = disabled
+1
View File
@@ -0,0 +1 @@
21
+1
View File
@@ -0,0 +1 @@
patreon: mihon
+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.2](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.20.0](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.2"
Example: "0.20.0"
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.2](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.20.0](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
+22 -11
View File
@@ -26,38 +26,49 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Dependency Review
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Check code format
run: ./gradlew spotlessCheck
- name: Run unit tests
id: unit_tests
run: ./gradlew testDebugUnitTest
- name: Verify SQLDelight migrations
run: ./gradlew verifySqlDelightMigration
- name: Upload test report
if: steps.unit_tests.outcome == 'failure'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test-report-${{ github.sha }}
path: app/build/reports/tests/testDebugUnitTest
- name: Build app
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Run unit tests
run: ./gradlew testReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Upload mapping
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release
+33 -40
View File
@@ -4,6 +4,9 @@ on:
tags:
- v*
env:
MIHON_GITHUB_RELEASE: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
@@ -29,43 +32,37 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
- name: Build
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
- name: Sign APK
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: '35.0.1'
storeFileBase64: ${{ secrets.SIGNING_KEY }}
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyAlias: ${{ secrets.ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Rename APK
run: |
set -e
mv app/build/outputs/apk/release/app-universal-release-unsigned-signed.apk mihon-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-arm64-v8a-release-unsigned-signed.apk mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-armeabi-v7a-release-unsigned-signed.apk mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-x86-release-unsigned-signed.apk mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-x86_64-release-unsigned-signed.apk mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-universal-release.apk mihon-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-arm64-v8a-release.apk mihon-arm64-v8a-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-armeabi-v7a-release.apk mihon-armeabi-v7a-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-x86-release.apk mihon-x86-${{ needs.get_tag.outputs.tag }}.apk
mv app/build/outputs/apk/release/app-x86_64-release.apk mihon-x86_64-${{ needs.get_tag.outputs.tag }}.apk
- name: Upload APK
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mihon
path: |
@@ -83,41 +80,35 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0
with:
java-version: 17
java-version-file: .github/.java-version
distribution: temurin
- name: Set up Gradle
uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6.2.0
with:
cache-disabled: true
- name: Build
run: ./gradlew assembleFoss -Penable-updater
- name: Sign APK
uses: r0adkll/sign-android-release@f30bdd30588842ac76044ecdbd4b6d0e3e813478
with:
releaseDirectory: app/build/outputs/apk/foss
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
env:
BUILD_TOOLS_VERSION: '35.0.1'
storeFileBase64: ${{ secrets.SIGNING_KEY }}
storePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyAlias: ${{ secrets.ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Rename APK
run: |
set -e
mv app/build/outputs/apk/foss/app-universal-foss-unsigned-signed.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
mv app/build/outputs/apk/foss/app-universal-foss.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
- name: Upload APK
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mihon-foss
path: mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
@@ -130,12 +121,12 @@ jobs:
steps:
- name: Download all artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
merge-multiple: true
- name: Delete all artifacts
uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
with:
failOnError: false
name: |
@@ -143,12 +134,14 @@ jobs:
mihon-foss
- name: Create GitHub Release
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
tag_name: ${{ needs.get_tag.outputs.tag }}
name: Mihon ${{ needs.get_tag.outputs.tag }}
body: |
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you’re upgrading from an earlier version. Consider [donating via Open Collective](https://opencollective.com/mihon/contribute) to help keep Mihon improving!
Consider supporting the development on [Patreon](https://www.patreon.com/mihon/membership) or [OpenCollective](https://opencollective.com/mihon/contribute) to keep Mihon improving!
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you're upgrading from an earlier version.
<!-->
+1
View File
@@ -10,6 +10,7 @@ build
/captures
# Configuration files
keystore.properties
local.properties
# macOS specific files
+126 -1
View File
@@ -12,6 +12,123 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
## [Unreleased]
## [v0.20.0] - 2026-06-27
### Added
- Add support for `tachiyomix` 1.6 extensions ([@AntsyLich](https://github.com/AntsyLich))
- Add support for `tachiyomix` extension index format ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
- Add vertical chapter navigator for long strip mode ([@AntsyLich](https://github.com/AntsyLich)) ([#3377](https://github.com/mihonapp/mihon/pull/3377))
- Add option to open app settings from system settings ([@JFronny](https://github.com/JFronny)) ([#3180](https://github.com/mihonapp/mihon/pull/3180))
### Changed
- Change the term "extension repo" to "extension store" ([@AntsyLich](https://github.com/AntsyLich)) ([#3349](https://github.com/mihonapp/mihon/pull/3349))
- Change the term "Obsolete" to "Orphaned" for extensions ([@AntsyLich](https://github.com/AntsyLich)) ([#3383](https://github.com/mihonapp/mihon/pull/3383))
- Remove text limit of manga notes ([@AntsyLich](https://github.com/AntsyLich)) ([#3410](https://github.com/mihonapp/mihon/pull/3410))
- Tweak Catppuccin color scheme for clarity ([@xenitane](https://github.com/xenitane)) ([#3436](https://github.com/mihonapp/mihon/pull/3436))
- Update app shortcuts to use app icon color scheme ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2835](https://github.com/mihonapp/mihon/pull/2835))
### Improved
- Batch database operations during backup restore for improved performance ([@Lolle2000la](https://github.com/Lolle2000la)) ([#3267](https://github.com/mihonapp/mihon/pull/3267))
### Fixed
- Add missing `outlineVariant` color to Nord theme ([@CompileConnected](https://github.com/CompileConnected)) ([#3184](https://github.com/mihonapp/mihon/pull/3184))
- Continue reading button missing when unread filter is off ([@AntsyLich](https://github.com/AntsyLich)) ([#3382](https://github.com/mihonapp/mihon/pull/3382))
- Fix random `FileNotFoundException` when reading chapters ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3154](https://github.com/mihonapp/mihon/pull/3154))
## [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))
- Display author & artist name in MAL search results ([@MajorTanya](https://github.com/MajorTanya)) ([#2833](https://github.com/mihonapp/mihon/pull/2833))
- Add filter options to Updates tab ([@MajorTanya](https://github.com/MajorTanya)) ([#2851](https://github.com/mihonapp/mihon/pull/2851))
- Add bookmarked chapters to chapter download options ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#2891](https://github.com/mihonapp/mihon/pull/2891))
- Add `src:` prefix to search the library by source ID ([@MajorTanya](https://github.com/MajorTanya)) ([#2927](https://github.com/mihonapp/mihon/pull/2927))
- Add `src:local` as a way to search for Local Source entries ([@MajorTanya](https://github.com/MajorTanya)) ([#2928](https://github.com/mihonapp/mihon/pull/2928))
### Improved
- Minimize memory usage by reducing in-memory cover cache size ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2266](https://github.com/mihonapp/mihon/pull/2266))
- 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)) ([#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))
- Add a small increment to chapter number before comparison to fix progress sync issues for Suwayomi ([@cpiber](https://github.com/cpiber)) ([#2657](https://github.com/mihonapp/mihon/pull/2675))
- Add all pages of adjacent chapters in the UI instead of only the first or last three ([@AntsyLich](https://github.com/AntsyLich)) ([#2995](https://github.com/mihonapp/mihon/pull/2995))
- Going back now first clears search query on browse extension tab ([@cuong-tran](https://github.com/cuong-tran)) ([#2906](https://github.com/mihonapp/mihon/pull/2906))
- Automatic library updates now run even when connected to a VPN ([@AntsyLich](https://github.com/AntsyLich)) ([#2773](https://github.com/mihonapp/mihon/pull/2773))
### Fixed
- Fix reader tap zones triggering after scrolling is stopped by tapping ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2680](https://github.com/mihonapp/mihon/pull/2680))
- Fix shizuku installer not updating installed extensions ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2697](https://github.com/mihonapp/mihon/pull/2697))
- Fix mass migration not using the same search queries as individual migration ([@AntsyLich](https://github.com/AntsyLich)) ([#2736](https://github.com/mihonapp/mihon/pull/2736))
- Fix reader not saving read duration when changing chapter ([@AntsyLich](https://github.com/AntsyLich), [@KotlinHero](https://github.com/KotlinHero)) ([#2784](https://github.com/mihonapp/mihon/pull/2784))
- Fix pre-1970 upload date display in chapter list ([@MajorTanya](https://github.com/MajorTanya)) ([#2779](https://github.com/mihonapp/mihon/pull/2779))
- Fix crash when trying to install/update extensions while shizuku is not running ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#2837](https://github.com/mihonapp/mihon/pull/2837))
- Fix Add Repo input not taking up the full dialog width ([@cuong-tran](https://github.com/cuong-tran)) ([#2816](https://github.com/mihonapp/mihon/pull/2816))
- 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))
### 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))
## [v0.19.3] - 2025-11-07
### Improved
- Improved various aspects of the WebView multi window support ([@TheUnlocked](https://github.com/TheUnlocked)) ([#2662](https://github.com/mihonapp/mihon/pull/2662))
### Removed
- Revert "Fix reader tap zones triggering after scrolling was stopped by the user" due to introduction of regression ([@AntsyLich](https://github.com/AntsyLich)) ([#2670](https://github.com/mihonapp/mihon/pull/2670))
### Fixed
- Fix WebView crash introduced in 0.19.2 ([@bapeey](https://github.com/bapeey)) ([#2649](https://github.com/mihonapp/mihon/pull/2649))
- Fix extra padding appearing in reader after user interactions ([@AntsyLich](https://github.com/AntsyLich)) ([#2669](https://github.com/mihonapp/mihon/pull/2669))
- Fix long strip reader not scrolling on consecutive taps ([@AntsyLich](https://github.com/AntsyLich)) ([#2670](https://github.com/mihonapp/mihon/pull/2670))
## [v0.19.2] - 2025-11-02
### Added
- Advanced setting to limit download filenames to ASCII characters. This is provided only as a workaround for OSes that do not properly handle standard Unicode filenames. This setting is generally not recommended and should only be used as a last resort ([@raxod502](https://github.com/radian-software)) ([#2305](https://github.com/mihonapp/mihon/pull/2305))
@@ -433,7 +550,15 @@ 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.2...main
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.20.0...main
[v0.20.0]: https://github.com/mihonapp/mihon/compare/v0.19.9...v0.20.0
[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
[v0.19.1]: https://github.com/mihonapp/mihon/compare/v0.19.0...v0.19.1
[v0.19.0]: https://github.com/mihonapp/mihon/compare/v0.18.0...v0.19.0
+124 -81
View File
@@ -1,14 +1,20 @@
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
import java.io.FileInputStream
import java.util.Properties
import kotlin.io.encoding.Base64
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.androidx.baselineProfile)
alias(libs.plugins.kotlin.serialization)
}
if (Config.includeTelemetry) {
@@ -18,7 +24,7 @@ if (Config.includeTelemetry) {
}
}
shortcutHelper.setFilePath("./shortcuts.xml")
val keystorePropertiesFile = rootProject.file("keystore.properties")
android {
namespace = "eu.kanade.tachiyomi"
@@ -26,31 +32,62 @@ android {
defaultConfig {
applicationId = "app.mihon"
versionCode = 15
versionName = "0.19.2"
versionCode = 24
versionName = "0.20.0"
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}")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
if (System.getenv("MIHON_GITHUB_RELEASE").toBoolean()) {
val tempStoreFile = file(System.getenv("RUNNER_TEMP")).resolve("antsy.keystore")
val storeFileBytes = System.getenv("storeFileBase64").let(Base64::decode)
tempStoreFile.outputStream().use { it.write(storeFileBytes) }
signingConfigs {
named("debug") {
storeFile = tempStoreFile
storePassword = System.getenv("storePassword")
keyAlias = System.getenv("keyAlias")
keyPassword = System.getenv("keyPassword")
}
}
} else if (keystorePropertiesFile.exists()) {
val keystoreProperties = FileInputStream(keystorePropertiesFile).use { Properties().apply { load(it) } }
signingConfigs {
named("debug") {
storeFile = file(keystoreProperties.getProperty("storeFile"))
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
}
buildTypes {
val debug by getting {
val debug = getByName("debug") {
applicationIdSuffix = ".dev"
versionNameSuffix = "-${getCommitCount()}"
versionNameSuffix = "-${getLatestCommitCount()}"
isPseudoLocalesEnabled = true
}
val release by getting {
val release = getByName("release") {
isMinifyEnabled = Config.enableCodeShrink
isShrinkResources = Config.enableCodeShrink
signingConfig = debug.signingConfig
isProfileable = true
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)
@@ -68,29 +105,24 @@ android {
applicationIdSuffix = ".debug"
versionNameSuffix = debug.versionNameSuffix
signingConfig = debug.signingConfig
matchingFallbacks.addAll(commonMatchingFallbacks)
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLastCommitTime = false)}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime(useLatestCommitTime = false)}\"")
}
create("benchmark") {
initWith(release)
isDebuggable = false
isProfileable = true
versionNameSuffix = "-benchmark"
applicationIdSuffix = ".benchmark"
signingConfig = debug.signingConfig
matchingFallbacks.addAll(commonMatchingFallbacks)
}
}
sourceSets {
getByName("preview").res.srcDirs("src/debug/res")
getByName("benchmark").res.srcDirs("src/debug/res")
getByName("preview").res.directories.add("src/debug/res")
getByName("benchmark").res.directories.add("src/debug/res")
}
splits {
@@ -139,10 +171,6 @@ android {
viewBinding = true
buildConfig = true
aidl = true
// Disable some unused things
renderScript = false
shaders = false
}
lint {
@@ -159,6 +187,7 @@ kotlin {
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
"-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi",
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
@@ -169,7 +198,14 @@ kotlin {
}
}
baselineProfile {
baselineProfileOutputDir = "baselineProfiles"
mergeIntoMain = true
}
dependencies {
baselineProfile(projects.baselineProfile)
implementation(projects.i18n)
implementation(projects.core.archive)
implementation(projects.core.common)
@@ -183,93 +219,93 @@ 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(platform(kotlinx.coroutines.bom))
implementation(kotlinx.bundles.coroutines)
implementation(libs.bundles.kotlinx.coroutines)
implementation(libs.sqldelight.async)
// 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)
// Logging
implementation(libs.logcat)
@@ -285,22 +321,29 @@ 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
it.packaging.resources.excludes.add("META-INF/*.version")
}
}
buildscript {
dependencies {
classpath(kotlinx.gradle)
}
}
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="accent_blue">#7EBBED</color>
<color name="icon_background">#2E3943</color>
</resources>
+17 -2
View File
@@ -60,8 +60,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep link to add repos -->
<intent-filter android:label="@string/action_add_repo">
<!-- Legacy deeplink to add extension store -->
<intent-filter android:label="@string/action_addExtensionStore">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -71,6 +71,17 @@
<data android:host="add-repo" />
</intent-filter>
<!-- Deeplink to add extension store -->
<intent-filter android:label="@string/action_addExtensionStore">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mihon" />
<data android:host="extension-store" />
</intent-filter>
<!-- Open backup files -->
<intent-filter android:label="@string/pref_restore_backup">
<action android:name="android.intent.action.VIEW" />
@@ -130,6 +141,10 @@
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/app_settings">
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -25,17 +25,17 @@ import eu.kanade.domain.track.interactor.AddTracks
import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
import eu.kanade.domain.track.interactor.TrackChapter
import mihon.data.repository.ExtensionRepoRepositoryImpl
import mihon.data.extension.repository.ExtensionStoreRepositoryImpl
import mihon.data.extension.service.ExtensionStoreService
import mihon.domain.chapter.interactor.FilterChaptersForDownload
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extensionrepo.service.ExtensionRepoService
import mihon.domain.extension.interactor.AddExtensionStore
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
import mihon.domain.extension.interactor.GetExtensionStores
import mihon.domain.extension.interactor.RemoveExtensionStore
import mihon.domain.extension.interactor.UpdateExtensionStores
import mihon.domain.extension.repository.ExtensionStoreRepository
import mihon.domain.migration.usecases.MigrateMangaUseCase
import mihon.domain.source.interactor.UpdateMangaFromRemote
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
@@ -57,6 +57,7 @@ import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.interactor.SetSortModeForCategory
import tachiyomi.domain.category.interactor.UpdateCategory
import tachiyomi.domain.category.repository.CategoryRepository
import tachiyomi.domain.chapter.interactor.GetBookmarkedChaptersByMangaId
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
@@ -157,6 +158,7 @@ class DomainModule : InjektModule {
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { GetChapter(get()) }
addFactory { GetChaptersByMangaId(get()) }
addFactory { GetBookmarkedChaptersByMangaId(get()) }
addFactory { GetChapterByUrlAndMangaId(get()) }
addFactory { UpdateChapter(get()) }
addFactory { SetReadStatus(get(), get(), get(), get()) }
@@ -193,15 +195,17 @@ class DomainModule : InjektModule {
addFactory { ToggleSourcePin(get()) }
addFactory { TrustExtension(get(), get()) }
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
addFactory { ExtensionRepoService(get(), get()) }
addFactory { GetExtensionRepo(get()) }
addFactory { GetExtensionRepoCount(get()) }
addFactory { CreateExtensionRepo(get(), get()) }
addFactory { DeleteExtensionRepo(get()) }
addFactory { ReplaceExtensionRepo(get()) }
addFactory { UpdateExtensionRepo(get(), get()) }
addSingletonFactory { ExtensionStoreService(get(), get(), get()) }
addSingletonFactory<ExtensionStoreRepository> { ExtensionStoreRepositoryImpl(get(), get()) }
addFactory { AddExtensionStore(get()) }
addFactory { GetExtensionStoreCountAsFlow(get()) }
addFactory { GetExtensionStores(get()) }
addFactory { RemoveExtensionStore(get()) }
addFactory { UpdateExtensionStores(get()) }
addFactory { ToggleIncognito(get()) }
addFactory { GetIncognitoState(get(), get(), get()) }
addFactory { UpdateMangaFromRemote(get(), get(), get(), get(), get(), get(), get()) }
}
}
@@ -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) ->
@@ -89,6 +89,7 @@ class SyncChaptersWithSource(
// Update metadata from source if necessary.
if (source is HttpSource) {
val sChapter = chapter.toSChapter()
@Suppress("DEPRECATION")
source.prepareNewChapter(sChapter, manga.toSManga())
chapter = chapter.copyFromSChapter(sChapter)
}
@@ -171,7 +172,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
@@ -13,6 +13,7 @@ fun Chapter.toSChapter(): SChapter {
it.date_upload = dateUpload
it.chapter_number = chapterNumber.toFloat()
it.scanlator = scanlator
it.memo = memo
}
}
@@ -23,6 +24,7 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
dateUpload = sChapter.date_upload,
chapterNumber = sChapter.chapter_number.toDouble(),
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
memo = sChapter.memo,
)
}
@@ -39,4 +41,5 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
it.date_upload = dateUpload
it.chapter_number = chapterNumber.toFloat()
it.source_order = sourceOrder.toInt()
it.memo = memo
}
@@ -12,16 +12,12 @@ class GetExtensionLanguages(
) {
fun subscribe(): Flow<List<String>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
extensionManager.availableExtensionsFlow,
) { enabledLanguage, availableExtensions ->
availableExtensions
.flatMap { ext ->
if (ext.sources.isEmpty()) {
listOf(ext.lang)
} else {
ext.sources.map { it.lang }
}
ext.sources.map { it.lang }
}
.distinct()
.sortedWith(
@@ -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,
@@ -39,9 +39,6 @@ class GetExtensionsByType(
(showNsfwSources || !extension.isNsfw)
}
.flatMap { ext ->
if (ext.sources.isEmpty()) {
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
}
ext.sources.filter { it.lang in enabledLanguages }
.map {
ext.copy(
@@ -3,22 +3,22 @@ package eu.kanade.domain.extension.interactor
import android.content.pm.PackageInfo
import androidx.core.content.pm.PackageInfoCompat
import eu.kanade.domain.source.service.SourcePreferences
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extension.repository.ExtensionStoreRepository
import tachiyomi.core.common.preference.getAndSet
class TrustExtension(
private val extensionRepoRepository: ExtensionRepoRepository,
private val repository: ExtensionStoreRepository,
private val preferences: SourcePreferences,
) {
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
val trustedFingerprints = repository.getAll().map { it.signingKey }.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()
}
}
@@ -1,24 +1,26 @@
package eu.kanade.domain.manga.interactor
import app.cash.sqldelight.async.coroutines.awaitAsList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.Database
import tachiyomi.data.subscribeToList
class GetExcludedScanlators(
private val handler: DatabaseHandler,
private val database: Database,
) {
suspend fun await(mangaId: Long): Set<String> {
return handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
return database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.awaitAsList()
.toSet()
}
fun subscribe(mangaId: Long): Flow<Set<String>> {
return handler.subscribeToList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}
return database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.subscribeToList()
.map { it.toSet() }
}
}
@@ -1,22 +1,24 @@
package eu.kanade.domain.manga.interactor
import tachiyomi.data.DatabaseHandler
import app.cash.sqldelight.async.coroutines.awaitAsList
import tachiyomi.data.Database
class SetExcludedScanlators(
private val handler: DatabaseHandler,
private val database: Database,
) {
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
handler.await(inTransaction = true) {
val currentExcluded = handler.awaitList {
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
}.toSet()
database.transaction {
val currentExcluded = database.excluded_scanlatorsQueries
.getExcludedScanlatorsByMangaId(mangaId)
.awaitAsList()
.toSet()
val toAdd = excludedScanlators.minus(currentExcluded)
for (scanlator in toAdd) {
excluded_scanlatorsQueries.insert(mangaId, scanlator)
database.excluded_scanlatorsQueries.insert(mangaId, scanlator)
}
val toRemove = currentExcluded.minus(excludedScanlators)
excluded_scanlatorsQueries.remove(mangaId, toRemove)
database.excluded_scanlatorsQueries.remove(mangaId, toRemove)
}
}
}
@@ -1,17 +1,9 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant
import java.time.ZonedDateTime
@@ -28,67 +20,6 @@ class UpdateManga(
return mangaRepository.updateAll(mangaUpdates)
}
suspend fun awaitUpdateFromSource(
localManga: Manga,
remoteManga: SManga,
manualFetch: Boolean,
coverCache: CoverCache = Injekt.get(),
libraryPreferences: LibraryPreferences = Injekt.get(),
downloadManager: DownloadManager = Injekt.get(),
): Boolean {
val remoteTitle = try {
remoteManga.title
} catch (_: UninitializedPropertyAccessException) {
""
}
// 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())) {
remoteTitle
} else {
null
}
val coverLastModified =
when {
// Never refresh covers if the url is empty to avoid "losing" existing covers
remoteManga.thumbnail_url.isNullOrEmpty() -> null
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
localManga.isLocal() -> Instant.now().toEpochMilli()
localManga.hasCustomCover(coverCache) -> {
coverCache.deleteFromCache(localManga, false)
null
}
else -> {
coverCache.deleteFromCache(localManga, false)
Instant.now().toEpochMilli()
}
}
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
val success = mangaRepository.update(
MangaUpdate(
id = localManga.id,
title = title,
coverLastModified = coverLastModified,
author = remoteManga.author,
artist = remoteManga.artist,
description = remoteManga.description,
genre = remoteManga.getGenres(),
thumbnailUrl = thumbnailUrl,
status = remoteManga.status.toLong(),
updateStrategy = remoteManga.update_strategy,
initialized = true,
),
)
if (success && title != null) {
downloadManager.renameManga(localManga, title)
}
return success
}
suspend fun awaitUpdateFetchInterval(
manga: Manga,
dateTime: ZonedDateTime = ZonedDateTime.now(),
@@ -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
@@ -45,6 +45,7 @@ fun Manga.toSManga(): SManga = SManga.create().also {
it.status = status.toInt()
it.thumbnail_url = thumbnailUrl
it.initialized = initialized
it.memo = memo
}
fun Manga.copyFrom(other: SManga): Manga {
@@ -66,6 +67,7 @@ fun Manga.copyFrom(other: SManga): Manga {
status = other.status.toLong(),
updateStrategy = other.update_strategy,
initialized = other.initialized && initialized,
memo = other.memo,
)
}
@@ -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) {
@@ -22,7 +22,6 @@ import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.source.Source
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.StateFlow
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.library.model.LibraryDisplayMode
@@ -86,7 +85,7 @@ fun BrowseSourceContent(
else -> stringResource(MR.strings.no_results_found)
},
actions = if (source is LocalSource) {
persistentListOf(
listOf(
EmptyScreenAction(
stringRes = MR.strings.local_source_help_guide,
icon = Icons.AutoMirrored.Outlined.HelpOutline,
@@ -94,7 +93,7 @@ fun BrowseSourceContent(
),
)
} else {
persistentListOf(
listOf(
EmptyScreenAction(
stringRes = MR.strings.action_retry,
icon = Icons.Outlined.Refresh,
@@ -56,8 +56,6 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
@@ -80,12 +78,12 @@ fun ExtensionDetailsScreen(
val uriHandler = LocalUriHandler.current
val url = remember(state.extension) {
val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
regex.find(state.extension?.repoUrl.orEmpty())
regex.find(state.extension?.store?.indexUrl.orEmpty())
?.let {
val (user, repo) = it.destructured
"https://github.com/$user/$repo"
}
?: state.extension?.repoUrl
?: state.extension?.store?.indexUrl
}
Scaffold(
@@ -95,37 +93,35 @@ fun ExtensionDetailsScreen(
navigateUp = navigateUp,
actions = {
AppBarActions(
actions = persistentListOf<AppBar.AppBarAction>().builder()
.apply {
if (url != null) {
add(
AppBar.Action(
title = stringResource(MR.strings.action_open_repo),
icon = Icons.AutoMirrored.Outlined.Launch,
onClick = {
uriHandler.openUri(url)
},
),
)
}
addAll(
listOf(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_enable_all),
onClick = onClickEnableAll,
),
AppBar.OverflowAction(
title = stringResource(MR.strings.action_disable_all),
onClick = onClickDisableAll,
),
AppBar.OverflowAction(
title = stringResource(MR.strings.pref_clear_cookies),
onClick = onClickClearCookies,
),
actions = buildList {
if (url != null) {
add(
AppBar.Action(
title = stringResource(MR.strings.action_open_repo),
icon = Icons.AutoMirrored.Outlined.Launch,
onClick = {
uriHandler.openUri(url)
},
),
)
}
.build(),
addAll(
listOf(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_enable_all),
onClick = onClickEnableAll,
),
AppBar.OverflowAction(
title = stringResource(MR.strings.action_disable_all),
onClick = onClickDisableAll,
),
AppBar.OverflowAction(
title = stringResource(MR.strings.pref_clear_cookies),
onClick = onClickClearCookies,
),
),
)
},
)
},
scrollBehavior = scrollBehavior,
@@ -157,7 +153,7 @@ fun ExtensionDetailsScreen(
private fun ExtensionDetails(
contentPadding: PaddingValues,
extension: Extension.Installed,
sources: ImmutableList<ExtensionSourceItem>,
sources: List<ExtensionSourceItem>,
incognitoMode: Boolean,
onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickUninstall: () -> Unit,
@@ -248,14 +244,17 @@ private fun DetailsHeader(
if (extension is Extension.Installed) {
append("\n\n")
append(
appendLine(
"""
Update available: ${extension.hasUpdate}
Obsolete: ${extension.isObsolete}
Orphaned: ${extension.isObsolete}
Shared: ${extension.isShared}
Repository: ${extension.repoUrl}
""".trimIndent(),
)
val store = extension.store
if (store != null) {
append("Repository: ${store.indexUrl}")
}
}
}
context.copyToClipboard("Extension Debug information", extDebugInfo)
@@ -47,7 +47,7 @@ import eu.kanade.presentation.browse.components.BaseBrowseItem
import eu.kanade.presentation.browse.components.ExtensionIcon
import eu.kanade.presentation.components.WarningBanner
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
import eu.kanade.presentation.util.animateItemFastScroll
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
import eu.kanade.tachiyomi.extension.model.Extension
@@ -56,7 +56,6 @@ import eu.kanade.tachiyomi.ui.browse.extension.ExtensionUiModel
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.PullRefresh
@@ -104,11 +103,11 @@ fun ExtensionScreen(
EmptyScreen(
stringRes = msg,
modifier = Modifier.padding(contentPadding),
actions = persistentListOf(
actions = listOf(
EmptyScreenAction(
stringRes = MR.strings.label_extension_repos,
stringRes = MR.strings.extensionStores,
icon = Icons.Outlined.Settings,
onClick = { navigator.push(ExtensionReposScreen()) },
onClick = { navigator.push(ExtensionStoresScreen()) },
),
),
)
@@ -10,7 +10,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
@@ -27,7 +27,7 @@ fun GlobalSearchScreen(
onChangeSearchFilter: (SourceFilter) -> Unit,
onToggleResults: () -> Unit,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickSource: (Source) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
@@ -62,10 +62,10 @@ fun GlobalSearchScreen(
@Composable
internal fun GlobalSearchContent(
items: Map<CatalogueSource, SearchItemResult>,
items: Map<Source, SearchItemResult>,
contentPadding: PaddingValues,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickSource: (Source) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
fromSourceId: Long? = null,
@@ -3,7 +3,7 @@ package eu.kanade.presentation.browse
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
import tachiyomi.domain.manga.model.Manga
@@ -19,7 +19,7 @@ fun MigrateSearchScreen(
onChangeSearchFilter: (SourceFilter) -> Unit,
onToggleResults: () -> Unit,
getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit,
onClickSource: (Source) -> Unit,
onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit,
) {
@@ -26,7 +26,6 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
import eu.kanade.presentation.browse.components.SourceIcon
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.ImmutableList
import tachiyomi.domain.source.model.Source
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.Badge
@@ -76,7 +75,7 @@ fun MigrateSourceScreen(
@Composable
private fun MigrateSourceList(
list: ImmutableList<Pair<Source, Long>>,
list: List<Pair<Source, Long>>,
contentPadding: PaddingValues,
onClickItem: (Source) -> Unit,
onLongClickItem: (Source) -> Unit,
@@ -18,7 +18,6 @@ import eu.kanade.presentation.components.RadioMenuItem
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@@ -54,44 +53,42 @@ fun BrowseSourceToolbar(
onClickCloseSearch = navigateUp,
actions = {
AppBarActions(
actions = persistentListOf<AppBar.AppBarAction>().builder()
.apply {
actions = buildList {
add(
AppBar.Action(
title = stringResource(MR.strings.action_display_mode),
icon = if (displayMode == LibraryDisplayMode.List) {
Icons.AutoMirrored.Filled.ViewList
} else {
Icons.Filled.ViewModule
},
onClick = { selectingDisplayMode = true },
),
)
if (isLocalSource) {
add(
AppBar.Action(
title = stringResource(MR.strings.action_display_mode),
icon = if (displayMode == LibraryDisplayMode.List) {
Icons.AutoMirrored.Filled.ViewList
} else {
Icons.Filled.ViewModule
},
onClick = { selectingDisplayMode = true },
AppBar.OverflowAction(
title = stringResource(MR.strings.label_help),
onClick = onHelpClick,
),
)
} else {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_web_view),
onClick = onWebViewClick,
),
)
if (isLocalSource) {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.label_help),
onClick = onHelpClick,
),
)
} else {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_web_view),
onClick = onWebViewClick,
),
)
}
if (isConfigurableSource) {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_settings),
onClick = onSettingsClick,
),
)
}
}
.build(),
if (isConfigurableSource) {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_settings),
onClick = onSettingsClick,
),
)
}
},
)
DropdownMenu(
@@ -27,8 +27,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import eu.kanade.core.preference.asToggleableState
import eu.kanade.presentation.category.visualName
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import tachiyomi.core.common.preference.CheckboxState
import tachiyomi.domain.category.model.Category
@@ -41,7 +39,7 @@ import kotlin.time.Duration.Companion.seconds
fun CategoryCreateDialog(
onDismissRequest: () -> Unit,
onCreate: (String) -> Unit,
categories: ImmutableList<String>,
categories: List<String>,
) {
var name by remember { mutableStateOf("") }
@@ -103,7 +101,7 @@ fun CategoryCreateDialog(
fun CategoryRenameDialog(
onDismissRequest: () -> Unit,
onRename: (String) -> Unit,
categories: ImmutableList<String>,
categories: List<String>,
category: String,
) {
var name by remember { mutableStateOf(category) }
@@ -195,7 +193,7 @@ fun CategoryDeleteDialog(
@Composable
fun ChangeCategoryDialog(
initialSelection: ImmutableList<CheckboxState<Category>>,
initialSelection: List<CheckboxState<Category>>,
onDismissRequest: () -> Unit,
onEditCategories: () -> Unit,
onConfirm: (List<Long>, List<Long>) -> Unit,
@@ -267,7 +265,7 @@ fun ChangeCategoryDialog(
if (index != -1) {
val mutableList = selection.toMutableList()
mutableList[index] = it.next()
selection = mutableList.toList().toImmutableList()
selection = mutableList.toList()
}
}
Row(
@@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.shouldExpandFAB
@@ -18,7 +18,7 @@ fun CategoryFloatingActionButton(
onCreate: () -> Unit,
modifier: Modifier = Modifier,
) {
ExtendedFloatingActionButton(
SmallExtendedFloatingActionButton(
text = { Text(text = stringResource(MR.strings.action_add)) },
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
onClick = onCreate,
@@ -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,
) {
@@ -21,8 +21,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
@@ -50,7 +51,6 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.collections.immutable.ImmutableList
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clearFocusOnSoftKeyboardHide
@@ -58,8 +58,6 @@ import tachiyomi.presentation.core.util.runOnEnterKeyPressed
import tachiyomi.presentation.core.util.secondaryItemAlpha
import tachiyomi.presentation.core.util.showSoftKeyboard
const val SEARCH_DEBOUNCE_MILLIS = 250L
@Composable
fun AppBar(
title: String?,
@@ -189,13 +187,13 @@ fun AppBarTitle(
@Composable
fun AppBarActions(
actions: ImmutableList<AppBar.AppBarAction>,
actions: List<AppBar.AppBarAction>,
) {
var showMenu by remember { mutableStateOf(false) }
actions.filterIsInstance<AppBar.Action>().map {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(it.title)
@@ -220,7 +218,7 @@ fun AppBarActions(
val overflowActions = actions.filterIsInstance<AppBar.OverflowAction>()
if (overflowActions.isNotEmpty()) {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(stringResource(MR.strings.action_menu_overflow_description))
@@ -349,7 +347,7 @@ fun SearchToolbar(
// Don't show search action
} else if (searchQuery == null) {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(stringResource(MR.strings.action_search))
@@ -369,7 +367,7 @@ fun SearchToolbar(
}
} else if (searchQuery.isNotEmpty()) {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(stringResource(MR.strings.action_reset))
@@ -22,7 +22,7 @@ fun relativeDateText(
Instant.ofEpochMilli(dateEpochMillis),
ZoneId.systemDefault(),
)
.takeIf { dateEpochMillis > 0L },
.takeIf { dateEpochMillis != 0L },
)
}
@@ -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,
@@ -1,24 +1,22 @@
package eu.kanade.presentation.components
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import eu.kanade.presentation.manga.DownloadAction
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
@Composable
fun DownloadDropdownMenu(
modifier: Modifier = Modifier,
expanded: Boolean,
onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
offset: DpOffset? = null,
modifier: Modifier = Modifier,
) {
if (offset != null) {
DropdownMenu(
@@ -49,16 +47,17 @@ fun DownloadDropdownMenu(
}
@Composable
private fun ColumnScope.DownloadDropdownMenuItems(
private fun DownloadDropdownMenuItems(
onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
) {
val options = persistentListOf(
val options = listOf(
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
DownloadAction.NEXT_5_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 5, 5),
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
DownloadAction.NEXT_25_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 25, 25),
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
DownloadAction.BOOKMARKED_CHAPTERS to stringResource(MR.strings.download_bookmarked),
)
options.map { (downloadAction, string) ->
@@ -7,7 +7,6 @@ import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.PreviewLightDark
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.EmptyScreenAction
@@ -31,7 +30,7 @@ private fun WithActionPreview() {
Surface {
EmptyScreen(
stringRes = MR.strings.empty_screen,
actions = persistentListOf(
actions = listOf(
EmptyScreenAction(
stringRes = MR.strings.action_retry,
icon = Icons.Outlined.Refresh,
@@ -27,7 +27,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastForEachIndexed
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.launch
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.TabText
@@ -41,7 +40,7 @@ object TabbedDialogPaddings {
@Composable
fun TabbedDialog(
onDismissRequest: () -> Unit,
tabTitles: ImmutableList<String>,
tabTitles: List<String>,
modifier: Modifier = Modifier,
tabOverflowMenuContent: (@Composable ColumnScope.(() -> Unit) -> Unit)? = null,
pagerState: PagerState = rememberPagerState { tabTitles.size },
@@ -22,8 +22,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.zIndex
import dev.icerock.moko.resources.StringResource
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.TabText
@@ -32,7 +30,7 @@ import tachiyomi.presentation.core.i18n.stringResource
@Composable
fun TabbedScreen(
titleRes: StringResource,
tabs: ImmutableList<TabContent>,
tabs: List<TabContent>,
state: PagerState = rememberPagerState { tabs.size },
searchQuery: String? = null,
onChangeSearchQuery: (String?) -> Unit = {},
@@ -94,6 +92,6 @@ data class TabContent(
val titleRes: StringResource,
val badgeNumber: Int? = null,
val searchEnabled: Boolean = false,
val actions: ImmutableList<AppBar.AppBarAction> = persistentListOf(),
val actions: List<AppBar.AppBarAction> = listOf(),
val content: @Composable (contentPadding: PaddingValues, snackbarHostState: SnackbarHostState) -> Unit,
)
@@ -20,7 +20,6 @@ import eu.kanade.presentation.history.components.HistoryItem
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.presentation.util.animateItemFastScroll
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
@@ -49,7 +48,7 @@ fun HistoryScreen(
onChangeSearchQuery = onSearchQueryChange,
actions = {
AppBarActions(
persistentListOf(
listOf(
AppBar.Action(
title = stringResource(MR.strings.pref_clear_history),
icon = Icons.Outlined.DeleteSweep,
@@ -21,7 +21,6 @@ import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.ui.library.LibrarySettingsScreenModel
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.common.preference.TriState
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode
@@ -47,7 +46,7 @@ fun LibrarySettingsDialog(
) {
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = persistentListOf(
tabTitles = listOf(
stringResource(MR.strings.action_filter),
stringResource(MR.strings.action_sort),
stringResource(MR.strings.action_display),
@@ -78,9 +77,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 +91,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 +117,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 +228,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 +243,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 +267,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,
)
}
@@ -10,7 +10,7 @@ import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import tachiyomi.presentation.core.components.Badge
@Composable
internal fun DownloadsBadge(count: Long) {
internal fun DownloadsBadge(count: Int) {
if (count > 0) {
Badge(
text = "$count",
@@ -44,13 +44,13 @@ internal fun LibraryComfortableGrid(
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount)
UnreadBadge(count = libraryItem.unreadCount)
DownloadsBadge(count = libraryItem.badges.downloadCount)
UnreadBadge(count = libraryItem.badges.unreadCount)
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
isLocal = libraryItem.badges.isLocal,
sourceLanguage = libraryItem.badges.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
@@ -45,13 +45,13 @@ internal fun LibraryCompactGrid(
lastModified = manga.coverLastModified,
),
coverBadgeStart = {
DownloadsBadge(count = libraryItem.downloadCount)
UnreadBadge(count = libraryItem.unreadCount)
DownloadsBadge(count = libraryItem.badges.downloadCount)
UnreadBadge(count = libraryItem.badges.unreadCount)
},
coverBadgeEnd = {
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
isLocal = libraryItem.badges.isLocal,
sourceLanguage = libraryItem.badges.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
@@ -54,11 +54,11 @@ internal fun LibraryList(
lastModified = manga.coverLastModified,
),
badge = {
DownloadsBadge(count = libraryItem.downloadCount)
UnreadBadge(count = libraryItem.unreadCount)
DownloadsBadge(count = libraryItem.badges.downloadCount)
UnreadBadge(count = libraryItem.badges.unreadCount)
LanguageBadge(
isLocal = libraryItem.isLocal,
sourceLanguage = libraryItem.sourceLanguage,
isLocal = libraryItem.badges.isLocal,
sourceLanguage = libraryItem.badges.sourceLanguage,
)
},
onLongClick = { onLongClick(libraryItem.libraryManga) },
@@ -19,7 +19,6 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.SearchToolbar
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.Pill
import tachiyomi.presentation.core.i18n.stringResource
@@ -96,7 +95,7 @@ private fun LibraryRegularToolbar(
actions = {
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
AppBarActions(
persistentListOf(
listOf(
AppBar.Action(
title = stringResource(MR.strings.action_filter),
icon = Icons.Outlined.FilterList,
@@ -133,7 +132,7 @@ private fun LibrarySelectionToolbar(
titleContent = { Text(text = "$selectedCount") },
actions = {
AppBarActions(
persistentListOf(
listOf(
AppBar.Action(
title = stringResource(MR.strings.action_select_all),
icon = Icons.Outlined.SelectAll,
@@ -31,7 +31,6 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.downloadedFilter
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.common.preference.TriState
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
@@ -66,11 +65,11 @@ fun ChapterSettingsDialog(
)
}
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() }
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly.get() }
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = persistentListOf(
tabTitles = listOf(
stringResource(MR.strings.action_filter),
stringResource(MR.strings.action_sort),
stringResource(MR.strings.action_display),
@@ -1,10 +1,7 @@
package eu.kanade.presentation.manga
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -27,9 +24,11 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.SmallExtendedFloatingActionButton
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.animateFloatingActionButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -71,7 +70,6 @@ import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.TwoPanelBox
import tachiyomi.presentation.core.components.VerticalFastScroller
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.PullRefresh
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
@@ -124,7 +122,7 @@ fun MangaScreen(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
) {
@@ -255,7 +253,7 @@ private fun MangaScreenSmallImpl(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
) {
@@ -330,25 +328,23 @@ private fun MangaScreenSmallImpl(
val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected
}
AnimatedVisibility(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
ExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
)
}
SmallExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
),
)
},
) { contentPadding ->
val topPadding = contentPadding.calculateTopPadding()
@@ -499,7 +495,7 @@ fun MangaScreenLargeImpl(
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
// Chapter selection
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onAllChapterSelected: (Boolean) -> Unit,
onInvertSelection: () -> Unit,
) {
@@ -572,27 +568,25 @@ fun MangaScreenLargeImpl(
val isFABVisible = remember(chapters) {
chapters.fastAny { !it.chapter.read } && !isAnySelected
}
AnimatedVisibility(
visible = isFABVisible,
enter = fadeIn(),
exit = fadeOut(),
) {
ExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(
if (isReading) MR.strings.action_resume else MR.strings.action_start,
),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
)
}
SmallExtendedFloatingActionButton(
text = {
val isReading = remember(state.chapters) {
state.chapters.fastAny { it.chapter.read }
}
Text(
text = stringResource(
if (isReading) MR.strings.action_resume else MR.strings.action_start,
),
)
},
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
onClick = onContinueReading,
expanded = chapterListState.shouldExpandFAB(),
modifier = Modifier.animateFloatingActionButton(
visible = isFABVisible,
alignment = Alignment.BottomEnd,
),
)
},
) { contentPadding ->
PullRefresh(
@@ -745,7 +739,7 @@ private fun LazyListScope.sharedChapterItems(
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
onChapterSelected: (ChapterList.Item, Boolean, Boolean) -> Unit,
onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
) {
items(
@@ -793,14 +787,14 @@ private fun LazyListScope.sharedChapterItems(
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = {
onChapterSelected(item, !item.selected, true, true)
onChapterSelected(item, !item.selected, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
},
onClick = {
onChapterItemClick(
chapterItem = item,
isAnyChapterSelected = isAnyChapterSelected,
onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
onToggleSelection = { onChapterSelected(item, !item.selected, false) },
onChapterClicked = onChapterClicked,
)
},
@@ -6,6 +6,7 @@ enum class DownloadAction {
NEXT_10_CHAPTERS,
NEXT_25_CHAPTERS,
UNREAD_CHAPTERS,
BOOKMARKED_CHAPTERS,
}
enum class EditCoverAction {
@@ -91,10 +91,10 @@ fun MangaBottomActionMenu(
) {
val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
var resetJob: Job? = remember { null }
var resetJob by remember { mutableStateOf<Job?>(null) }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0..<7).forEach { i -> confirm[i] = i == toConfirmIndex }
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1.seconds)
@@ -252,10 +252,10 @@ fun LibraryBottomActionMenu(
) {
val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
var resetJob: Job? = remember { null }
var resetJob by remember { mutableStateOf<Job?>(null) }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0..5).forEach { i -> confirm[i] = i == toConfirmIndex }
confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel()
resetJob = scope.launch {
delay(1.seconds)
@@ -48,7 +48,6 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
@@ -93,7 +92,7 @@ fun MangaCoverDialog(
Spacer(modifier = Modifier.weight(1f))
ActionsPill {
AppBarActions(
actions = persistentListOf(
actions = listOf(
AppBar.Action(
title = stringResource(MR.strings.action_share),
icon = Icons.Outlined.Share,
@@ -20,7 +20,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
import kotlinx.collections.immutable.toImmutableList
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.WheelTextPicker
@@ -124,7 +123,7 @@ fun SetIntervalDialog(
it.toString()
}
}
.toImmutableList()
WheelTextPicker(
items = items,
size = size,
@@ -567,44 +567,47 @@ private fun ColumnScope.MangaContentInfo(
}
}
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = markdownAnnotator(
annotate = { content, child ->
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
@Composable
private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = remember(loadImages, linkStyle) {
markdownAnnotator(
annotate = { content, child ->
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
?.getUnescapedTextInNode(content)
?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK)
?.findChildOfType(MarkdownTokenTypes.AUTOLINK)
val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
?.getUnescapedTextInNode(content)
?: return@markdownAnnotator false
?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK)
?.findChildOfType(MarkdownTokenTypes.AUTOLINK)
?.getUnescapedTextInNode(content)
?: return@markdownAnnotator false
val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE)
?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT)
?.getUnescapedTextInNode(content).orEmpty()
val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE)
?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT)
?.getUnescapedTextInNode(content).orEmpty()
withLink(LinkAnnotation.Url(url = url)) {
pushStyle(linkStyle)
appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG)
append(altText)
pop()
withLink(LinkAnnotation.Url(url = url)) {
pushStyle(linkStyle)
appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG)
append(altText)
pop()
}
return@markdownAnnotator true
}
return@markdownAnnotator true
}
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
append(content.substring(child.startOffset, child.endOffset))
return@markdownAnnotator true
}
if (child.type in DISALLOWED_MARKDOWN_TYPES) {
append(content.substring(child.startOffset, child.endOffset))
return@markdownAnnotator true
}
false
},
config = markdownAnnotatorConfig(
eolAsNewLine = true,
),
)
false
},
config = markdownAnnotatorConfig(
eolAsNewLine = true,
),
)
}
@Composable
private fun MangaSummary(
@@ -615,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",
@@ -55,9 +55,6 @@ import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import kotlin.time.Duration.Companion.seconds
private const val MAX_LENGTH = 250
private const val MAX_LENGTH_WARN = MAX_LENGTH * 0.9
@Composable
fun MangaNotesTextArea(
state: MangaNotesScreen.State,
@@ -102,7 +99,6 @@ fun MangaNotesTextArea(
RichTextEditor(
state = richTextState,
textStyle = MaterialTheme.typography.bodyLarge,
maxLength = MAX_LENGTH,
placeholder = {
Text(text = stringResource(MR.strings.notes_placeholder))
},
@@ -182,12 +178,7 @@ fun MangaNotesTextArea(
contentAlignment = Alignment.Center,
) {
Text(
text = (MAX_LENGTH - textLength).toString(),
color = if (textLength > MAX_LENGTH_WARN) {
MaterialTheme.colorScheme.error
} else {
Color.Unspecified
},
text = textLength.toString(),
modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
)
}
@@ -21,7 +21,6 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.AppBarTitle
import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.manga.DownloadAction
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.theme.active
@@ -76,7 +75,7 @@ fun MangaToolbar(
val filterTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current
AppBarActions(
actions = persistentListOf<AppBar.AppBarAction>().builder().apply {
actions = buildList {
if (isActionMode) {
add(
AppBar.Action(
@@ -92,7 +91,7 @@ fun MangaToolbar(
onClick = onInvertSelection,
),
)
return@apply
return@buildList
}
if (onClickDownload != null) {
add(
@@ -147,8 +146,7 @@ fun MangaToolbar(
onClick = onClickEditNotes,
),
)
}
.build(),
},
)
},
isActionMode = isActionMode,
@@ -14,7 +14,6 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
@@ -223,6 +222,7 @@ private val markdownComponents = markdownComponents(
if (type in DISALLOWED_MARKDOWN_TYPES) {
MarkdownText(
content = model.content.substring(model.node.startOffset, model.node.endOffset),
node = model.node,
style = model.typography.text,
)
}
@@ -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 {
@@ -5,8 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import eu.kanade.tachiyomi.data.track.Tracker
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.core.common.preference.Preference as PreferenceData
@@ -27,6 +25,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
@@ -68,10 +67,10 @@ sealed class Preference {
@Suppress("UNCHECKED_CAST")
data class ListPreference<T>(
val preference: PreferenceData<T>,
val entries: ImmutableMap<T, String>,
val entries: Map<T, String>,
override val title: String,
override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
{ v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null,
override val enabled: Boolean = true,
@@ -81,8 +80,8 @@ sealed class Preference {
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
@Composable
internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
subtitleProvider(value as T, entries as ImmutableMap<T, String>)
internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
subtitleProvider(value as T, entries as Map<T, String>)
}
/**
@@ -90,10 +89,10 @@ sealed class Preference {
*/
data class BasicListPreference(
val value: String,
val entries: ImmutableMap<String, String>,
val entries: Map<String, String>,
override val title: String,
override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
{ v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null,
override val enabled: Boolean = true,
@@ -106,10 +105,10 @@ sealed class Preference {
*/
data class MultiSelectListPreference(
val preference: PreferenceData<Set<String>>,
val entries: ImmutableMap<String, String>,
val entries: Map<String, String>,
override val title: String,
override val subtitle: String? = "%s",
val subtitleProvider: @Composable (value: Set<String>, entries: ImmutableMap<String, String>) -> String? =
val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? =
{ v, e ->
val combined = remember(v, e) {
v.mapNotNull { e[it] }
@@ -176,6 +175,6 @@ sealed class Preference {
override val title: String,
override val enabled: Boolean = true,
val preferenceItems: ImmutableList<PreferenceItem<out Any, out Any>>,
val preferenceItems: List<PreferenceItem<out Any, out Any>>,
) : Preference()
}
@@ -147,6 +147,7 @@ internal fun PreferenceItem(
title = item.title,
subtitle = item.subtitle,
icon = item.icon,
widget = item.widget,
onPreferenceClick = item.onClick,
)
}
@@ -53,9 +53,6 @@ import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import eu.kanade.tachiyomi.util.system.powerManager
import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch
import logcat.LogPriority
import okhttp3.Headers
@@ -99,7 +96,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 = {
@@ -140,7 +137,7 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.label_background_activity),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_disable_battery_optimization),
subtitle = stringResource(MR.strings.pref_disable_battery_optimization_summary),
@@ -179,7 +176,7 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.label_data),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_invalidate_download_cache),
subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary),
@@ -204,12 +201,12 @@ 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(
title = stringResource(MR.strings.label_network),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_clear_cookies),
onClick = {
@@ -238,8 +235,8 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = networkPreferences.dohProvider(),
entries = persistentMapOf(
preference = networkPreferences.dohProvider,
entries = mapOf(
-1 to stringResource(MR.strings.disabled),
PREF_DOH_CLOUDFLARE to "Cloudflare",
PREF_DOH_GOOGLE to "Google",
@@ -296,7 +293,7 @@ object SettingsAdvancedScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.label_library),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_refresh_library_covers),
onClick = { MetadataUpdateJob.startNow(context) },
@@ -319,12 +316,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 +340,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(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = basePreferences.hardwareBitmapThreshold(),
preference = basePreferences.hardwareBitmapThreshold,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option ->
val display = if (index == 0) {
@@ -360,8 +357,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}
option to display
}
.toMap()
.toImmutableMap(),
.toMap(),
title = stringResource(MR.strings.pref_hardware_bitmap_threshold),
subtitleProvider = { value, options ->
stringResource(MR.strings.pref_hardware_bitmap_threshold_summary, options[value].orEmpty())
@@ -370,13 +366,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 +387,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>() }
@@ -420,7 +416,7 @@ object SettingsAdvancedScreen : SearchableSettings {
}
return Preference.PreferenceGroup(
title = stringResource(MR.strings.label_extensions),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = extensionInstallerPref,
entries = extensionInstallerPref.entries
@@ -432,8 +428,7 @@ object SettingsAdvancedScreen : SearchableSettings {
true
}
}
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.ext_installer_pref),
onValueChanged = {
if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
@@ -19,8 +19,6 @@ import eu.kanade.presentation.more.settings.screen.appearance.AppLanguageScreen
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
@@ -50,18 +48,18 @@ 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(
title = stringResource(MR.strings.pref_category_theme),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.CustomPreference(
title = stringResource(MR.strings.pref_app_theme),
) {
@@ -103,23 +101,22 @@ 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)
}
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_app_language),
onClick = { navigator.push(AppLanguageScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.tabletUiMode(),
preference = uiPreferences.tabletUiMode,
entries = TabletUiMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.pref_tablet_ui_mode),
onValueChanged = {
context.toast(MR.strings.requires_app_restart)
@@ -127,17 +124,16 @@ object SettingsAppearanceScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.dateFormat(),
preference = uiPreferences.dateFormat,
entries = DateFormats
.associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now)
"${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
}
.toImmutableMap(),
},
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 +142,7 @@ object SettingsAppearanceScreen : SearchableSettings {
),
),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.imagesInDescription(),
preference = uiPreferences.imagesInDescription,
title = stringResource(MR.strings.pref_display_images_description),
),
),
@@ -11,10 +11,9 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import kotlinx.collections.immutable.persistentListOf
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
import mihon.domain.extension.interactor.GetExtensionStoreCountAsFlow
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
@@ -34,32 +33,32 @@ object SettingsBrowseScreen : SearchableSettings {
val navigator = LocalNavigator.currentOrThrow
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val getExtensionRepoCount = remember { Injekt.get<GetExtensionRepoCount>() }
val getExtensionStoreCountAsFlow = remember { Injekt.get<GetExtensionStoreCountAsFlow>() }
val reposCount by getExtensionRepoCount.subscribe().collectAsState(0)
val reposCount by getExtensionStoreCountAsFlow().collectAsState(0)
return listOf(
Preference.PreferenceGroup(
title = stringResource(MR.strings.label_sources),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.hideInLibraryItems(),
preference = sourcePreferences.hideInLibraryItems,
title = stringResource(MR.strings.pref_hide_in_library_items),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.label_extension_repos),
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount, reposCount),
title = stringResource(MR.strings.extensionStores),
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount.toInt(), reposCount),
onClick = {
navigator.push(ExtensionReposScreen())
navigator.push(ExtensionStoresScreen())
},
),
),
),
Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_nsfw_content),
preferenceItems = persistentListOf(
preferenceItems = listOf(
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 = {
@@ -56,8 +56,6 @@ import eu.kanade.tachiyomi.data.export.LibraryExporter
import eu.kanade.tachiyomi.data.export.LibraryExporter.ExportOptions
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import logcat.LogPriority
@@ -103,7 +101,7 @@ object SettingsDataScreen : SearchableSettings {
val backupPreferences = Injekt.get<BackupPreferences>()
val storagePreferences = Injekt.get<StoragePreferences>()
return persistentListOf(
return listOf(
getStorageLocationPref(storagePreferences = storagePreferences),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
@@ -167,11 +165,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 +185,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() {
@@ -207,7 +205,7 @@ object SettingsDataScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.label_backup),
preferenceItems = persistentListOf(
preferenceItems = listOf(
// Manual actions
Preference.PreferenceItem.CustomPreference(
title = stringResource(restorePreferenceKeyString),
@@ -254,8 +252,8 @@ object SettingsDataScreen : SearchableSettings {
// Automatic backups
Preference.PreferenceItem.ListPreference(
preference = backupPreferences.backupInterval(),
entries = persistentMapOf(
preference = backupPreferences.backupInterval,
entries = mapOf(
0 to stringResource(MR.strings.off),
6 to stringResource(MR.strings.update_6hour),
12 to stringResource(MR.strings.update_12hour),
@@ -289,7 +287,7 @@ object SettingsDataScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_storage_usage),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.CustomPreference(
title = stringResource(MR.strings.pref_storage_usage),
) {
@@ -321,7 +319,7 @@ object SettingsDataScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoClearChapterCache(),
preference = libraryPreferences.autoClearChapterCache,
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
),
),
@@ -382,7 +380,7 @@ object SettingsDataScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.export),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.library_list),
onClick = { showDialog = true },
@@ -12,9 +12,6 @@ import androidx.compose.ui.util.fastMap
import eu.kanade.presentation.category.visualName
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.download.service.DownloadPreferences
@@ -37,19 +34,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 +54,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,
@@ -85,14 +82,14 @@ object SettingsDownloadScreen : SearchableSettings {
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_delete_chapters),
preferenceItems = persistentListOf(
preferenceItems = listOf(
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(),
entries = persistentMapOf(
preference = downloadPreferences.removeAfterReadSlots,
entries = mapOf(
-1 to stringResource(MR.strings.disabled),
0 to stringResource(MR.strings.last_read_chapter),
1 to stringResource(MR.strings.second_to_last),
@@ -103,7 +100,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,10 +117,9 @@ 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(),
.associate { it.id.toString() to it.visualName },
title = stringResource(MR.strings.pref_remove_exclude_categories),
)
}
@@ -133,10 +129,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()
@@ -162,7 +158,7 @@ object SettingsDownloadScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_auto_download),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadNewChaptersPref,
title = stringResource(MR.strings.pref_download_new),
@@ -192,9 +188,9 @@ object SettingsDownloadScreen : SearchableSettings {
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.download_ahead),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.autoDownloadWhileReading(),
preference = downloadPreferences.autoDownloadWhileReading,
entries = listOf(0, 2, 3, 5, 10)
.associateWith {
if (it == 0) {
@@ -202,8 +198,7 @@ object SettingsDownloadScreen : SearchableSettings {
} else {
pluralStringResource(MR.plurals.next_unread_chapters, count = it, it)
}
}
.toImmutableMap(),
},
title = stringResource(MR.strings.auto_download_while_reading),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)),
@@ -20,9 +20,6 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.TriStateListDialog
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.ui.category.CategoryScreen
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags
@@ -73,14 +70,14 @@ 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 }
return Preference.PreferenceGroup(
title = stringResource(MR.strings.categories),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.action_edit_categories),
subtitle = pluralStringResource(
@@ -91,12 +88,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { navigator.push(CategoryScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.defaultCategory(),
entries = ids.zip(labels).toMap().toImmutableMap(),
preference = libraryPreferences.defaultCategory,
entries = ids.zip(labels).toMap(),
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 +115,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()
@@ -146,10 +143,10 @@ object SettingsLibraryScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_library_update),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = autoUpdateIntervalPref,
entries = persistentMapOf(
entries = mapOf(
0 to stringResource(MR.strings.update_never),
12 to stringResource(MR.strings.update_12hour),
24 to stringResource(MR.strings.update_24hour),
@@ -164,8 +161,8 @@ object SettingsLibraryScreen : SearchableSettings {
},
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateDeviceRestrictions(),
entries = persistentMapOf(
preference = libraryPreferences.autoUpdateDeviceRestrictions,
entries = mapOf(
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
DEVICE_CHARGING to stringResource(MR.strings.charging),
@@ -189,13 +186,13 @@ 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(),
entries = persistentMapOf(
preference = libraryPreferences.autoUpdateMangaRestrictions,
entries = mapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed),
@@ -204,7 +201,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),
),
),
@@ -217,10 +214,10 @@ object SettingsLibraryScreen : SearchableSettings {
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_behavior),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToStartAction(),
entries = persistentMapOf(
preference = libraryPreferences.swipeToStartAction,
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
@@ -233,8 +230,8 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_start),
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToEndAction(),
entries = persistentMapOf(
preference = libraryPreferences.swipeToEndAction,
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
@@ -247,8 +244,8 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_end),
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.markDuplicateReadChapterAsRead(),
entries = persistentMapOf(
preference = libraryPreferences.markDuplicateReadChapterAsRead,
entries = mapOf(
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
MARK_DUPLICATE_CHAPTER_READ_NEW to
@@ -257,7 +254,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),
),
),
@@ -44,7 +44,6 @@ import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
@@ -88,7 +87,7 @@ object SettingsMainScreen : Screen() {
navigateUp = backPress::invoke,
actions = {
AppBarActions(
persistentListOf(
listOf(
AppBar.Action(
title = stringResource(MR.strings.action_search),
icon = Icons.Outlined.Search,
@@ -10,9 +10,6 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
@@ -33,15 +30,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(),
.associate { it.flagValue to stringResource(it.stringRes) },
title = stringResource(MR.strings.pref_viewer_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPref.doubleTapAnimSpeed(),
entries = persistentMapOf(
preference = readerPref.doubleTapAnimSpeed,
entries = mapOf(
1 to stringResource(MR.strings.double_tap_anim_speed_0),
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
250 to stringResource(MR.strings.double_tap_anim_speed_fast),
@@ -49,17 +45,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,21 +70,20 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreenPref = readerPreferences.fullscreen()
val fullscreen by fullscreenPref.collectAsState()
val fullscreen by readerPreferences.fullscreen.collectAsState()
val verticalNavigatorForLongStrip by readerPreferences.verticalNavigatorForLongStrip.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.defaultOrientationType(),
preference = readerPreferences.defaultOrientationType,
entries = ReaderOrientation.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(),
.associate { it.flagValue to stringResource(it.stringRes) },
title = stringResource(MR.strings.pref_rotation_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerTheme(),
entries = persistentMapOf(
preference = readerPreferences.readerTheme,
entries = mapOf(
1 to stringResource(MR.strings.black_background),
2 to stringResource(MR.strings.gray_background),
0 to stringResource(MR.strings.white_background),
@@ -97,43 +92,52 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_reader_theme),
),
Preference.PreferenceItem.SwitchPreference(
preference = fullscreenPref,
preference = readerPreferences.fullscreen,
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),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.verticalNavigatorForLongStrip,
title = stringResource(MR.strings.pref_webtoon_vertical_navigator),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.verticalNavigatorOnLeft,
title = stringResource(MR.strings.pref_webtoon_vertical_navigator_on_left),
enabled = verticalNavigatorForLongStrip,
),
),
)
}
@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(
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.flashOnPageChange(),
preference = readerPreferences.flashOnPageChange,
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
@@ -155,7 +159,7 @@ object SettingsReaderScreen : SearchableSettings {
),
Preference.PreferenceItem.ListPreference(
preference = flashColorPref,
entries = persistentMapOf(
entries = mapOf(
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
ReaderPreferences.FlashColor.WHITE_BLACK
@@ -172,21 +176,21 @@ object SettingsReaderScreen : SearchableSettings {
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reading),
preferenceItems = persistentListOf(
preferenceItems = listOf(
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 +199,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()
@@ -207,25 +211,23 @@ object SettingsReaderScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pager_viewer),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = navModePref,
entries = ReaderPreferences.TapZones
.mapIndexed { index, it -> index to stringResource(it) }
.toMap()
.toImmutableMap(),
.toMap(),
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.pagerNavInverted(),
entries = persistentListOf(
preference = readerPreferences.pagerNavInverted,
entries = listOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
ReaderPreferences.TappingInvertMode.VERTICAL,
ReaderPreferences.TappingInvertMode.BOTH,
)
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
enabled = navMode != 5,
),
@@ -233,29 +235,27 @@ object SettingsReaderScreen : SearchableSettings {
preference = imageScaleTypePref,
entries = ReaderPreferences.ImageScaleType
.mapIndexed { index, it -> index + 1 to stringResource(it) }
.toMap()
.toImmutableMap(),
.toMap(),
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()
.toImmutableMap(),
.toMap(),
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()
@@ -306,25 +306,23 @@ object SettingsReaderScreen : SearchableSettings {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.webtoon_viewer),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
preference = navModePref,
entries = ReaderPreferences.TapZones
.mapIndexed { index, it -> index to stringResource(it) }
.toMap()
.toImmutableMap(),
.toMap(),
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.webtoonNavInverted(),
entries = persistentListOf(
preference = readerPreferences.webtoonNavInverted,
entries = listOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
ReaderPreferences.TappingInvertMode.VERTICAL,
ReaderPreferences.TappingInvertMode.BOTH,
)
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.pref_read_with_tapping_inverted),
enabled = navMode != 5,
),
@@ -338,8 +336,8 @@ object SettingsReaderScreen : SearchableSettings {
onValueChanged = { webtoonSidePaddingPref.set(it) },
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(),
entries = persistentMapOf(
preference = readerPreferences.readerHideThreshold,
entries = mapOf(
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low),
@@ -348,7 +346,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 +358,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 +372,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,17 +390,17 @@ 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),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
preference = readWithVolumeKeysPref,
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,
),
@@ -414,13 +412,13 @@ object SettingsReaderScreen : SearchableSettings {
private fun getActionsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_reader_actions),
preferenceItems = persistentListOf(
preferenceItems = listOf(
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),
),
@@ -12,8 +12,6 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
import eu.kanade.tachiyomi.util.system.telemetryIncluded
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
@@ -45,12 +43,12 @@ 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(
title = stringResource(MR.strings.pref_security),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.SwitchPreference(
preference = useAuthPref,
title = stringResource(MR.strings.lock_with_biometrics),
@@ -62,7 +60,7 @@ object SettingsSecurityScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = securityPreferences.lockAppAfter(),
preference = securityPreferences.lockAppAfter,
entries = LockAfterValues
.associateWith {
when (it) {
@@ -70,8 +68,7 @@ object SettingsSecurityScreen : SearchableSettings {
0 -> stringResource(MR.strings.lock_always)
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
}
}
.toImmutableMap(),
},
title = stringResource(MR.strings.lock_when_idle),
enabled = authSupported && useAuth,
onValueChanged = {
@@ -82,14 +79,13 @@ 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(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.secure_screen),
),
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
@@ -103,14 +99,14 @@ object SettingsSecurityScreen : SearchableSettings {
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
preferenceItems = listOf(
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),
),
@@ -120,7 +116,7 @@ object SettingsSecurityScreen : SearchableSettings {
}
}
private val LockAfterValues = persistentListOf(
private val LockAfterValues = listOf(
0, // Always
1,
2,
@@ -55,9 +55,6 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentMap
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager
@@ -90,7 +87,6 @@ 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()
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@@ -115,7 +111,7 @@ object SettingsTrackingScreen : SearchableSettings {
.filter { it is EnhancedTracker }
.partition { service ->
val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
sourceManager.getAll().any { it::class.qualifiedName in acceptedSources }
}
var enhancedTrackerInfo = stringResource(MR.strings.enhanced_tracking_info)
if (enhancedTrackers.second.isNotEmpty()) {
@@ -128,19 +124,18 @@ 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(),
.associateWith { stringResource(it.titleRes) },
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
),
Preference.PreferenceGroup(
title = stringResource(MR.strings.services),
preferenceItems = persistentListOf(
preferenceItems = listOf(
Preference.PreferenceItem.TrackerPreference(
tracker = trackerManager.myAnimeList,
login = { context.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true) },
@@ -185,7 +180,7 @@ object SettingsTrackingScreen : SearchableSettings {
logout = service::logout,
)
} + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo))
).toImmutableList(),
),
),
)
}
@@ -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",
@@ -225,7 +229,6 @@ object AboutScreen : Screen() {
is GetApplicationRelease.Result.OsTooOld -> {
context.toast(MR.strings.update_check_eol)
}
else -> {}
}
} catch (e: Exception) {
context.toast(e.message)
@@ -276,7 +279,7 @@ object AboutScreen : Screen() {
)
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
Injekt.get<UiPreferences>().dateFormat.get(),
),
)
} catch (e: Exception) {
@@ -18,7 +18,6 @@ import com.google.android.material.textview.MaterialTextView
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.util.Screen
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
@@ -42,7 +41,7 @@ class OpenSourceLibraryLicenseScreen(
actions = {
if (!website.isNullOrEmpty()) {
AppBarActions(
persistentListOf(
listOf(
AppBar.Action(
title = stringResource(MR.strings.website),
icon = Icons.Default.Public,
@@ -2,13 +2,16 @@ package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
@@ -27,7 +30,9 @@ class OpenSourceLicensesScreen : Screen() {
)
},
) { contentPadding ->
val libraries by produceLibraries(R.raw.aboutlibraries)
LibrariesContainer(
libraries = libraries,
modifier = Modifier
.fillMaxSize(),
contentPadding = contentPadding,
@@ -40,7 +40,6 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import tachiyomi.core.common.util.lang.launchIO
@@ -140,7 +139,7 @@ class ClearDatabaseScreen : Screen() {
actions = {
if (s.items.isNotEmpty()) {
AppBarActions(
actions = persistentListOf(
actions = listOf(
AppBar.Action(
title = stringResource(MR.strings.action_select_all),
icon = Icons.Outlined.SelectAll,
@@ -27,8 +27,6 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import org.xmlpull.v1.XmlPullParser
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
@@ -94,8 +92,7 @@ class AppLanguageScreen : Screen() {
}
}
private fun getLangs(context: Context): ImmutableList<Language> {
val langs = mutableListOf<Language>()
private fun getLangs(context: Context): List<Language> = buildList {
val parser = context.resources.getXml(R.xml.locales_config)
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
@@ -105,7 +102,7 @@ class AppLanguageScreen : Screen() {
val langTag = parser.getAttributeValue(i)
val displayName = LocaleHelper.getLocalizedDisplayName(langTag)
if (displayName.isNotEmpty()) {
langs.add(Language(langTag, displayName, LocaleHelper.getDisplayName(langTag)))
add(Language(langTag, displayName, LocaleHelper.getDisplayName(langTag)))
}
}
}
@@ -113,10 +110,8 @@ class AppLanguageScreen : Screen() {
eventType = parser.next()
}
langs.sortBy { it.displayName }
langs.add(0, Language("", context.stringResource(MR.strings.label_default), null))
return langs.toImmutableList()
sortBy { it.displayName }
add(0, Language("", context.stringResource(MR.strings.label_default), null))
}
private data class Language(
@@ -1,96 +0,0 @@
package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionReposScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.flow.collectLatest
import tachiyomi.presentation.core.screens.LoadingScreen
class ExtensionReposScreen(
private val url: String? = null,
) : Screen() {
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { ExtensionReposScreenModel() }
val state by screenModel.state.collectAsState()
LaunchedEffect(url) {
url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
}
if (state is RepoScreenState.Loading) {
LoadingScreen()
return
}
val successState = state as RepoScreenState.Success
ExtensionReposScreen(
state = successState,
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
onOpenWebsite = { context.openInBrowser(it.website) },
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
onClickRefresh = { screenModel.refreshRepos() },
navigateUp = navigator::pop,
)
when (val dialog = successState.dialog) {
null -> {}
is RepoDialog.Create -> {
ExtensionRepoCreateDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(it) },
repoUrls = successState.repos.map { it.baseUrl }.toImmutableSet(),
)
}
is RepoDialog.Delete -> {
ExtensionRepoDeleteDialog(
onDismissRequest = screenModel::dismissDialog,
onDelete = { screenModel.deleteRepo(dialog.repo) },
repo = dialog.repo,
)
}
is RepoDialog.Conflict -> {
ExtensionRepoConflictDialog(
onDismissRequest = screenModel::dismissDialog,
onMigrate = { screenModel.replaceRepo(dialog.newRepo) },
oldRepo = dialog.oldRepo,
newRepo = dialog.newRepo,
)
}
is RepoDialog.Confirm -> {
ExtensionRepoConfirmDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(dialog.url) },
repo = dialog.url,
)
}
}
LaunchedEffect(Unit) {
screenModel.events.collectLatest { event ->
if (event is RepoEvent.LocalizedMessage) {
context.toast(event.stringRes)
}
}
}
}
}
@@ -1,150 +0,0 @@
package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionReposScreenModel(
private val getExtensionRepo: GetExtensionRepo = Injekt.get(),
private val createExtensionRepo: CreateExtensionRepo = Injekt.get(),
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
val events = _events.receiveAsFlow()
init {
screenModelScope.launchIO {
getExtensionRepo.subscribeAll()
.collectLatest { repos ->
mutableState.update {
RepoScreenState.Success(
repos = repos.toImmutableSet(),
)
}
}
}
}
/**
* Creates and adds a new repo to the database.
*
* @param baseUrl The baseUrl of the repo to create.
*/
fun createRepo(baseUrl: String) {
screenModelScope.launchIO {
when (val result = createExtensionRepo.await(baseUrl)) {
CreateExtensionRepo.Result.Success -> extensionManager.findAvailableExtensions()
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
showDialog(RepoDialog.Conflict(result.oldRepo, result.newRepo))
}
else -> {}
}
}
}
/**
* Inserts a repo to the database, replace a matching repo with the same signing key fingerprint if found.
*
* @param newRepo The repo to insert
*/
fun replaceRepo(newRepo: ExtensionRepo) {
screenModelScope.launchIO {
replaceExtensionRepo.await(newRepo)
}
}
/**
* Refreshes information for each repository.
*/
fun refreshRepos() {
val status = state.value
if (status is RepoScreenState.Success) {
screenModelScope.launchIO {
updateExtensionRepo.awaitAll()
}
}
}
/**
* Deletes the given repo from the database
*/
fun deleteRepo(baseUrl: String) {
screenModelScope.launchIO {
deleteExtensionRepo.await(baseUrl)
extensionManager.findAvailableExtensions()
}
}
fun showDialog(dialog: RepoDialog) {
mutableState.update {
when (it) {
RepoScreenState.Loading -> it
is RepoScreenState.Success -> it.copy(dialog = dialog)
}
}
}
fun dismissDialog() {
mutableState.update {
when (it) {
RepoScreenState.Loading -> it
is RepoScreenState.Success -> it.copy(dialog = null)
}
}
}
}
sealed class RepoEvent {
sealed class LocalizedMessage(val stringRes: StringResource) : RepoEvent()
data object InvalidUrl : LocalizedMessage(MR.strings.invalid_repo_name)
data object RepoAlreadyExists : LocalizedMessage(MR.strings.error_repo_exists)
}
sealed class RepoDialog {
data object Create : RepoDialog()
data class Delete(val repo: String) : RepoDialog()
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
data class Confirm(val url: String) : RepoDialog()
}
sealed class RepoScreenState {
@Immutable
data object Loading : RepoScreenState()
@Immutable
data class Success(
val repos: ImmutableSet<ExtensionRepo>,
val oldRepos: ImmutableSet<String>? = null,
val dialog: RepoDialog? = null,
) : RepoScreenState() {
val isEmpty: Boolean
get() = repos.isEmpty()
}
}
@@ -0,0 +1,85 @@
package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreConfirmDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreCreateDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoreDeleteDialog
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionStoresScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.openInBrowser
import tachiyomi.presentation.core.screens.LoadingScreen
class ExtensionStoresScreen(
private val url: String? = null,
) : Screen() {
@Composable
override fun Content() {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val screenModel = rememberScreenModel { ExtensionStoresScreenModel() }
val state by screenModel.state.collectAsState()
LaunchedEffect(url) {
url?.let { screenModel.addFromDeeplink(url) }
}
if (state is ExtensionStoreScreenState.Loading) {
LoadingScreen()
return
}
val successState = state as ExtensionStoreScreenState.Success
ExtensionStoresScreen(
state = successState,
onClickCreate = { screenModel.showDialog(ExtensionStoreDialog.Create()) },
onCopy = { context.copyToClipboard(it.indexUrl, it.indexUrl) },
onOpenWebsite = { it.contact.website.let(context::openInBrowser) },
onOpenDiscord = { it.contact.discord?.let(context::openInBrowser) },
onClickDelete = { screenModel.showDialog(ExtensionStoreDialog.Delete(it)) },
onClickRefresh = { screenModel.refreshRepos() },
navigateUp = navigator::pop,
)
when (val dialog = successState.dialog) {
null -> {}
is ExtensionStoreDialog.Create -> {
ExtensionStoreCreateDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(it) },
storeIndexUrls = successState.stores.map { it.indexUrl }.toSet(),
processing = dialog.processing,
errorMessage = dialog.errorMessage,
)
}
is ExtensionStoreDialog.Delete -> {
ExtensionStoreDeleteDialog(
onDismissRequest = screenModel::dismissDialog,
onDelete = { screenModel.deleteRepo(dialog.store.indexUrl) },
storeName = dialog.store.name,
storeIndexUrl = dialog.store.indexUrl,
)
}
is ExtensionStoreDialog.Confirm -> {
ExtensionStoreConfirmDialog(
onDismissRequest = screenModel::dismissDialog,
onCreate = { screenModel.createRepo(dialog.url) },
storeIndexUrl = dialog.url,
storeAlreadyExists = dialog.alreadyExists,
processing = dialog.processing,
errorMessage = dialog.errorMessage,
)
}
}
}
}
@@ -0,0 +1,165 @@
package eu.kanade.presentation.more.settings.screen.browse
import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import mihon.domain.extension.interactor.AddExtensionStore
import mihon.domain.extension.interactor.GetExtensionStores
import mihon.domain.extension.interactor.RemoveExtensionStore
import mihon.domain.extension.interactor.UpdateExtensionStores
import mihon.domain.extension.model.ExtensionStore
import tachiyomi.core.common.util.lang.launchIO
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionStoresScreenModel(
private val getExtensionStores: GetExtensionStores = Injekt.get(),
private val addExtensionStore: AddExtensionStore = Injekt.get(),
private val removeExtensionStore: RemoveExtensionStore = Injekt.get(),
private val updateExtensionStores: UpdateExtensionStores = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
) : StateScreenModel<ExtensionStoreScreenState>(ExtensionStoreScreenState.Loading) {
private inline fun updateSuccessState(
func: (ExtensionStoreScreenState.Success) -> ExtensionStoreScreenState.Success,
) {
mutableState.update {
when (it) {
ExtensionStoreScreenState.Loading -> it
is ExtensionStoreScreenState.Success -> func(it)
}
}
}
init {
screenModelScope.launchIO {
getExtensionStores.subscribe()
.collectLatest { stores ->
mutableState.update {
when (it) {
ExtensionStoreScreenState.Loading -> ExtensionStoreScreenState.Success(stores = stores)
is ExtensionStoreScreenState.Success -> it.copy(stores = stores)
}
}
}
}
}
/**
* Creates and adds a new repo to the database.
*
* @param baseUrl The baseUrl of the repo to create.
*/
fun createRepo(baseUrl: String) {
screenModelScope.launch {
updateSuccessState {
it.copy(
dialog = when (it.dialog) {
is ExtensionStoreDialog.Create -> it.dialog.copy(processing = true)
is ExtensionStoreDialog.Confirm -> it.dialog.copy(processing = true)
else -> it.dialog
},
)
}
addExtensionStore(baseUrl)
.onSuccess {
extensionManager.findAvailableExtensions()
dismissDialog()
}
.onFailure { throwable ->
updateSuccessState {
it.copy(
dialog = when (it.dialog) {
is ExtensionStoreDialog.Create -> it.dialog.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
is ExtensionStoreDialog.Confirm -> it.dialog.copy(
processing = false,
errorMessage = throwable.message ?: "unknown error",
)
else -> it.dialog
},
)
}
}
}
}
/**
* Refreshes information for each repository.
*/
fun refreshRepos() {
val status = state.value
if (status is ExtensionStoreScreenState.Success) {
screenModelScope.launchIO {
updateExtensionStores()
}
}
}
/**
* Deletes the given repo from the database
*/
fun deleteRepo(baseUrl: String) {
screenModelScope.launchIO {
removeExtensionStore(baseUrl)
extensionManager.findAvailableExtensions()
}
}
fun addFromDeeplink(storeIndexUrl: String) {
updateSuccessState { state ->
state.copy(
dialog = ExtensionStoreDialog.Confirm(
url = storeIndexUrl,
alreadyExists = state.stores.any { it.indexUrl == storeIndexUrl },
),
)
}
}
fun showDialog(dialog: ExtensionStoreDialog) {
updateSuccessState { state ->
state.copy(dialog = dialog)
}
}
fun dismissDialog() {
updateSuccessState {
it.copy(dialog = null)
}
}
}
sealed class ExtensionStoreDialog {
data class Create(val processing: Boolean = false, val errorMessage: String? = null) : ExtensionStoreDialog()
data class Delete(val store: ExtensionStore) : ExtensionStoreDialog()
data class Confirm(
val url: String,
val alreadyExists: Boolean = false,
val processing: Boolean = false,
val errorMessage: String? = null,
) : ExtensionStoreDialog()
}
sealed class ExtensionStoreScreenState {
@Immutable
data object Loading : ExtensionStoreScreenState()
@Immutable
data class Success(
val stores: List<ExtensionStore>,
val dialog: ExtensionStoreDialog? = null,
) : ExtensionStoreScreenState() {
val isEmpty: Boolean
get() = stores.isEmpty()
}
}
@@ -1,186 +0,0 @@
package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.KeyboardType
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.coroutines.delay
import mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
import kotlin.time.Duration.Companion.seconds
@Composable
fun ExtensionRepoCreateDialog(
onDismissRequest: () -> Unit,
onCreate: (String) -> Unit,
repoUrls: ImmutableSet<String>,
) {
var name by remember { mutableStateOf("") }
val focusRequester = remember { FocusRequester() }
val nameAlreadyExists = remember(name) { repoUrls.contains(name) }
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
enabled = name.isNotEmpty() && !nameAlreadyExists,
onClick = {
onCreate(name)
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_add))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
title = {
Text(text = stringResource(MR.strings.action_add_repo))
},
text = {
Column {
Text(text = stringResource(MR.strings.action_add_repo_message, stringResource(MR.strings.app_name)))
OutlinedTextField(
modifier = Modifier
.focusRequester(focusRequester),
value = name,
onValueChange = { name = it },
label = {
Text(text = stringResource(MR.strings.label_add_repo_input))
},
supportingText = {
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
MR.strings.error_repo_exists
} else {
MR.strings.information_required_plain
}
Text(text = stringResource(msgRes))
},
isError = name.isNotEmpty() && nameAlreadyExists,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
singleLine = true,
)
}
},
)
LaunchedEffect(focusRequester) {
// TODO: https://issuetracker.google.com/issues/204502668
delay(0.1.seconds)
focusRequester.requestFocus()
}
}
@Composable
fun ExtensionRepoDeleteDialog(
onDismissRequest: () -> Unit,
onDelete: () -> Unit,
repo: String,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = {
onDelete()
onDismissRequest()
}) {
Text(text = stringResource(MR.strings.action_ok))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
title = {
Text(text = stringResource(MR.strings.action_delete_repo))
},
text = {
Text(text = stringResource(MR.strings.delete_repo_confirmation, repo))
},
)
}
@Composable
fun ExtensionRepoConflictDialog(
oldRepo: ExtensionRepo,
newRepo: ExtensionRepo,
onDismissRequest: () -> Unit,
onMigrate: () -> Unit,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
onClick = {
onMigrate()
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_replace_repo))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
title = {
Text(text = stringResource(MR.strings.action_replace_repo_title))
},
text = {
Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name))
},
)
}
@Composable
fun ExtensionRepoConfirmDialog(
onDismissRequest: () -> Unit,
onCreate: () -> Unit,
repo: String,
) {
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(MR.strings.action_add_repo))
},
text = {
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
},
confirmButton = {
TextButton(
onClick = {
onCreate()
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_add))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
},
)
}

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