Compare commits

...
160 Commits
Author SHA1 Message Date
AntsyLich 54d2c7b9e0 Release v0.19.7 2026-03-23 22:31:36 +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
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
317 changed files with 3572 additions and 2132 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.3](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.7](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.3"
Example: "0.19.7"
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.3](https://github.com/mihonapp/mihon/releases/latest)**.
- label: I have updated the app to version **[0.19.7](https://github.com/mihonapp/mihon/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
+8 -8
View File
@@ -26,20 +26,20 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Dependency Review
if: github.event_name == 'pull_request'
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.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@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
- name: Check code format
run: ./gradlew spotlessCheck
@@ -53,19 +53,19 @@ jobs:
- name: Upload test report
if: steps.unit_tests.outcome == 'failure'
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: test-report-${{ github.sha }}
path: app/build/reports/tests/testReleaseUnitTest
- name: Upload APK
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
- name: Upload mapping
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mapping-${{ github.sha }}
path: app/build/outputs/mapping/release
+13 -13
View File
@@ -29,16 +29,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.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@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
- name: Build
run: ./gradlew assembleRelease -Pinclude-telemetry -Penable-updater
@@ -65,7 +65,7 @@ jobs:
mv app/build/outputs/apk/release/app-x86_64-release-unsigned-signed.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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mihon
path: |
@@ -83,16 +83,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK
uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.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@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
with:
cache-disabled: true
@@ -117,7 +117,7 @@ jobs:
mv app/build/outputs/apk/foss/app-universal-foss-unsigned-signed.apk mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
- name: Upload APK
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: mihon-foss
path: mihon-${{ needs.get_tag.outputs.tag }}-foss.apk
@@ -130,7 +130,7 @@ jobs:
steps:
- name: Download all artifacts
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
merge-multiple: true
@@ -143,12 +143,12 @@ jobs:
mihon-foss
- name: Create GitHub Release
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.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!
Check out the [past release notes](https://github.com/mihonapp/mihon/releases) if you're upgrading from an earlier version. Consider [becoming a patron](https://www.patreon.com/mihon/membership) to help keep Mihon improving!
<!-->
+67 -1
View File
@@ -10,6 +10,68 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- `Fixed` - for any bug fixes.
- `Other` - for technical stuff.
## [Unreleased]
## [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))
@@ -443,7 +505,11 @@ 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.3...main
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.19.7...main
[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
+6 -3
View File
@@ -26,8 +26,8 @@ android {
defaultConfig {
applicationId = "app.mihon"
versionCode = 16
versionName = "0.19.3"
versionCode = 20
versionName = "0.19.7"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -159,12 +159,14 @@ 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",
"-opt-in=kotlinx.coroutines.FlowPreview",
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xannotation-default-target=param-property",
)
}
}
@@ -198,7 +200,7 @@ dependencies {
implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(androidx.sqlite.bundled)
implementation(kotlinx.reflect)
implementation(kotlinx.immutables)
@@ -270,6 +272,7 @@ dependencies {
implementation(libs.compose.grid)
implementation(libs.reorderable)
implementation(libs.bundles.markdown)
implementation(libs.materialKolor)
// Logging
implementation(libs.logcat)
@@ -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()) }
@@ -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,17 @@ 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"), "")
}
@@ -46,7 +46,7 @@ class SetReadStatus(
return@withNonCancellableContext Result.InternalError(e)
}
if (read && downloadPreferences.removeAfterMarkedAsRead().get()) {
if (read && downloadPreferences.removeAfterMarkedAsRead.get()) {
chaptersToUpdate
.groupBy { it.mangaId }
.forEach { (mangaId, chapters) ->
@@ -171,7 +171,7 @@ class SyncChaptersWithSource(
val deletedChapterNumberDateFetchMap = removedChapters.sortedByDescending { it.dateFetch }
.associate { it.chapterNumber to it.dateFetch }
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead().get()
val markDuplicateAsRead = libraryPreferences.markDuplicateReadChapterAsRead.get()
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_NEW)
// Date fetch is set in such a way that the upper ones will have bigger value than the lower ones
@@ -12,7 +12,7 @@ class GetExtensionLanguages(
) {
fun subscribe(): Flow<List<String>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
extensionManager.availableExtensionsFlow,
) { enabledLanguage, availableExtensions ->
availableExtensions
@@ -15,7 +15,7 @@ class GetExtensionSources(
val isMultiLangSingleSource =
isMultiSource && extension.sources.map { it.name }.distinct().size == 1
return preferences.disabledSources().changes().map { disabledSources ->
return preferences.disabledSources.changes().map { disabledSources ->
fun Source.isEnabled() = id.toString() !in disabledSources
extension.sources
@@ -13,10 +13,10 @@ class GetExtensionsByType(
) {
fun subscribe(): Flow<Extensions> {
val showNsfwSources = preferences.showNsfwSource().get()
val showNsfwSources = preferences.showNsfwSource.get()
return combine(
preferences.enabledLanguages().changes(),
preferences.enabledLanguages.changes(),
extensionManager.installedExtensionsFlow,
extensionManager.untrustedExtensionsFlow,
extensionManager.availableExtensionsFlow,
@@ -14,11 +14,11 @@ class TrustExtension(
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions.get()
}
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
preferences.trustedExtensions().getAndSet { exts ->
preferences.trustedExtensions.getAndSet { exts ->
// Remove previously trusted versions
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
@@ -27,6 +27,6 @@ class TrustExtension(
}
fun revokeAll() {
preferences.trustedExtensions().delete()
preferences.trustedExtensions.delete()
}
}
@@ -44,7 +44,7 @@ class UpdateManga(
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
val title =
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles().get())) {
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles.get())) {
remoteTitle
} else {
null
@@ -22,7 +22,7 @@ val Manga.readerOrientation: Long
val Manga.downloadedFilter: TriState
get() {
if (Injekt.get<BasePreferences>().downloadedOnly().get()) return TriState.ENABLED_IS
if (Injekt.get<BasePreferences>().downloadedOnly.get()) return TriState.ENABLED_IS
return when (downloadedFilterRaw) {
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
@@ -17,10 +17,10 @@ class GetEnabledSources(
fun subscribe(): Flow<List<Source>> {
return combine(
preferences.pinnedSources().changes(),
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.lastUsedSource().changes(),
preferences.pinnedSources.changes(),
preferences.enabledLanguages.changes(),
preferences.disabledSources.changes(),
preferences.lastUsedSource.changes(),
repository.getSources(),
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
sources
@@ -13,19 +13,19 @@ class GetIncognitoState(
private val extensionManager: ExtensionManager,
) {
fun await(sourceId: Long?): Boolean {
if (basePreferences.incognitoMode().get()) return true
if (basePreferences.incognitoMode.get()) return true
if (sourceId == null) return false
val extensionPackage = extensionManager.getExtensionPackage(sourceId) ?: return false
return extensionPackage in sourcePreferences.incognitoExtensions().get()
return extensionPackage in sourcePreferences.incognitoExtensions.get()
}
fun subscribe(sourceId: Long?): Flow<Boolean> {
if (sourceId == null) return basePreferences.incognitoMode().changes()
if (sourceId == null) return basePreferences.incognitoMode.changes()
return combine(
basePreferences.incognitoMode().changes(),
sourcePreferences.incognitoExtensions().changes(),
basePreferences.incognitoMode.changes(),
sourcePreferences.incognitoExtensions.changes(),
extensionManager.getExtensionPackageAsFlow(sourceId),
) { incognito, incognitoExtensions, extensionPackage ->
incognito || (extensionPackage in incognitoExtensions)
@@ -15,8 +15,8 @@ class GetLanguagesWithSources(
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
return combine(
preferences.enabledLanguages().changes(),
preferences.disabledSources().changes(),
preferences.enabledLanguages.changes(),
preferences.disabledSources.changes(),
repository.getOnlineSources(),
) { enabledLanguage, disabledSource, onlineSources ->
val sortedSources = onlineSources.sortedWith(
@@ -16,8 +16,8 @@ class GetSourcesWithFavoriteCount(
fun subscribe(): Flow<List<Pair<Source, Long>>> {
return combine(
preferences.migrationSortingDirection().changes(),
preferences.migrationSortingMode().changes(),
preferences.migrationSortingDirection.changes(),
preferences.migrationSortingMode.changes(),
repository.getSourcesWithFavoriteCount(),
) { direction, mode, list ->
list
@@ -7,8 +7,8 @@ class SetMigrateSorting(
) {
fun await(mode: Mode, direction: Direction) {
preferences.migrationSortingMode().set(mode)
preferences.migrationSortingDirection().set(direction)
preferences.migrationSortingMode.set(mode)
preferences.migrationSortingDirection.set(direction)
}
enum class Mode {
@@ -7,7 +7,7 @@ class ToggleIncognito(
private val preferences: SourcePreferences,
) {
fun await(extensions: String, enable: Boolean) {
preferences.incognitoExtensions().getAndSet {
preferences.incognitoExtensions.getAndSet {
if (enable) it.plus(extensions) else it.minus(extensions)
}
}
@@ -8,8 +8,8 @@ class ToggleLanguage(
) {
fun await(language: String) {
val isEnabled = language in preferences.enabledLanguages().get()
preferences.enabledLanguages().getAndSet { enabled ->
val isEnabled = language in preferences.enabledLanguages.get()
preferences.enabledLanguages.getAndSet { enabled ->
if (isEnabled) enabled.minus(language) else enabled.plus(language)
}
}
@@ -13,19 +13,19 @@ class ToggleSource(
}
fun await(sourceId: Long, enable: Boolean = isEnabled(sourceId)) {
preferences.disabledSources().getAndSet { disabled ->
preferences.disabledSources.getAndSet { disabled ->
if (enable) disabled.minus("$sourceId") else disabled.plus("$sourceId")
}
}
fun await(sourceIds: List<Long>, enable: Boolean) {
val transformedSourceIds = sourceIds.map { it.toString() }
preferences.disabledSources().getAndSet { disabled ->
preferences.disabledSources.getAndSet { disabled ->
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
}
}
private fun isEnabled(sourceId: Long): Boolean {
return sourceId.toString() in preferences.disabledSources().get()
return sourceId.toString() in preferences.disabledSources.get()
}
}
@@ -9,8 +9,8 @@ class ToggleSourcePin(
) {
fun await(source: Source) {
val isPinned = source.id.toString() in preferences.pinnedSources().get()
preferences.pinnedSources().getAndSet { pinned ->
val isPinned = source.id.toString() in preferences.pinnedSources.get()
preferences.pinnedSources.getAndSet { pinned ->
if (isPinned) pinned.minus("${source.id}") else pinned.plus("${source.id}")
}
}
@@ -10,68 +10,80 @@ import tachiyomi.core.common.preference.getLongArray
import tachiyomi.domain.library.model.LibraryDisplayMode
class SourcePreferences(
private val preferenceStore: PreferenceStore,
preferenceStore: PreferenceStore,
) {
fun sourceDisplayMode() = preferenceStore.getObjectFromString(
val sourceDisplayMode: Preference<LibraryDisplayMode> = preferenceStore.getObjectFromString(
"pref_display_mode_catalogue",
LibraryDisplayMode.default,
LibraryDisplayMode.Serializer::serialize,
LibraryDisplayMode.Serializer::deserialize,
)
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
val enabledLanguages: Preference<Set<String>> = preferenceStore.getStringSet(
"source_languages",
LocaleHelper.getDefaultEnabledLanguages(),
)
fun disabledSources() = preferenceStore.getStringSet("hidden_catalogues", emptySet())
val disabledSources: Preference<Set<String>> = preferenceStore.getStringSet("hidden_catalogues", emptySet())
fun incognitoExtensions() = preferenceStore.getStringSet("incognito_extensions", emptySet())
val incognitoExtensions: Preference<Set<String>> = preferenceStore.getStringSet("incognito_extensions", emptySet())
fun pinnedSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
val pinnedSources: Preference<Set<String>> = preferenceStore.getStringSet("pinned_catalogues", emptySet())
fun lastUsedSource() = preferenceStore.getLong(
val lastUsedSource: Preference<Long> = preferenceStore.getLong(
Preference.appStateKey("last_catalogue_source"),
-1,
)
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
val showNsfwSource: Preference<Boolean> = preferenceStore.getBoolean("show_nsfw_source", true)
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
val migrationSortingMode: Preference<SetMigrateSorting.Mode> = preferenceStore.getEnum(
"pref_migration_sorting",
SetMigrateSorting.Mode.ALPHABETICAL,
)
fun migrationSortingDirection() = preferenceStore.getEnum(
val migrationSortingDirection: Preference<SetMigrateSorting.Direction> = preferenceStore.getEnum(
"pref_migration_direction",
SetMigrateSorting.Direction.ASCENDING,
)
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
val hideInLibraryItems: Preference<Boolean> = preferenceStore.getBoolean("browse_hide_in_library_items", false)
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
val extensionRepos: Preference<Set<String>> = preferenceStore.getStringSet("extension_repos", emptySet())
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
val extensionUpdatesCount: Preference<Int> = preferenceStore.getInt("ext_updates_count", 0)
fun trustedExtensions() = preferenceStore.getStringSet(
val trustedExtensions: Preference<Set<String>> = preferenceStore.getStringSet(
Preference.appStateKey("trusted_extensions"),
emptySet(),
)
fun globalSearchFilterState() = preferenceStore.getBoolean(
val globalSearchFilterState: Preference<Boolean> = preferenceStore.getBoolean(
Preference.appStateKey("has_filters_toggle_state"),
false,
)
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
val migrationSources: Preference<List<Long>> = preferenceStore.getLongArray("migration_sources", emptyList())
fun migrationFlags() = preferenceStore.getObjectFromInt(
val migrationFlags: Preference<Set<MigrationFlag>> = preferenceStore.getObjectFromInt(
key = "migration_flags",
defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) },
deserializer = { value: Int -> MigrationFlag.fromBit(value) },
)
fun migrationDeepSearchMode() = preferenceStore.getBoolean("migration_deep_search", false)
val migrationDeepSearchMode: Preference<Boolean> = preferenceStore.getBoolean("migration_deep_search", false)
fun migrationPrioritizeByChapters() = preferenceStore.getBoolean("migration_prioritize_by_chapters", false)
val migrationPrioritizeByChapters: Preference<Boolean> = preferenceStore.getBoolean(
"migration_prioritize_by_chapters",
false,
)
fun migrationHideUnmatched() = preferenceStore.getBoolean("migration_hide_unmatched", false)
val migrationHideUnmatched: Preference<Boolean> = preferenceStore.getBoolean("migration_hide_unmatched", false)
fun migrationHideWithoutUpdates() = preferenceStore.getBoolean("migration_hide_without_updates", false)
val migrationHideWithoutUpdates: Preference<Boolean> = preferenceStore.getBoolean(
"migration_hide_without_updates",
false,
)
}
@@ -34,11 +34,11 @@ class TrackPreferences(
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
val anilistScoreType: Preference<String> = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
val autoUpdateTrack: Preference<Boolean> = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
val autoUpdateTrackOnMarkRead: Preference<AutoTrackState> = preferenceStore.getEnum(
"pref_auto_update_manga_on_mark_read",
AutoTrackState.ALWAYS,
)
@@ -5,6 +5,7 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum
import java.time.format.DateTimeFormatter
@@ -12,12 +13,12 @@ import java.time.format.FormatStyle
import java.util.Locale
class UiPreferences(
private val preferenceStore: PreferenceStore,
preferenceStore: PreferenceStore,
) {
fun themeMode() = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
val themeMode: Preference<ThemeMode> = preferenceStore.getEnum("pref_theme_mode_key", ThemeMode.SYSTEM)
fun appTheme() = preferenceStore.getEnum(
val appTheme: Preference<AppTheme> = preferenceStore.getEnum(
"pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) {
AppTheme.MONET
@@ -26,15 +27,15 @@ class UiPreferences(
},
)
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
val themeDarkAmoled: Preference<Boolean> = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
val relativeTime: Preference<Boolean> = preferenceStore.getBoolean("relative_time_v2", true)
fun dateFormat() = preferenceStore.getString("app_date_format", "")
val dateFormat: Preference<String> = preferenceStore.getString("app_date_format", "")
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
val tabletUiMode: Preference<TabletUiMode> = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
fun imagesInDescription() = preferenceStore.getBoolean("pref_render_images_description", true)
val imagesInDescription: Preference<Boolean> = preferenceStore.getBoolean("pref_render_images_description", true)
companion object {
fun dateFormat(format: String): DateTimeFormatter = when (format) {
@@ -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,
@@ -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
@@ -195,7 +196,7 @@ fun AppBarActions(
actions.filterIsInstance<AppBar.Action>().map {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(it.title)
@@ -220,7 +221,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 +350,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 +370,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,6 +1,5 @@
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
@@ -14,11 +13,11 @@ 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,7 +48,7 @@ fun DownloadDropdownMenu(
}
@Composable
private fun ColumnScope.DownloadDropdownMenuItems(
private fun DownloadDropdownMenuItems(
onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
) {
@@ -59,6 +58,7 @@ private fun ColumnScope.DownloadDropdownMenuItems(
DownloadAction.NEXT_10_CHAPTERS to pluralStringResource(MR.plurals.download_amount, 10, 10),
DownloadAction.NEXT_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) ->
@@ -78,9 +78,9 @@ fun LibrarySettingsDialog(
private fun ColumnScope.FilterPage(
screenModel: LibrarySettingsScreenModel,
) {
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded.collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly.collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_downloaded),
@@ -92,25 +92,25 @@ private fun ColumnScope.FilterPage(
enabled = !downloadedOnly,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterDownloaded) },
)
val filterUnread by screenModel.libraryPreferences.filterUnread().collectAsState()
val filterUnread by screenModel.libraryPreferences.filterUnread.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_unread),
state = filterUnread,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterUnread) },
)
val filterStarted by screenModel.libraryPreferences.filterStarted().collectAsState()
val filterStarted by screenModel.libraryPreferences.filterStarted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_started),
state = filterStarted,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterStarted) },
)
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked().collectAsState()
val filterBookmarked by screenModel.libraryPreferences.filterBookmarked.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked,
onClick = { screenModel.toggleFilter(LibraryPreferences::filterBookmarked) },
)
val filterCompleted by screenModel.libraryPreferences.filterCompleted().collectAsState()
val filterCompleted by screenModel.libraryPreferences.filterCompleted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.completed),
state = filterCompleted,
@@ -118,7 +118,7 @@ private fun ColumnScope.FilterPage(
)
// TODO: re-enable when custom intervals are ready for stable
if ((!isReleaseBuildType) && LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) {
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState()
val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_interval_custom),
state = filterIntervalCustom,
@@ -229,7 +229,7 @@ private val displayModes = listOf(
private fun ColumnScope.DisplayPage(
screenModel: LibrarySettingsScreenModel,
) {
val displayMode by screenModel.libraryPreferences.displayMode().collectAsState()
val displayMode by screenModel.libraryPreferences.displayMode.collectAsState()
SettingsChipRow(MR.strings.action_display_mode) {
displayModes.map { (titleRes, mode) ->
FilterChip(
@@ -244,9 +244,9 @@ private fun ColumnScope.DisplayPage(
val configuration = LocalConfiguration.current
val columnPreference = remember {
if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenModel.libraryPreferences.landscapeColumns()
screenModel.libraryPreferences.landscapeColumns
} else {
screenModel.libraryPreferences.portraitColumns()
screenModel.libraryPreferences.portraitColumns
}
}
@@ -268,32 +268,32 @@ private fun ColumnScope.DisplayPage(
HeadingItem(MR.strings.overlay_header)
CheckboxItem(
label = stringResource(MR.strings.action_display_download_badge),
pref = screenModel.libraryPreferences.downloadBadge(),
pref = screenModel.libraryPreferences.downloadBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_unread_badge),
pref = screenModel.libraryPreferences.unreadBadge(),
pref = screenModel.libraryPreferences.unreadBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_local_badge),
pref = screenModel.libraryPreferences.localBadge(),
pref = screenModel.libraryPreferences.localBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_language_badge),
pref = screenModel.libraryPreferences.languageBadge(),
pref = screenModel.libraryPreferences.languageBadge,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_show_continue_reading_button),
pref = screenModel.libraryPreferences.showContinueReadingButton(),
pref = screenModel.libraryPreferences.showContinueReadingButton,
)
HeadingItem(MR.strings.tabs_header)
CheckboxItem(
label = stringResource(MR.strings.action_display_show_tabs),
pref = screenModel.libraryPreferences.categoryTabs(),
pref = screenModel.libraryPreferences.categoryTabs,
)
CheckboxItem(
label = stringResource(MR.strings.action_display_show_number_of_items),
pref = screenModel.libraryPreferences.categoryNumberOfItems(),
pref = screenModel.libraryPreferences.categoryNumberOfItems,
)
}
@@ -66,7 +66,7 @@ fun ChapterSettingsDialog(
)
}
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly().get() }
val downloadedOnly = remember { Injekt.get<BasePreferences>().downloadedOnly.get() }
TabbedDialog(
onDismissRequest = onDismissRequest,
@@ -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)
@@ -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",
@@ -125,7 +125,7 @@ internal class PermissionStep : OnboardingStep {
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
val crashlyticsPref = privacyPreferences.crashlytics()
val crashlyticsPref = privacyPreferences.crashlytics
val crashlytics by crashlyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_crashlytics),
@@ -134,7 +134,7 @@ internal class PermissionStep : OnboardingStep {
onToggleChange = crashlyticsPref::set,
)
val analyticsPref = privacyPreferences.analytics()
val analyticsPref = privacyPreferences.analytics
val analytics by analyticsPref.collectAsState()
PermissionSwitch(
title = stringResource(MR.strings.onboarding_permission_analytics),
@@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
internal class StorageStep : OnboardingStep {
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory()
private val storagePref = Injekt.get<StoragePreferences>().baseStorageDirectory
private var _isComplete by mutableStateOf(false)
@@ -19,13 +19,13 @@ internal class ThemeStep : OnboardingStep {
@Composable
override fun Content() {
val themeModePref = uiPreferences.themeMode()
val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState()
Column {
@@ -27,6 +27,7 @@ sealed class Preference {
override val title: String,
override val subtitle: String? = null,
override val enabled: Boolean = true,
val widget: @Composable (() -> Unit)? = null,
val onClick: (() -> Unit)? = null,
) : PreferenceItem<String, Unit>() {
override val icon: ImageVector? = null
@@ -147,6 +147,7 @@ internal fun PreferenceItem(
title = item.title,
subtitle = item.subtitle,
icon = item.icon,
widget = item.widget,
onPreferenceClick = item.onClick,
)
}
@@ -99,7 +99,7 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = networkPreferences.verboseLogging(),
preference = networkPreferences.verboseLogging,
title = stringResource(MR.strings.pref_verbose_logging),
subtitle = stringResource(MR.strings.pref_verbose_logging_summary),
onValueChanged = {
@@ -204,7 +204,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val context = LocalContext.current
val networkHelper = remember { Injekt.get<NetworkHelper>() }
val userAgentPref = networkPreferences.defaultUserAgent()
val userAgentPref = networkPreferences.defaultUserAgent
val userAgent by userAgentPref.collectAsState()
return Preference.PreferenceGroup(
@@ -238,7 +238,7 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = networkPreferences.dohProvider(),
preference = networkPreferences.dohProvider,
entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled),
PREF_DOH_CLOUDFLARE to "Cloudflare",
@@ -319,12 +319,12 @@ object SettingsAdvancedScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.updateMangaTitles(),
preference = libraryPreferences.updateMangaTitles,
title = stringResource(MR.strings.pref_update_library_manga_titles),
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.disallowNonAsciiFilenames(),
preference = libraryPreferences.disallowNonAsciiFilenames,
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
),
@@ -343,14 +343,14 @@ object SettingsAdvancedScreen : SearchableSettings {
uri?.let {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
basePreferences.displayProfile().set(uri.toString())
basePreferences.displayProfile.set(uri.toString())
}
}
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_reader),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = basePreferences.hardwareBitmapThreshold(),
preference = basePreferences.hardwareBitmapThreshold,
entries = GLUtil.CUSTOM_TEXTURE_LIMIT_OPTIONS
.mapIndexed { index, option ->
val display = if (index == 0) {
@@ -370,13 +370,13 @@ object SettingsAdvancedScreen : SearchableSettings {
GLUtil.DEVICE_TEXTURE_LIMIT > GLUtil.SAFE_TEXTURE_LIMIT,
),
Preference.PreferenceItem.SwitchPreference(
preference = basePreferences.alwaysDecodeLongStripWithSSIV(),
preference = basePreferences.alwaysDecodeLongStripWithSSIV,
title = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_2),
subtitle = stringResource(MR.strings.pref_always_decode_long_strip_with_ssiv_summary),
),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_display_profile),
subtitle = basePreferences.displayProfile().get(),
subtitle = basePreferences.displayProfile.get(),
onClick = {
chooseColorProfile.launch(arrayOf("*/*"))
},
@@ -391,7 +391,7 @@ object SettingsAdvancedScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val uriHandler = LocalUriHandler.current
val extensionInstallerPref = basePreferences.extensionInstaller()
val extensionInstallerPref = basePreferences.extensionInstaller
var shizukuMissing by rememberSaveable { mutableStateOf(false) }
val trustExtension = remember { Injekt.get<TrustExtension>() }
@@ -50,13 +50,13 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val themeModePref = uiPreferences.themeMode()
val themeModePref = uiPreferences.themeMode
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appThemePref = uiPreferences.appTheme
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoledPref = uiPreferences.themeDarkAmoled
val amoled by amoledPref.collectAsState()
return Preference.PreferenceGroup(
@@ -103,7 +103,7 @@ object SettingsAppearanceScreen : SearchableSettings {
val now = remember { LocalDate.now() }
val dateFormat by uiPreferences.dateFormat().collectAsState()
val dateFormat by uiPreferences.dateFormat.collectAsState()
val formattedNow = remember(dateFormat) {
UiPreferences.dateFormat(dateFormat).format(now)
}
@@ -116,7 +116,7 @@ object SettingsAppearanceScreen : SearchableSettings {
onClick = { navigator.push(AppLanguageScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.tabletUiMode(),
preference = uiPreferences.tabletUiMode,
entries = TabletUiMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
@@ -127,7 +127,7 @@ object SettingsAppearanceScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = uiPreferences.dateFormat(),
preference = uiPreferences.dateFormat,
entries = DateFormats
.associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now)
@@ -137,7 +137,7 @@ object SettingsAppearanceScreen : SearchableSettings {
title = stringResource(MR.strings.pref_date_format),
),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.relativeTime(),
preference = uiPreferences.relativeTime,
title = stringResource(MR.strings.pref_relative_format),
subtitle = stringResource(
MR.strings.pref_relative_format_summary,
@@ -146,7 +146,7 @@ object SettingsAppearanceScreen : SearchableSettings {
),
),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.imagesInDescription(),
preference = uiPreferences.imagesInDescription,
title = stringResource(MR.strings.pref_display_images_description),
),
),
@@ -43,7 +43,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.label_sources),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.hideInLibraryItems(),
preference = sourcePreferences.hideInLibraryItems,
title = stringResource(MR.strings.pref_hide_in_library_items),
),
Preference.PreferenceItem.TextPreference(
@@ -59,7 +59,7 @@ object SettingsBrowseScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_nsfw_content),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = sourcePreferences.showNsfwSource(),
preference = sourcePreferences.showNsfwSource,
title = stringResource(MR.strings.pref_show_nsfw_source),
subtitle = stringResource(MR.strings.requires_app_restart),
onValueChanged = {
@@ -167,11 +167,11 @@ object SettingsDataScreen : SearchableSettings {
storagePreferences: StoragePreferences,
): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory)
return Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_storage_location),
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
subtitle = storageLocationText(storagePreferences.baseStorageDirectory),
onClick = {
try {
pickStorageLocation.launch(null)
@@ -187,7 +187,7 @@ object SettingsDataScreen : SearchableSettings {
val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp().collectAsState()
val lastAutoBackup by backupPreferences.lastAutoBackupTimestamp.collectAsState()
val chooseBackup = rememberLauncherForActivityResult(
object : ActivityResultContracts.GetContent() {
@@ -254,7 +254,7 @@ object SettingsDataScreen : SearchableSettings {
// Automatic backups
Preference.PreferenceItem.ListPreference(
preference = backupPreferences.backupInterval(),
preference = backupPreferences.backupInterval,
entries = persistentMapOf(
0 to stringResource(MR.strings.off),
6 to stringResource(MR.strings.update_6hour),
@@ -321,7 +321,7 @@ object SettingsDataScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoClearChapterCache(),
preference = libraryPreferences.autoClearChapterCache,
title = stringResource(MR.strings.pref_auto_clear_chapter_cache),
),
),
@@ -37,19 +37,19 @@ object SettingsDownloadScreen : SearchableSettings {
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState()
val parallelSourceLimit by downloadPreferences.parallelSourceLimit.collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit.collectAsState()
return listOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.downloadOnlyOverWifi(),
preference = downloadPreferences.downloadOnlyOverWifi,
title = stringResource(MR.strings.connected_to_wifi),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.saveChaptersAsCBZ(),
preference = downloadPreferences.saveChaptersAsCBZ,
title = stringResource(MR.strings.save_chapter_as_cbz),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.splitTallImages(),
preference = downloadPreferences.splitTallImages,
title = stringResource(MR.strings.split_tall_images),
subtitle = stringResource(MR.strings.split_tall_images_summary),
),
@@ -57,14 +57,14 @@ object SettingsDownloadScreen : SearchableSettings {
value = parallelSourceLimit,
valueRange = 1..10,
title = stringResource(MR.strings.pref_download_concurrent_sources),
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) },
onValueChanged = { downloadPreferences.parallelSourceLimit.set(it) },
),
Preference.PreferenceItem.SliderPreference(
value = parallelPageLimit,
valueRange = 1..15,
title = stringResource(MR.strings.pref_download_concurrent_pages),
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) },
onValueChanged = { downloadPreferences.parallelPageLimit.set(it) },
),
getDeleteChaptersGroup(
downloadPreferences = downloadPreferences,
@@ -87,11 +87,11 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_delete_chapters),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeAfterMarkedAsRead(),
preference = downloadPreferences.removeAfterMarkedAsRead,
title = stringResource(MR.strings.pref_remove_after_marked_as_read),
),
Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.removeAfterReadSlots(),
preference = downloadPreferences.removeAfterReadSlots,
entries = persistentMapOf(
-1 to stringResource(MR.strings.disabled),
0 to stringResource(MR.strings.last_read_chapter),
@@ -103,7 +103,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.pref_remove_after_read),
),
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.removeBookmarkedChapters(),
preference = downloadPreferences.removeBookmarkedChapters,
title = stringResource(MR.strings.pref_remove_bookmarked_chapters),
),
getExcludedCategoriesPreference(
@@ -120,7 +120,7 @@ object SettingsDownloadScreen : SearchableSettings {
categories: () -> List<Category>,
): Preference.PreferenceItem.MultiSelectListPreference {
return Preference.PreferenceItem.MultiSelectListPreference(
preference = downloadPreferences.removeExcludeCategories(),
preference = downloadPreferences.removeExcludeCategories,
entries = categories()
.associate { it.id.toString() to it.visualName }
.toImmutableMap(),
@@ -133,10 +133,10 @@ object SettingsDownloadScreen : SearchableSettings {
downloadPreferences: DownloadPreferences,
allCategories: List<Category>,
): Preference.PreferenceGroup {
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude
val downloadNewChapters by downloadNewChaptersPref.collectAsState()
@@ -194,7 +194,7 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.download_ahead),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = downloadPreferences.autoDownloadWhileReading(),
preference = downloadPreferences.autoDownloadWhileReading,
entries = listOf(0, 2, 3, 5, 10)
.associateWith {
if (it == 0) {
@@ -73,7 +73,7 @@ object SettingsLibraryScreen : SearchableSettings {
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
// For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
val ids = listOf(libraryPreferences.defaultCategory.defaultValue()) +
allCategories.fastMap { it.id.toInt() }
val labels = listOf(stringResource(MR.strings.default_category_summary)) +
allCategories.fastMap { it.visualName }
@@ -91,12 +91,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { navigator.push(CategoryScreen()) },
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.defaultCategory(),
preference = libraryPreferences.defaultCategory,
entries = ids.zip(labels).toMap().toImmutableMap(),
title = stringResource(MR.strings.default_category),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.categorizedDisplaySettings(),
preference = libraryPreferences.categorizedDisplaySettings,
title = stringResource(MR.strings.categorized_display_settings),
onValueChanged = {
if (!it) {
@@ -118,9 +118,9 @@ object SettingsLibraryScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval()
val autoUpdateCategoriesPref = libraryPreferences.updateCategories()
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude()
val autoUpdateIntervalPref = libraryPreferences.autoUpdateInterval
val autoUpdateCategoriesPref = libraryPreferences.updateCategories
val autoUpdateCategoriesExcludePref = libraryPreferences.updateCategoriesExclude
val autoUpdateInterval by autoUpdateIntervalPref.collectAsState()
@@ -164,7 +164,7 @@ object SettingsLibraryScreen : SearchableSettings {
},
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateDeviceRestrictions(),
preference = libraryPreferences.autoUpdateDeviceRestrictions,
entries = persistentMapOf(
DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
@@ -189,12 +189,12 @@ object SettingsLibraryScreen : SearchableSettings {
onClick = { showCategoriesDialog = true },
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.autoUpdateMetadata(),
preference = libraryPreferences.autoUpdateMetadata,
title = stringResource(MR.strings.pref_library_update_refresh_metadata),
subtitle = stringResource(MR.strings.pref_library_update_refresh_metadata_summary),
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.autoUpdateMangaRestrictions(),
preference = libraryPreferences.autoUpdateMangaRestrictions,
entries = persistentMapOf(
MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
@@ -204,7 +204,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_library_update_smart_update),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.newShowUpdatesCount(),
preference = libraryPreferences.newShowUpdatesCount,
title = stringResource(MR.strings.pref_library_update_show_tab_badge),
),
),
@@ -219,7 +219,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_behavior),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToStartAction(),
preference = libraryPreferences.swipeToStartAction,
entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
@@ -233,7 +233,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_start),
),
Preference.PreferenceItem.ListPreference(
preference = libraryPreferences.swipeToEndAction(),
preference = libraryPreferences.swipeToEndAction,
entries = persistentMapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to
stringResource(MR.strings.disabled),
@@ -247,7 +247,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_chapter_swipe_end),
),
Preference.PreferenceItem.MultiSelectListPreference(
preference = libraryPreferences.markDuplicateReadChapterAsRead(),
preference = libraryPreferences.markDuplicateReadChapterAsRead,
entries = persistentMapOf(
MARK_DUPLICATE_CHAPTER_READ_EXISTING to
stringResource(MR.strings.pref_mark_duplicate_read_chapter_read_existing),
@@ -257,7 +257,7 @@ object SettingsLibraryScreen : SearchableSettings {
title = stringResource(MR.strings.pref_mark_duplicate_read_chapter_read),
),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.hideMissingChapters(),
preference = libraryPreferences.hideMissingChapters,
title = stringResource(MR.strings.pref_hide_missing_chapter_indicators),
),
),
@@ -33,14 +33,14 @@ object SettingsReaderScreen : SearchableSettings {
return listOf(
Preference.PreferenceItem.ListPreference(
preference = readerPref.defaultReadingMode(),
preference = readerPref.defaultReadingMode,
entries = ReadingMode.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(),
title = stringResource(MR.strings.pref_viewer_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPref.doubleTapAnimSpeed(),
preference = readerPref.doubleTapAnimSpeed,
entries = persistentMapOf(
1 to stringResource(MR.strings.double_tap_anim_speed_0),
500 to stringResource(MR.strings.double_tap_anim_speed_normal),
@@ -49,17 +49,17 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_double_tap_anim_speed),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showReadingMode(),
preference = readerPref.showReadingMode,
title = stringResource(MR.strings.pref_show_reading_mode),
subtitle = stringResource(MR.strings.pref_show_reading_mode_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.showNavigationOverlayOnStart(),
preference = readerPref.showNavigationOverlayOnStart,
title = stringResource(MR.strings.pref_show_navigation_mode),
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPref.pageTransitions(),
preference = readerPref.pageTransitions,
title = stringResource(MR.strings.pref_page_transitions),
),
getDisplayGroup(readerPreferences = readerPref),
@@ -74,20 +74,20 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreenPref = readerPreferences.fullscreen()
val fullscreenPref = readerPreferences.fullscreen
val fullscreen by fullscreenPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_display),
preferenceItems = persistentListOf(
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.defaultOrientationType(),
preference = readerPreferences.defaultOrientationType,
entries = ReaderOrientation.entries.drop(1)
.associate { it.flagValue to stringResource(it.stringRes) }
.toImmutableMap(),
title = stringResource(MR.strings.pref_rotation_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerTheme(),
preference = readerPreferences.readerTheme,
entries = persistentMapOf(
1 to stringResource(MR.strings.black_background),
2 to stringResource(MR.strings.gray_background),
@@ -101,16 +101,16 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.drawUnderCutout(),
preference = readerPreferences.drawUnderCutout,
title = stringResource(MR.strings.pref_cutout_short),
enabled = LocalView.current.hasDisplayCutout() && fullscreen,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.keepScreenOn(),
preference = readerPreferences.keepScreenOn,
title = stringResource(MR.strings.pref_keep_screen_on),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.showPageNumber(),
preference = readerPreferences.showPageNumber,
title = stringResource(MR.strings.pref_show_page_number),
),
),
@@ -119,21 +119,21 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
val flashPageState by readerPreferences.flashOnPageChange.collectAsState()
val flashMillisPref = readerPreferences.flashDurationMillis()
val flashMillisPref = readerPreferences.flashDurationMillis
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = readerPreferences.flashPageInterval()
val flashIntervalPref = readerPreferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor()
val flashColorPref = readerPreferences.flashColor
return Preference.PreferenceGroup(
title = "E-Ink",
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.flashOnPageChange(),
preference = readerPreferences.flashOnPageChange,
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
@@ -174,19 +174,19 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_category_reading),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipRead(),
preference = readerPreferences.skipRead,
title = stringResource(MR.strings.pref_skip_read_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipFiltered(),
preference = readerPreferences.skipFiltered,
title = stringResource(MR.strings.pref_skip_filtered_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.skipDupe(),
preference = readerPreferences.skipDupe,
title = stringResource(MR.strings.pref_skip_dupe_chapters),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.alwaysShowChapterTransition(),
preference = readerPreferences.alwaysShowChapterTransition,
title = stringResource(MR.strings.pref_always_show_chapter_transition),
),
),
@@ -195,10 +195,10 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getPagedGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val navModePref = readerPreferences.navigationModePager()
val imageScaleTypePref = readerPreferences.imageScaleType()
val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
val rotateToFitPref = readerPreferences.dualPageRotateToFit()
val navModePref = readerPreferences.navigationModePager
val imageScaleTypePref = readerPreferences.imageScaleType
val dualPageSplitPref = readerPreferences.dualPageSplitPaged
val rotateToFitPref = readerPreferences.dualPageRotateToFit
val navMode by navModePref.collectAsState()
val imageScaleType by imageScaleTypePref.collectAsState()
@@ -217,7 +217,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.pagerNavInverted(),
preference = readerPreferences.pagerNavInverted,
entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -238,7 +238,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_image_scale_type),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.zoomStart(),
preference = readerPreferences.zoomStart,
entries = ReaderPreferences.ZoomStart
.mapIndexed { index, it -> index + 1 to stringResource(it) }
.toMap()
@@ -246,16 +246,16 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_zoom_start),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBorders(),
preference = readerPreferences.cropBorders,
title = stringResource(MR.strings.pref_crop_borders),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.landscapeZoom(),
preference = readerPreferences.landscapeZoom,
title = stringResource(MR.strings.pref_landscape_zoom),
enabled = imageScaleType == 1,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.navigateToPan(),
preference = readerPreferences.navigateToPan,
title = stringResource(MR.strings.pref_navigate_pan),
enabled = navMode != 5,
),
@@ -268,7 +268,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertPaged(),
preference = readerPreferences.dualPageInvertPaged,
title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit,
@@ -282,7 +282,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvert(),
preference = readerPreferences.dualPageRotateToFitInvert,
title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit,
),
@@ -294,10 +294,10 @@ object SettingsReaderScreen : SearchableSettings {
private fun getWebtoonGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val numberFormat = remember { NumberFormat.getPercentInstance() }
val navModePref = readerPreferences.navigationModeWebtoon()
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon()
val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon()
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding()
val navModePref = readerPreferences.navigationModeWebtoon
val dualPageSplitPref = readerPreferences.dualPageSplitWebtoon
val rotateToFitPref = readerPreferences.dualPageRotateToFitWebtoon
val webtoonSidePaddingPref = readerPreferences.webtoonSidePadding
val navMode by navModePref.collectAsState()
val dualPageSplit by dualPageSplitPref.collectAsState()
@@ -316,7 +316,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_viewer_nav),
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.webtoonNavInverted(),
preference = readerPreferences.webtoonNavInverted,
entries = persistentListOf(
ReaderPreferences.TappingInvertMode.NONE,
ReaderPreferences.TappingInvertMode.HORIZONTAL,
@@ -338,7 +338,7 @@ object SettingsReaderScreen : SearchableSettings {
onValueChanged = { webtoonSidePaddingPref.set(it) },
),
Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(),
preference = readerPreferences.readerHideThreshold,
entries = persistentMapOf(
ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
@@ -348,7 +348,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_hide_threshold),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cropBordersWebtoon(),
preference = readerPreferences.cropBordersWebtoon,
title = stringResource(MR.strings.pref_crop_borders),
),
Preference.PreferenceItem.SwitchPreference(
@@ -360,7 +360,7 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageInvertWebtoon(),
preference = readerPreferences.dualPageInvertWebtoon,
title = stringResource(MR.strings.pref_dual_page_invert),
subtitle = stringResource(MR.strings.pref_dual_page_invert_summary),
enabled = dualPageSplit,
@@ -374,16 +374,16 @@ object SettingsReaderScreen : SearchableSettings {
},
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.dualPageRotateToFitInvertWebtoon(),
preference = readerPreferences.dualPageRotateToFitInvertWebtoon,
title = stringResource(MR.strings.pref_page_rotate_invert),
enabled = rotateToFit,
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDoubleTapZoomEnabled(),
preference = readerPreferences.webtoonDoubleTapZoomEnabled,
title = stringResource(MR.strings.pref_double_tap_zoom),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.webtoonDisableZoomOut(),
preference = readerPreferences.webtoonDisableZoomOut,
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
),
),
@@ -392,7 +392,7 @@ object SettingsReaderScreen : SearchableSettings {
@Composable
private fun getNavigationGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys()
val readWithVolumeKeysPref = readerPreferences.readWithVolumeKeys
val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_reader_navigation),
@@ -402,7 +402,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_read_with_volume_keys),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithVolumeKeysInverted(),
preference = readerPreferences.readWithVolumeKeysInverted,
title = stringResource(MR.strings.pref_read_with_volume_keys_inverted),
enabled = readWithVolumeKeys,
),
@@ -416,11 +416,11 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_reader_actions),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.readWithLongTap(),
preference = readerPreferences.readWithLongTap,
title = stringResource(MR.strings.pref_read_with_long_tap),
),
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.folderPerManga(),
preference = readerPreferences.folderPerManga,
title = stringResource(MR.strings.pref_create_folder_per_manga),
subtitle = stringResource(MR.strings.pref_create_folder_per_manga_summary),
),
@@ -45,7 +45,7 @@ object SettingsSecurityScreen : SearchableSettings {
): Preference.PreferenceGroup {
val context = LocalContext.current
val authSupported = remember { context.isAuthenticationSupported() }
val useAuthPref = securityPreferences.useAuthenticator()
val useAuthPref = securityPreferences.useAuthenticator
val useAuth by useAuthPref.collectAsState()
return Preference.PreferenceGroup(
@@ -62,7 +62,7 @@ object SettingsSecurityScreen : SearchableSettings {
},
),
Preference.PreferenceItem.ListPreference(
preference = securityPreferences.lockAppAfter(),
preference = securityPreferences.lockAppAfter,
entries = LockAfterValues
.associateWith {
when (it) {
@@ -82,11 +82,11 @@ object SettingsSecurityScreen : SearchableSettings {
),
Preference.PreferenceItem.SwitchPreference(
preference = securityPreferences.hideNotificationContent(),
preference = securityPreferences.hideNotificationContent,
title = stringResource(MR.strings.hide_notification_content),
),
Preference.PreferenceItem.ListPreference(
preference = securityPreferences.secureScreen(),
preference = securityPreferences.secureScreen,
entries = SecurityPreferences.SecureScreenMode.entries
.associateWith { stringResource(it.titleRes) }
.toImmutableMap(),
@@ -105,12 +105,12 @@ object SettingsSecurityScreen : SearchableSettings {
title = stringResource(MR.strings.pref_firebase),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.crashlytics(),
preference = privacyPreferences.crashlytics,
title = stringResource(MR.strings.onboarding_permission_crashlytics),
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
),
Preference.PreferenceItem.SwitchPreference(
preference = privacyPreferences.analytics(),
preference = privacyPreferences.analytics,
title = stringResource(MR.strings.onboarding_permission_analytics),
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
),
@@ -90,7 +90,7 @@ object SettingsTrackingScreen : SearchableSettings {
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
val trackerManager = remember { Injekt.get<TrackerManager>() }
val sourceManager = remember { Injekt.get<SourceManager>() }
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead
var dialog by remember { mutableStateOf<Any?>(null) }
dialog?.run {
@@ -128,11 +128,11 @@ object SettingsTrackingScreen : SearchableSettings {
return listOf(
Preference.PreferenceItem.SwitchPreference(
preference = trackPreferences.autoUpdateTrack(),
preference = trackPreferences.autoUpdateTrack,
title = stringResource(MR.strings.pref_auto_update_manga_sync),
),
Preference.PreferenceItem.ListPreference(
preference = trackPreferences.autoUpdateTrackOnMarkRead(),
preference = trackPreferences.autoUpdateTrackOnMarkRead,
entries = AutoTrackState.entries
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
@@ -225,7 +225,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 +275,7 @@ object AboutScreen : Screen() {
)
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
Injekt.get<UiPreferences>().dateFormat.get(),
),
)
} catch (e: Exception) {
@@ -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,
@@ -1,6 +1,7 @@
package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
@@ -61,6 +62,7 @@ fun ExtensionRepoCreateDialog(
OutlinedTextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
value = name,
onValueChange = { name = it },
@@ -1,24 +1,38 @@
package eu.kanade.presentation.more.settings.screen.debug
import android.os.Build
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import androidx.profileinstaller.ProfileVerifier
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.copyToClipboard
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import mihon.core.common.FeatureFlags
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DebugInfoScreen : Screen() {
@@ -47,6 +61,12 @@ class DebugInfoScreen : Screen() {
@Composable
private fun getAppInfoGroup(): Preference.PreferenceGroup {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val installationIdPref = remember { Injekt.get<BasePreferences>().installationId }
val installationId by installationIdPref.collectAsState()
return Preference.PreferenceGroup(
title = "App info",
preferenceItems = persistentListOf(
@@ -58,6 +78,28 @@ class DebugInfoScreen : Screen() {
title = "Build time",
subtitle = AboutScreen.getFormattedBuildTime(),
),
Preference.PreferenceItem.TextPreference(
title = "Installation ID",
subtitle = installationId,
widget = {
IconButton(
onClick = {
scope.launch {
installationIdPref.set(FeatureFlags.newInstallationId())
}
},
) {
Icon(
imageVector = Icons.Outlined.Autorenew,
tint = MaterialTheme.colorScheme.primary,
contentDescription = null,
)
}
},
onClick = {
context.copyToClipboard(installationId, installationId)
},
),
getProfileVerifierPreference(),
Preference.PreferenceItem.TextPreference(
title = "WebView version",
@@ -156,7 +156,7 @@ class WorkerInfoScreen : Screen() {
)
.toDateTimestampString(
UiPreferences.dateFormat(
Injekt.get<UiPreferences>().dateFormat().get(),
Injekt.get<UiPreferences>().dateFormat.get(),
),
)
appendLine("Next scheduled run: $timestamp")
@@ -24,10 +24,10 @@ class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis()
internal val flashMode = readerPreferences.flashColor()
internal val flashMillis = readerPreferences.flashDurationMillis
internal val flashMode = readerPreferences.flashColor
internal val flashIntervalPref = readerPreferences.flashPageInterval()
internal val flashIntervalPref = readerPreferences.flashPageInterval
// Internal State for Flash
private var flashInterval = flashIntervalPref.get()
@@ -22,44 +22,44 @@ import tachiyomi.presentation.core.util.collectAsState
@Composable
internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel) {
val customBrightness by screenModel.preferences.customBrightness().collectAsState()
val customBrightness by screenModel.preferences.customBrightness.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_custom_brightness),
pref = screenModel.preferences.customBrightness(),
pref = screenModel.preferences.customBrightness,
)
/**
/*
* Sets the brightness of the screen. Range is [-75, 100].
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
* From 1 to 100 it sets that value as brightness.
* 0 sets system brightness and hides the overlay.
*/
if (customBrightness) {
val customBrightnessValue by screenModel.preferences.customBrightnessValue().collectAsState()
val customBrightnessValue by screenModel.preferences.customBrightnessValue.collectAsState()
SliderItem(
value = customBrightnessValue,
valueRange = -75..100,
steps = 0,
label = stringResource(MR.strings.pref_custom_brightness),
onChange = { screenModel.preferences.customBrightnessValue().set(it) },
onChange = { screenModel.preferences.customBrightnessValue.set(it) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
}
val colorFilter by screenModel.preferences.colorFilter().collectAsState()
val colorFilter by screenModel.preferences.colorFilter.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_custom_color_filter),
pref = screenModel.preferences.colorFilter(),
pref = screenModel.preferences.colorFilter,
)
if (colorFilter) {
val colorFilterValue by screenModel.preferences.colorFilterValue().collectAsState()
val colorFilterValue by screenModel.preferences.colorFilterValue.collectAsState()
SliderItem(
value = colorFilterValue.red,
valueRange = 0..255,
steps = 0,
label = stringResource(MR.strings.color_filter_r_value),
onChange = { newRValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newRValue, RED_MASK, 16)
}
},
@@ -71,7 +71,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_g_value),
onChange = { newGValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newGValue, GREEN_MASK, 8)
}
},
@@ -83,7 +83,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_b_value),
onChange = { newBValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newBValue, BLUE_MASK, 0)
}
},
@@ -95,19 +95,19 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
steps = 0,
label = stringResource(MR.strings.color_filter_a_value),
onChange = { newAValue ->
screenModel.preferences.colorFilterValue().getAndSet {
screenModel.preferences.colorFilterValue.getAndSet {
getColorValue(it, newAValue, ALPHA_MASK, 24)
}
},
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
val colorFilterMode by screenModel.preferences.colorFilterMode().collectAsState()
val colorFilterMode by screenModel.preferences.colorFilterMode.collectAsState()
SettingsChipRow(MR.strings.pref_color_filter_mode) {
ColorFilterMode.mapIndexed { index, it ->
FilterChip(
selected = colorFilterMode == index,
onClick = { screenModel.preferences.colorFilterMode().set(index) },
onClick = { screenModel.preferences.colorFilterMode.set(index) },
label = { Text(stringResource(it.first)) },
)
}
@@ -116,11 +116,11 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
CheckboxItem(
label = stringResource(MR.strings.pref_grayscale),
pref = screenModel.preferences.grayscale(),
pref = screenModel.preferences.grayscale,
)
CheckboxItem(
label = stringResource(MR.strings.pref_inverted_colors),
pref = screenModel.preferences.invertedColors(),
pref = screenModel.preferences.invertedColors,
)
}
@@ -33,24 +33,24 @@ private val flashColors = listOf(
@Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState()
val readerTheme by screenModel.preferences.readerTheme.collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange.collectAsState()
val flashMillisPref = screenModel.preferences.flashDurationMillis()
val flashMillisPref = screenModel.preferences.flashDurationMillis
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = screenModel.preferences.flashPageInterval()
val flashIntervalPref = screenModel.preferences.flashPageInterval
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor()
val flashColorPref = screenModel.preferences.flashColor
val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) ->
FilterChip(
selected = readerTheme == value,
onClick = { screenModel.preferences.readerTheme().set(value) },
onClick = { screenModel.preferences.readerTheme.set(value) },
label = { Text(stringResource(labelRes)) },
)
}
@@ -58,45 +58,45 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
CheckboxItem(
label = stringResource(MR.strings.pref_show_page_number),
pref = screenModel.preferences.showPageNumber(),
pref = screenModel.preferences.showPageNumber,
)
CheckboxItem(
label = stringResource(MR.strings.pref_fullscreen),
pref = screenModel.preferences.fullscreen(),
pref = screenModel.preferences.fullscreen,
)
val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
val isFullscreen by screenModel.preferences.fullscreen.collectAsState()
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.drawUnderCutout(),
pref = screenModel.preferences.drawUnderCutout,
)
}
CheckboxItem(
label = stringResource(MR.strings.pref_keep_screen_on),
pref = screenModel.preferences.keepScreenOn(),
pref = screenModel.preferences.keepScreenOn,
)
CheckboxItem(
label = stringResource(MR.strings.pref_read_with_long_tap),
pref = screenModel.preferences.readWithLongTap(),
pref = screenModel.preferences.readWithLongTap,
)
CheckboxItem(
label = stringResource(MR.strings.pref_always_show_chapter_transition),
pref = screenModel.preferences.alwaysShowChapterTransition(),
pref = screenModel.preferences.alwaysShowChapterTransition,
)
CheckboxItem(
label = stringResource(MR.strings.pref_page_transitions),
pref = screenModel.preferences.pageTransitions(),
pref = screenModel.preferences.pageTransitions,
)
CheckboxItem(
label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(),
pref = screenModel.preferences.flashOnPageChange,
)
if (flashPageState) {
SliderItem(
@@ -63,32 +63,32 @@ internal fun ColumnScope.ReadingModePage(screenModel: ReaderSettingsScreenModel)
private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenModel) {
HeadingItem(MR.strings.pager_viewer)
val navigationModePager by screenModel.preferences.navigationModePager().collectAsState()
val pagerNavInverted by screenModel.preferences.pagerNavInverted().collectAsState()
val navigationModePager by screenModel.preferences.navigationModePager.collectAsState()
val pagerNavInverted by screenModel.preferences.pagerNavInverted.collectAsState()
TapZonesItems(
selected = navigationModePager,
onSelect = screenModel.preferences.navigationModePager()::set,
onSelect = screenModel.preferences.navigationModePager::set,
invertMode = pagerNavInverted,
onSelectInvertMode = screenModel.preferences.pagerNavInverted()::set,
onSelectInvertMode = screenModel.preferences.pagerNavInverted::set,
)
val imageScaleType by screenModel.preferences.imageScaleType().collectAsState()
val imageScaleType by screenModel.preferences.imageScaleType.collectAsState()
SettingsChipRow(MR.strings.pref_image_scale_type) {
ReaderPreferences.ImageScaleType.mapIndexed { index, it ->
FilterChip(
selected = imageScaleType == index + 1,
onClick = { screenModel.preferences.imageScaleType().set(index + 1) },
onClick = { screenModel.preferences.imageScaleType.set(index + 1) },
label = { Text(stringResource(it)) },
)
}
}
val zoomStart by screenModel.preferences.zoomStart().collectAsState()
val zoomStart by screenModel.preferences.zoomStart.collectAsState()
SettingsChipRow(MR.strings.pref_zoom_start) {
ReaderPreferences.ZoomStart.mapIndexed { index, it ->
FilterChip(
selected = zoomStart == index + 1,
onClick = { screenModel.preferences.zoomStart().set(index + 1) },
onClick = { screenModel.preferences.zoomStart.set(index + 1) },
label = { Text(stringResource(it)) },
)
}
@@ -96,42 +96,42 @@ private fun ColumnScope.PagerViewerSettings(screenModel: ReaderSettingsScreenMod
CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBorders(),
pref = screenModel.preferences.cropBorders,
)
CheckboxItem(
label = stringResource(MR.strings.pref_landscape_zoom),
pref = screenModel.preferences.landscapeZoom(),
pref = screenModel.preferences.landscapeZoom,
)
CheckboxItem(
label = stringResource(MR.strings.pref_navigate_pan),
pref = screenModel.preferences.navigateToPan(),
pref = screenModel.preferences.navigateToPan,
)
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged().collectAsState()
val dualPageSplitPaged by screenModel.preferences.dualPageSplitPaged.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitPaged(),
pref = screenModel.preferences.dualPageSplitPaged,
)
if (dualPageSplitPaged) {
CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_invert),
pref = screenModel.preferences.dualPageInvertPaged(),
pref = screenModel.preferences.dualPageInvertPaged,
)
}
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit().collectAsState()
val dualPageRotateToFit by screenModel.preferences.dualPageRotateToFit.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFit(),
pref = screenModel.preferences.dualPageRotateToFit,
)
if (dualPageRotateToFit) {
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvert(),
pref = screenModel.preferences.dualPageRotateToFitInvert,
)
}
}
@@ -142,65 +142,65 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
HeadingItem(MR.strings.webtoon_viewer)
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon().collectAsState()
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted().collectAsState()
val navigationModeWebtoon by screenModel.preferences.navigationModeWebtoon.collectAsState()
val webtoonNavInverted by screenModel.preferences.webtoonNavInverted.collectAsState()
TapZonesItems(
selected = navigationModeWebtoon,
onSelect = screenModel.preferences.navigationModeWebtoon()::set,
onSelect = screenModel.preferences.navigationModeWebtoon::set,
invertMode = webtoonNavInverted,
onSelectInvertMode = screenModel.preferences.webtoonNavInverted()::set,
onSelectInvertMode = screenModel.preferences.webtoonNavInverted::set,
)
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding().collectAsState()
val webtoonSidePadding by screenModel.preferences.webtoonSidePadding.collectAsState()
SliderItem(
value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding),
valueString = numberFormat.format(webtoonSidePadding / 100f),
onChange = {
screenModel.preferences.webtoonSidePadding().set(it)
screenModel.preferences.webtoonSidePadding.set(it)
},
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
CheckboxItem(
label = stringResource(MR.strings.pref_crop_borders),
pref = screenModel.preferences.cropBordersWebtoon(),
pref = screenModel.preferences.cropBordersWebtoon,
)
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon().collectAsState()
val dualPageSplitWebtoon by screenModel.preferences.dualPageSplitWebtoon.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_split),
pref = screenModel.preferences.dualPageSplitWebtoon(),
pref = screenModel.preferences.dualPageSplitWebtoon,
)
if (dualPageSplitWebtoon) {
CheckboxItem(
label = stringResource(MR.strings.pref_dual_page_invert),
pref = screenModel.preferences.dualPageInvertWebtoon(),
pref = screenModel.preferences.dualPageInvertWebtoon,
)
}
val dualPageRotateToFitWebtoon by screenModel.preferences.dualPageRotateToFitWebtoon().collectAsState()
val dualPageRotateToFitWebtoon by screenModel.preferences.dualPageRotateToFitWebtoon.collectAsState()
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate),
pref = screenModel.preferences.dualPageRotateToFitWebtoon(),
pref = screenModel.preferences.dualPageRotateToFitWebtoon,
)
if (dualPageRotateToFitWebtoon) {
CheckboxItem(
label = stringResource(MR.strings.pref_page_rotate_invert),
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon(),
pref = screenModel.preferences.dualPageRotateToFitInvertWebtoon,
)
}
CheckboxItem(
label = stringResource(MR.strings.pref_double_tap_zoom),
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled(),
pref = screenModel.preferences.webtoonDoubleTapZoomEnabled,
)
CheckboxItem(
label = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
pref = screenModel.preferences.webtoonDisableZoomOut(),
pref = screenModel.preferences.webtoonDisableZoomOut,
)
}
@@ -1,10 +1,11 @@
package eu.kanade.presentation.theme
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialExpressiveTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme
@@ -34,8 +35,8 @@ fun TachiyomiTheme(
) {
val uiPreferences = Injekt.get<UiPreferences>()
BaseTachiyomiTheme(
appTheme = appTheme ?: uiPreferences.appTheme().get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled().get(),
appTheme = appTheme ?: uiPreferences.appTheme.get(),
isAmoled = amoled ?: uiPreferences.themeDarkAmoled.get(),
content = content,
)
}
@@ -53,26 +54,36 @@ private fun BaseTachiyomiTheme(
isAmoled: Boolean,
content: @Composable () -> Unit,
) {
MaterialTheme(
colorScheme = getThemeColorScheme(appTheme, isAmoled),
val context = LocalContext.current
val isDark = isSystemInDarkTheme()
MaterialExpressiveTheme(
colorScheme = remember(appTheme, isDark, isAmoled) {
getThemeColorScheme(
context = context,
appTheme = appTheme,
isDark = isDark,
isAmoled = isAmoled,
)
},
content = content,
)
}
@Composable
@ReadOnlyComposable
private fun getThemeColorScheme(
context: Context,
appTheme: AppTheme,
isDark: Boolean,
isAmoled: Boolean,
): ColorScheme {
val colorScheme = if (appTheme == AppTheme.MONET) {
MonetColorScheme(LocalContext.current)
MonetColorScheme(context)
} else {
colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme)
}
return colorScheme.getColorScheme(
isSystemInDarkTheme(),
isAmoled,
isDark = isDark,
isAmoled = isAmoled,
overrideDarkSurfaceContainers = appTheme != AppTheme.MONET,
)
}
@@ -14,16 +14,25 @@ internal abstract class BaseColorScheme {
private val surfaceContainerHigh = Color(0xFF131313)
private val surfaceContainerHighest = Color(0xFF1B1B1B)
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
fun getColorScheme(
isDark: Boolean,
isAmoled: Boolean,
overrideDarkSurfaceContainers: Boolean,
): ColorScheme {
if (!isDark) return lightScheme
if (!isAmoled) return darkScheme
return darkScheme.copy(
val amoledScheme = darkScheme.copy(
background = Color.Black,
onBackground = Color.White,
surface = Color.Black,
onSurface = Color.White,
)
if (!overrideDarkSurfaceContainers) return amoledScheme
return amoledScheme.copy(
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
surfaceContainerLowest = surfaceContainer,
surfaceContainerLow = surfaceContainer,
@@ -1,22 +1,17 @@
package eu.kanade.presentation.theme.colorscheme
import android.annotation.SuppressLint
import android.app.UiModeManager
import android.app.WallpaperManager
import android.content.Context
import android.graphics.Bitmap
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.ui.graphics.Color
import androidx.core.content.getSystemService
import com.google.android.material.color.utilities.Hct
import com.google.android.material.color.utilities.MaterialDynamicColors
import com.google.android.material.color.utilities.QuantizerCelebi
import com.google.android.material.color.utilities.SchemeContent
import com.google.android.material.color.utilities.Score
import com.materialkolor.PaletteStyle
import com.materialkolor.dynamiccolor.ColorSpec
import com.materialkolor.ktx.DynamicScheme
import com.materialkolor.toColorScheme
internal class MonetColorScheme(context: Context) : BaseColorScheme() {
@@ -28,7 +23,7 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
?.primaryColor
?.toArgb()
if (seed != null) {
MonetCompatColorScheme(context, seed)
MonetCompatColorScheme(Color(seed))
} else {
TachiyomiColorScheme
}
@@ -41,19 +36,6 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() {
override val lightScheme
get() = monet.lightScheme
companion object {
@Suppress("Unused")
@SuppressLint("RestrictedApi")
fun extractSeedColorFromImage(bitmap: Bitmap): Int? {
val width = bitmap.width
val height = bitmap.height
val bitmapPixels = IntArray(width * height)
bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height)
return Score.score(QuantizerCelebi.quantize(bitmapPixels, 128), 1, 0)[0]
.takeIf { it != 0 } // Don't take fallback color
}
}
}
@RequiresApi(Build.VERSION_CODES.S)
@@ -62,64 +44,19 @@ private class MonetSystemColorScheme(context: Context) : BaseColorScheme() {
override val darkScheme = dynamicDarkColorScheme(context)
}
private class MonetCompatColorScheme(context: Context, seed: Int) : BaseColorScheme() {
override val lightScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = false)
override val darkScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = true)
internal class MonetCompatColorScheme(seed: Color) : BaseColorScheme() {
override val lightScheme = generateColorSchemeFromSeed(seed = seed, dark = false)
override val darkScheme = generateColorSchemeFromSeed(seed = seed, dark = true)
companion object {
private fun Int.toComposeColor(): Color = Color(this)
@SuppressLint("PrivateResource", "RestrictedApi")
private fun generateColorSchemeFromSeed(context: Context, seed: Int, dark: Boolean): ColorScheme {
val scheme = SchemeContent(
Hct.fromInt(seed),
dark,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
context.getSystemService<UiModeManager>()?.contrast?.toDouble() ?: 0.0
} else {
0.0
},
)
val dynamicColors = MaterialDynamicColors()
return ColorScheme(
primary = dynamicColors.primary().getArgb(scheme).toComposeColor(),
onPrimary = dynamicColors.onPrimary().getArgb(scheme).toComposeColor(),
primaryContainer = dynamicColors.primaryContainer().getArgb(scheme).toComposeColor(),
onPrimaryContainer = dynamicColors.onPrimaryContainer().getArgb(scheme).toComposeColor(),
inversePrimary = dynamicColors.inversePrimary().getArgb(scheme).toComposeColor(),
secondary = dynamicColors.secondary().getArgb(scheme).toComposeColor(),
onSecondary = dynamicColors.onSecondary().getArgb(scheme).toComposeColor(),
secondaryContainer = dynamicColors.secondaryContainer().getArgb(scheme).toComposeColor(),
onSecondaryContainer = dynamicColors.onSecondaryContainer().getArgb(scheme).toComposeColor(),
tertiary = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
onTertiary = dynamicColors.onTertiary().getArgb(scheme).toComposeColor(),
tertiaryContainer = dynamicColors.tertiary().getArgb(scheme).toComposeColor(),
onTertiaryContainer = dynamicColors.onTertiaryContainer().getArgb(scheme).toComposeColor(),
background = dynamicColors.background().getArgb(scheme).toComposeColor(),
onBackground = dynamicColors.onBackground().getArgb(scheme).toComposeColor(),
surface = dynamicColors.surface().getArgb(scheme).toComposeColor(),
onSurface = dynamicColors.onSurface().getArgb(scheme).toComposeColor(),
surfaceVariant = dynamicColors.surfaceVariant().getArgb(scheme).toComposeColor(),
onSurfaceVariant = dynamicColors.onSurfaceVariant().getArgb(scheme).toComposeColor(),
surfaceTint = dynamicColors.surfaceTint().getArgb(scheme).toComposeColor(),
inverseSurface = dynamicColors.inverseSurface().getArgb(scheme).toComposeColor(),
inverseOnSurface = dynamicColors.inverseOnSurface().getArgb(scheme).toComposeColor(),
error = dynamicColors.error().getArgb(scheme).toComposeColor(),
onError = dynamicColors.onError().getArgb(scheme).toComposeColor(),
errorContainer = dynamicColors.errorContainer().getArgb(scheme).toComposeColor(),
onErrorContainer = dynamicColors.onErrorContainer().getArgb(scheme).toComposeColor(),
outline = dynamicColors.outline().getArgb(scheme).toComposeColor(),
outlineVariant = dynamicColors.outlineVariant().getArgb(scheme).toComposeColor(),
scrim = Color.Black,
surfaceBright = dynamicColors.surfaceBright().getArgb(scheme).toComposeColor(),
surfaceDim = dynamicColors.surfaceDim().getArgb(scheme).toComposeColor(),
surfaceContainer = dynamicColors.surfaceContainer().getArgb(scheme).toComposeColor(),
surfaceContainerHigh = dynamicColors.surfaceContainerHigh().getArgb(scheme).toComposeColor(),
surfaceContainerHighest = dynamicColors.surfaceContainerHighest().getArgb(scheme).toComposeColor(),
surfaceContainerLow = dynamicColors.surfaceContainerLow().getArgb(scheme).toComposeColor(),
surfaceContainerLowest = dynamicColors.surfaceContainerLowest().getArgb(scheme).toComposeColor(),
fun generateColorSchemeFromSeed(seed: Color, dark: Boolean): ColorScheme {
return DynamicScheme(
seedColor = seed,
isDark = dark,
specVersion = ColorSpec.SpecVersion.SPEC_2025,
style = PaletteStyle.Expressive,
)
.toColorScheme(isAmoled = false)
}
}
}
@@ -1,5 +1,6 @@
package eu.kanade.presentation.track
import android.content.ClipData
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -47,6 +48,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -55,11 +57,11 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.Clipboard
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.platform.toClipEntry
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.intl.Locale
@@ -73,6 +75,7 @@ import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.system.openInBrowser
import kotlinx.coroutines.launch
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
@@ -240,7 +243,7 @@ private fun SearchResultItem(
onClick: () -> Unit,
) {
val context = LocalContext.current
val clipboardManager: ClipboardManager = LocalClipboardManager.current
val clipboard: Clipboard = LocalClipboard.current
val focusManager = LocalFocusManager.current
val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current)
val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current)
@@ -248,6 +251,7 @@ private fun SearchResultItem(
val shape = RoundedCornerShape(16.dp)
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
var dropDownMenuExpanded by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Box(
modifier = Modifier
.fillMaxWidth()
@@ -295,7 +299,13 @@ private fun SearchResultItem(
expanded = dropDownMenuExpanded,
onCollapseMenu = { dropDownMenuExpanded = false },
onCopyName = {
clipboardManager.setText(AnnotatedString(trackSearch.title))
scope.launch {
val clipEntry = ClipData.newPlainText(
trackSearch.title,
trackSearch.title,
).toClipEntry()
clipboard.setClipEntry(clipEntry)
}
},
onOpenInBrowser = {
val url = trackSearch.tracking_url
@@ -1,15 +1,11 @@
package eu.kanade.presentation.track.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -30,18 +26,13 @@ fun TrackLogoIcon(
Modifier
}
Box(
Image(
painter = painterResource(tracker.getLogo()),
contentDescription = tracker.name,
modifier = modifier
.size(48.dp)
.background(color = Color(tracker.getLogoColor()), shape = MaterialTheme.shapes.medium)
.padding(4.dp),
contentAlignment = Alignment.Center,
) {
Image(
painter = painterResource(tracker.getLogo()),
contentDescription = tracker.name,
)
}
.clip(MaterialTheme.shapes.medium),
)
}
@PreviewLightDark
@@ -1,8 +1,6 @@
package eu.kanade.presentation.track.components
import android.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.test.DummyTracker
@@ -13,8 +11,6 @@ internal class TrackLogoIconPreviewProvider : PreviewParameterProvider<Tracker>
DummyTracker(
id = 1L,
name = "Dummy Tracker",
valLogoColor = Color.rgb(18, 25, 35),
valLogo = R.drawable.ic_tracker_anilist,
),
)
}
@@ -0,0 +1,111 @@
package eu.kanade.presentation.updates
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.domain.updates.service.UpdatesPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.SettingsItemsPaddings
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState
@Composable
fun UpdatesFilterDialog(
onDismissRequest: () -> Unit,
screenModel: UpdatesSettingsScreenModel,
) {
TabbedDialog(
onDismissRequest = onDismissRequest,
tabTitles = persistentListOf(
stringResource(MR.strings.action_filter),
),
) {
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState()),
) {
FilterSheet(screenModel = screenModel)
}
}
}
@Composable
private fun ColumnScope.FilterSheet(
screenModel: UpdatesSettingsScreenModel,
) {
val filterDownloaded by screenModel.updatesPreferences.filterDownloaded.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_downloaded),
state = filterDownloaded,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) },
)
val filterUnread by screenModel.updatesPreferences.filterUnread.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_unread),
state = filterUnread,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnread) },
)
val filterStarted by screenModel.updatesPreferences.filterStarted.collectAsState()
TriStateItem(
label = stringResource(MR.strings.label_started),
state = filterStarted,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) },
)
val filterBookmarked by screenModel.updatesPreferences.filterBookmarked.collectAsState()
TriStateItem(
label = stringResource(MR.strings.action_filter_bookmarked),
state = filterBookmarked,
onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) },
)
HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small))
val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators.collectAsState()
fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators.getAndSet { !it }
Row(
modifier = Modifier
.clickable { toggleScanlatorFilter() }
.fillMaxWidth()
.padding(horizontal = SettingsItemsPaddings.Horizontal),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = stringResource(MR.strings.action_filter_excluded_scanlators),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyMedium,
)
Switch(
checked = filterExcludedScanlators,
onCheckedChange = { toggleScanlatorFilter() },
)
}
}
@@ -5,9 +5,12 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.CalendarMonth
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.SelectAll
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarScrollBehavior
@@ -37,6 +40,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.theme.active
import java.time.LocalDate
import kotlin.time.Duration.Companion.seconds
@@ -54,8 +58,10 @@ fun UpdateScreen(
onMultiBookmarkClicked: (List<UpdatesItem>, bookmark: Boolean) -> Unit,
onMultiMarkAsReadClicked: (List<UpdatesItem>, read: Boolean) -> Unit,
onMultiDeleteClicked: (List<UpdatesItem>) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
onOpenChapter: (UpdatesItem) -> Unit,
onFilterClicked: () -> Unit,
hasActiveFilters: Boolean,
) {
BackHandler(enabled = state.selectionMode) {
onSelectAll(false)
@@ -66,6 +72,8 @@ fun UpdateScreen(
UpdatesAppBar(
onCalendarClicked = { onCalendarClicked() },
onUpdateLibrary = { onUpdateLibrary() },
onFilterClicked = { onFilterClicked() },
hasFilters = hasActiveFilters,
actionModeCounter = state.selected.size,
onSelectAll = { onSelectAll(true) },
onInvertSelection = { onInvertSelection() },
@@ -133,6 +141,8 @@ fun UpdateScreen(
private fun UpdatesAppBar(
onCalendarClicked: () -> Unit,
onUpdateLibrary: () -> Unit,
onFilterClicked: () -> Unit,
hasFilters: Boolean,
// For action mode
actionModeCounter: Int,
onSelectAll: () -> Unit,
@@ -147,6 +157,12 @@ private fun UpdatesAppBar(
actions = {
AppBarActions(
persistentListOf(
AppBar.Action(
title = stringResource(MR.strings.action_filter),
icon = Icons.Outlined.FilterList,
iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current,
onClick = onFilterClicked,
),
AppBar.Action(
title = stringResource(MR.strings.action_view_upcoming),
icon = Icons.Outlined.CalendarMonth,
@@ -69,7 +69,7 @@ internal fun LazyListScope.updatesLastUpdatedItem(
internal fun LazyListScope.updatesUiItems(
uiModels: List<UpdatesUiModel>,
selectionMode: Boolean,
onUpdateSelected: (UpdatesItem, Boolean, Boolean, Boolean) -> Unit,
onUpdateSelected: (UpdatesItem, Boolean, Boolean) -> Unit,
onClickCover: (UpdatesItem) -> Unit,
onClickUpdate: (UpdatesItem) -> Unit,
onDownloadChapter: (List<UpdatesItem>, ChapterDownloadAction) -> Unit,
@@ -111,11 +111,11 @@ internal fun LazyListScope.updatesUiItems(
)
},
onLongClick = {
onUpdateSelected(updatesItem, !updatesItem.selected, true, true)
onUpdateSelected(updatesItem, !updatesItem.selected, true)
},
onClick = {
when {
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, true, false)
selectionMode -> onUpdateSelected(updatesItem, !updatesItem.selected, false)
else -> onClickUpdate(updatesItem)
}
},
@@ -9,21 +9,21 @@ import tachiyomi.domain.source.model.SourceNotInstalledException
import tachiyomi.i18n.MR
import java.net.UnknownHostException
context(Context)
context(context: Context)
val Throwable.formattedMessage: String
get() {
when (this) {
is HttpException -> return stringResource(MR.strings.exception_http, code)
is HttpException -> return context.stringResource(MR.strings.exception_http, code)
is UnknownHostException -> {
return if (!isOnline()) {
stringResource(MR.strings.exception_offline)
return if (!context.isOnline()) {
context.stringResource(MR.strings.exception_offline)
} else {
stringResource(MR.strings.exception_unknown_host, message ?: "")
context.stringResource(MR.strings.exception_unknown_host, message ?: "")
}
}
is NoResultsException -> return stringResource(MR.strings.no_results_found)
is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error)
is NoResultsException -> return context.stringResource(MR.strings.no_results_found)
is SourceNotInstalledException -> return context.stringResource(MR.strings.loader_not_implemented_error)
}
return when (val className = this::class.simpleName) {
"Exception", "IOException" -> message ?: className
@@ -4,5 +4,7 @@ import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.ui.Modifier
// https://issuetracker.google.com/352584409
context(LazyItemScope)
fun Modifier.animateItemFastScroll() = this.animateItem(fadeInSpec = null, fadeOutSpec = null)
context(itemScope: LazyItemScope)
fun Modifier.animateItemFastScroll() = with(itemScope) {
this@animateItemFastScroll.animateItem(fadeInSpec = null, fadeOutSpec = null)
}
@@ -3,6 +3,8 @@ package eu.kanade.presentation.webview
import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.os.Message
import android.webkit.JsPromptResult
import android.webkit.JsResult
import android.webkit.WebResourceRequest
import android.webkit.WebView
import androidx.activity.compose.BackHandler
@@ -20,6 +22,7 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
@@ -95,6 +98,11 @@ fun WebViewScreenContent(
var currentUrl by remember { mutableStateOf(url) }
var showCloudflareHelp by remember { mutableStateOf(false) }
var isActive by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
onDispose { isActive = false }
}
val webClient = remember {
object : AccompanistWebViewClient() {
@@ -163,6 +171,36 @@ fun WebViewScreenContent(
}
return false
}
override fun onJsAlert(view: WebView, url: String?, message: String?, result: JsResult): Boolean {
if (!isActive) {
result.confirm()
return true
}
return super.onJsAlert(view, url, message, result)
}
override fun onJsConfirm(view: WebView, url: String?, message: String?, result: JsResult): Boolean {
if (!isActive) {
result.cancel()
return true
}
return super.onJsConfirm(view, url, message, result)
}
override fun onJsPrompt(
view: WebView,
url: String?,
message: String?,
defaultValue: String?,
result: JsPromptResult,
): Boolean {
if (!isActive) {
result.cancel()
return true
}
return super.onJsPrompt(view, url, message, defaultValue, result)
}
}
}
@@ -273,7 +311,7 @@ fun WebViewScreenContent(
.align(Alignment.BottomCenter),
)
is LoadingState.Loading -> LinearProgressIndicator(
progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f },
progress = { loadingState.progress },
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
+23 -10
View File
@@ -17,6 +17,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.memory.MemoryCache
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowRgb565
import coil3.request.crossfade
@@ -106,7 +107,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val scope = ProcessLifecycleOwner.get().lifecycleScope
// Show notification to disable Incognito Mode when it's enabled
basePreferences.incognitoMode().changes()
basePreferences.incognitoMode.changes()
.onEach { enabled ->
if (enabled) {
disableIncognitoReceiver.register()
@@ -134,31 +135,37 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
}
.launchIn(scope)
privacyPreferences.analytics()
privacyPreferences.analytics
.changes()
.onEach(TelemetryConfig::setAnalyticsEnabled)
.launchIn(scope)
privacyPreferences.crashlytics()
privacyPreferences.crashlytics
.changes()
.onEach(TelemetryConfig::setCrashlyticsEnabled)
.launchIn(scope)
basePreferences.hardwareBitmapThreshold().let { preference ->
basePreferences.hardwareBitmapThreshold.let { preference ->
if (!preference.isSet()) preference.set(GLUtil.DEVICE_TEXTURE_LIMIT)
}
basePreferences.hardwareBitmapThreshold().changes()
basePreferences.hardwareBitmapThreshold.changes()
.onEach { ImageUtil.hardwareBitmapThreshold = it }
.launchIn(scope)
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode().get())
setAppCompatDelegateThemeMode(Injekt.get<UiPreferences>().themeMode.get())
// Updates widget update
WidgetManager(Injekt.get(), Injekt.get()).apply { init(scope) }
if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
if (!LogcatLogger.isInstalled) {
val minLogPriority = when {
networkPreferences.verboseLogging.get() -> LogPriority.VERBOSE
BuildConfig.DEBUG -> LogPriority.DEBUG
else -> LogPriority.INFO
}
LogcatLogger.install()
LogcatLogger.loggers += AndroidLogcatLogger(minLogPriority)
}
initializeMigrator()
@@ -196,9 +203,15 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
add(MangaKeyer())
}
memoryCache(
MemoryCache.Builder()
.maxSizePercent(context)
.build(),
)
crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(DeviceUtil.isLowRamDevice(this@App))
if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
if (networkPreferences.verboseLogging.get()) logger(DebugLogger())
// Coil spawns a new thread for every image load by default
fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
@@ -243,7 +256,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
private var registered = false
override fun onReceive(context: Context, intent: Intent) {
basePreferences.incognitoMode().set(false)
basePreferences.incognitoMode.set(false)
}
fun register() {
@@ -101,7 +101,7 @@ class BackupNotifier(private val context: Context) {
}
setContentTitle(contentTitle)
if (!preferences.hideNotificationContent().get()) {
if (!preferences.hideNotificationContent.get()) {
setContentText(content)
}
@@ -89,7 +89,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
fun setupTask(context: Context, prefInterval: Int? = null) {
val backupPreferences = Injekt.get<BackupPreferences>()
val interval = prefInterval ?: backupPreferences.backupInterval().get()
val interval = prefInterval ?: backupPreferences.backupInterval.get()
if (interval > 0) {
val constraints = Constraints(
requiresBatteryNotLow = true,
@@ -108,7 +108,7 @@ class BackupCreator(
BackupFileValidator(context).validate(fileUri)
if (isAutoBackup) {
backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli())
backupPreferences.lastAutoBackupTimestamp.set(Instant.now().toEpochMilli())
}
return fileUri.toString()
@@ -32,7 +32,7 @@ class CategoriesRestorer(
.let { id -> it.toCategory(id).copy(order = order) }
}
libraryPreferences.categorizedDisplaySettings().set(
libraryPreferences.categorizedDisplaySettings.set(
(dbCategories + categories)
.distinctBy { it.flags }
.size > 1,
@@ -56,7 +56,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
override suspend fun doWork(): Result {
var networkCheck = checkNetworkState(
applicationContext.activeNetworkState(),
downloadPreferences.downloadOnlyOverWifi().get(),
downloadPreferences.downloadOnlyOverWifi.get(),
)
var active = networkCheck && downloadManager.downloaderStart()
@@ -69,7 +69,7 @@ class DownloadJob(context: Context, workerParams: WorkerParameters) : CoroutineW
coroutineScope {
combineTransform(
applicationContext.networkStateFlow(),
downloadPreferences.downloadOnlyOverWifi().changes(),
downloadPreferences.downloadOnlyOverWifi.changes(),
transform = { a, b -> emit(checkNetworkState(a, b)) },
)
.onEach { networkCheck = it }
@@ -397,7 +397,7 @@ class DownloadManager(
private suspend fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
// Retrieve the categories that are set to exclude from being deleted on read
val categoriesToExclude = downloadPreferences.removeExcludeCategories().get().map(String::toLong)
val categoriesToExclude = downloadPreferences.removeExcludeCategories.get().map(String::toLong)
val categoriesForManga = getCategories.await(manga.id)
.map { it.id }
@@ -408,7 +408,7 @@ class DownloadManager(
chapters
}
return if (!downloadPreferences.removeBookmarkedChapters().get()) {
return if (!downloadPreferences.removeBookmarkedChapters.get()) {
filteredCategoryManga.filterNot { it.bookmark }
} else {
filteredCategoryManga
@@ -96,7 +96,7 @@ internal class DownloadNotifier(private val context: Context) {
download.pages!!.size,
)
if (preferences.hideNotificationContent().get()) {
if (preferences.hideNotificationContent.get()) {
setContentTitle(downloadingProgressText)
setContentText(null)
} else {
@@ -136,7 +136,7 @@ class DownloadProvider(
fun getSourceDirName(source: Source): String {
return DiskUtil.buildValidFilename(
source.toString(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
)
}
@@ -148,7 +148,7 @@ class DownloadProvider(
fun getMangaDirName(mangaTitle: String): String {
return DiskUtil.buildValidFilename(
mangaTitle,
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames.get(),
)
}
@@ -163,7 +163,7 @@ class DownloadProvider(
chapterName: String,
chapterScanlator: String?,
chapterUrl: String,
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames().get(),
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames.get(),
): String {
var dirName = sanitizeChapterName(chapterName)
if (!chapterScanlator.isNullOrBlank()) {
@@ -206,7 +206,7 @@ class DownloadProvider(
chapterName,
chapterScanlator,
chapterUrl,
!libraryPreferences.disallowNonAsciiFilenames().get(),
!libraryPreferences.disallowNonAsciiFilenames.get(),
)
return buildList(2) {
@@ -193,7 +193,7 @@ class Downloader(
downloaderJob = scope.launch {
val activeDownloadsFlow = combine(
queueState,
downloadPreferences.parallelSourceLimit().changes(),
downloadPreferences.parallelSourceLimit.changes(),
) { a, b -> a to b }.transformLatest { (queue, parallelCount) ->
while (true) {
val activeDownloads = queue.asSequence()
@@ -369,7 +369,7 @@ class Downloader(
download.status = Download.State.DOWNLOADING
// Start downloading images, consider we can have downloaded images already
pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit().get()) { page ->
pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit.get()) { page ->
flow {
// Fetch image URL if necessary
if (page.imageUrl.isNullOrEmpty()) {
@@ -406,7 +406,7 @@ class Downloader(
)
// Only rename the directory if it's downloaded
if (downloadPreferences.saveChaptersAsCBZ().get()) {
if (downloadPreferences.saveChaptersAsCBZ.get()) {
archiveChapter(mangaDir, chapterDirname, tmpDir)
} else {
tmpDir.renameTo(chapterDirname)
@@ -545,7 +545,7 @@ class Downloader(
}
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
if (!downloadPreferences.splitTallImages().get()) return
if (!downloadPreferences.splitTallImages.get()) return
try {
val filenamePrefix = "%03d".format(Locale.ENGLISH, page.number)
@@ -99,7 +99,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (tags.contains(WORK_NAME_AUTO)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
val preferences = Injekt.get<LibraryPreferences>()
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
if ((DEVICE_ONLY_ON_WIFI in restrictions) && !context.isConnectedToWifi()) {
return Result.retry()
}
@@ -113,7 +113,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely()
libraryPreferences.lastUpdatedTimestamp().set(Instant.now().toEpochMilli())
libraryPreferences.lastUpdatedTimestamp.set(Instant.now().toEpochMilli())
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
addMangaToQueue(categoryId)
@@ -160,8 +160,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { categoryId in it.categories }
} else {
val includedCategories = libraryPreferences.updateCategories().get().map { it.toLong() }
val excludedCategories = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }
val includedCategories = libraryPreferences.updateCategories.get().map { it.toLong() }
val excludedCategories = libraryPreferences.updateCategoriesExclude.get().map { it.toLong() }
libraryManga.filter {
val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
@@ -170,7 +170,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
}
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val restrictions = libraryPreferences.autoUpdateMangaRestrictions.get()
val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
val (_, fetchWindowUpperBound) = fetchInterval.getWindow(ZonedDateTime.now())
@@ -272,7 +272,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
hasDownloads.store(true)
}
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
libraryPreferences.newUpdatesCount.getAndSet { it + newChapters.size }
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.toTypedArray())
@@ -332,7 +332,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val source = sourceManager.getOrStub(manga.source)
// Update manga metadata if needed
if (libraryPreferences.autoUpdateMetadata().get()) {
if (libraryPreferences.autoUpdateMetadata.get()) {
val networkManga = source.getMangaDetails(manga.toSManga())
updateManga.awaitUpdateFromSource(manga, networkManga, manualFetch = false, coverCache)
}
@@ -418,33 +418,32 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
*/
private const val KEY_CATEGORY = "category"
fun cancelAllWorks(context: Context) {
context.workManager.cancelAllWorkByTag(TAG)
}
fun setupTask(
context: Context,
prefInterval: Int? = null,
) {
val preferences = Injekt.get<LibraryPreferences>()
val interval = prefInterval ?: preferences.autoUpdateInterval().get()
val interval = prefInterval ?: preferences.autoUpdateInterval.get()
if (interval > 0) {
val restrictions = preferences.autoUpdateDeviceRestrictions().get()
val restrictions = preferences.autoUpdateDeviceRestrictions.get()
val networkType = if (DEVICE_NETWORK_NOT_METERED in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val networkRequestBuilder = NetworkRequest.Builder()
if (DEVICE_ONLY_ON_WIFI in restrictions) {
networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
}
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
val networkRequest = NetworkRequest.Builder().apply {
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
if (DEVICE_ONLY_ON_WIFI in restrictions) {
addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
}
if (DEVICE_NETWORK_NOT_METERED in restrictions) {
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
}
}
.build()
val constraints = Constraints.Builder()
// 'networkRequest' only applies to Android 9+, otherwise 'networkType' is used
.setRequiredNetworkRequest(networkRequestBuilder.build(), networkType)
.setRequiredNetworkRequest(networkRequest, networkType)
.setRequiresCharging(DEVICE_CHARGING in restrictions)
.setRequiresBatteryNotLow(true)
.build()
@@ -98,7 +98,7 @@ class LibraryUpdateNotifier(
),
)
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
}
@@ -173,7 +173,7 @@ class LibraryUpdateNotifier(
Notifications.CHANNEL_NEW_CHAPTERS,
) {
setContentTitle(context.stringResource(MR.strings.notification_new_chapters))
if (updates.size == 1 && !securityPreferences.hideNotificationContent().get()) {
if (updates.size == 1 && !securityPreferences.hideNotificationContent.get()) {
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
} else {
setContentText(
@@ -184,7 +184,7 @@ class LibraryUpdateNotifier(
),
)
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
setStyle(
NotificationCompat.BigTextStyle().bigText(
updates.joinToString("\n") {
@@ -208,7 +208,7 @@ class LibraryUpdateNotifier(
}
// Per-manga notification
if (!securityPreferences.hideNotificationContent().get()) {
if (!securityPreferences.hideNotificationContent.get()) {
launchUI {
context.notify(
updates.map { (manga, chapters) ->
@@ -201,7 +201,7 @@ class NotificationReceiver : BroadcastReceiver() {
val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) }
.map {
val chapter = it.copy(read = true)
if (downloadPreferences.removeAfterMarkedAsRead().get()) {
if (downloadPreferences.removeAfterMarkedAsRead.get()) {
val manga = getManga.await(mangaId)
if (manga != null) {
val source = sourceManager.get(manga.source)
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.track
import androidx.annotation.CallSuper
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track
@@ -24,9 +23,6 @@ interface Tracker {
val supportsPrivateTracking: Boolean
@ColorInt
fun getLogoColor(): Int
@DrawableRes
fun getLogo(): Int
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.anilist
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.R
@@ -44,7 +43,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
override val supportsPrivateTracking: Boolean = true
private val scorePreference = trackPreferences.anilistScoreType()
private val scorePreference = trackPreferences.anilistScoreType
init {
// If the preference is an int from APIv1, logout user to force using APIv2
@@ -56,9 +55,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
}
}
override fun getLogo() = R.drawable.ic_tracker_anilist
override fun getLogoColor() = Color.rgb(18, 25, 35)
override fun getLogo() = R.drawable.brand_anilist
override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
@@ -41,9 +41,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun addLibManga(track: Track): Track {
return withIOContext {
val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private) {
val query = $$"""
|mutation AddManga($mangaId: Int, $progress: Int, $status: MediaListStatus, $private: Boolean) {
|SaveMediaListEntry (mediaId: $mangaId, progress: $progress, status: $status, private: $private) {
| id
| status
|}
@@ -78,14 +78,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
val query = """
val query = $$"""
|mutation UpdateManga(
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean,
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|$listId: Int, $progress: Int, $status: MediaListStatus, $private: Boolean,
|$score: Int, $startedAt: FuzzyDateInput, $completedAt: FuzzyDateInput
|) {
|SaveMediaListEntry(
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private,
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|id: $listId, progress: $progress, status: $status, private: $private,
|scoreRaw: $score, startedAt: $startedAt, completedAt: $completedAt
|) {
|id
|status
@@ -114,9 +114,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun deleteLibManga(track: DomainTrack) {
withIOContext {
val query = """
|mutation DeleteManga(${'$'}listId: Int) {
|DeleteMediaListEntry(id: ${'$'}listId) {
val query = $$"""
|mutation DeleteManga($listId: Int) {
|DeleteMediaListEntry(id: $listId) {
|deleted
|}
|}
@@ -135,10 +135,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
val query = """
|query Search(${'$'}query: String) {
val query = $$"""
|query Search($query: String) {
|Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|media(search: $query, type: MANGA, format_not_in: [NOVEL]) {
|id
|staff {
|edges {
@@ -197,10 +197,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun findLibManga(track: Track, userid: Int): Track? {
return withIOContext {
val query = """
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
val query = $$"""
|query ($id: Int!, $manga_id: Int!) {
|Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
|mediaList(userId: $id, type: MANGA, mediaId: $manga_id) {
|id
|status
|scoreRaw: score(format: POINT_100)
@@ -17,7 +17,7 @@ fun Track.toApiStatus() = when (status) {
private val preferences: TrackPreferences by injectLazy()
fun DomainTrack.toApiScore(): String = when (preferences.anilistScoreType().get()) {
fun DomainTrack.toApiScore(): String = when (preferences.anilistScoreType.get()) {
// 10 point
"POINT_10" -> (score.toInt() / 10).toString()
// 100 point
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.bangumi
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@@ -79,9 +78,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return track
}
override fun getLogo() = R.drawable.ic_tracker_bangumi
override fun getLogoColor() = Color.rgb(240, 145, 153)
override fun getLogo() = R.drawable.brand_bangumi
override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
@@ -111,7 +108,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
// Users can set a 'username' (not nickname) once which effectively
// replaces the stringified ID in certain queries.
// If no username is set, the API returns the user ID as a strings
var username = api.getUsername()
val username = api.getUsername()
saveCredentials(username, oauth.accessToken)
} catch (_: Throwable) {
logout()
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kavita
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@@ -34,9 +33,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
private val sourceManager: SourceManager by injectLazy()
override fun getLogo(): Int = R.drawable.ic_tracker_kavita
override fun getLogoColor() = Color.rgb(74, 198, 148)
override fun getLogo(): Int = R.drawable.brand_kavita
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
@@ -140,7 +137,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
}
authentication.apiUrl = prefApiUrl
authentication.jwtToken = token.toString()
authentication.jwtToken = token
}
authentications = oauth
}
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kitsu
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@@ -36,9 +35,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
private val api by lazy { KitsuApi(client, interceptor) }
override fun getLogo() = R.drawable.ic_tracker_kitsu
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getLogo() = R.drawable.brand_kitsu
override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.komga
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@@ -31,9 +30,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
val api by lazy { KomgaApi(id, client) }
override fun getLogo() = R.drawable.ic_tracker_komga
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getLogo() = R.drawable.brand_komga
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.mangaupdates
import android.graphics.Color
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
@@ -42,9 +41,7 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
private val api by lazy { MangaUpdatesApi(interceptor, client) }
override fun getLogo(): Int = R.drawable.ic_manga_updates
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
override fun getLogo(): Int = R.drawable.brand_mangaupdates
override fun getStatusList(): List<Long> {
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
@@ -193,6 +193,6 @@ class MangaUpdatesApi(
companion object {
private const val BASE_URL = "https://api.mangaupdates.com"
private val CONTENT_TYPE = "application/vnd.api+json".toMediaType()
private val CONTENT_TYPE = "application/json".toMediaType()
}
}

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