Compare commits

..

147 Commits

Author SHA1 Message Date
Jobobby04 759fd4d4e3 Follow previous comment 2024-08-17 20:38:26 -04:00
FooIbar fc956fc791 Add comment about RecyclerView cache size (#1119)
Note for forks: Increasing cache size may cause OOM on API < 26, better
to make it API 26+ only.

(cherry picked from commit 1c47a6b9b35c622200c731cdbbc076f5263e8d06)
2024-08-17 20:33:30 -04:00
AntsyLich 9d7346157b Sync compose theme with MDC theme
(cherry picked from commit 9a34ace09c66274e6c2b3f9446058a0fa99d4bd0)

# Conflicts:
#	CHANGELOG.md
2024-08-17 20:31:20 -04:00
Weblate (bot) 0f0f4cf4a9 Translations update from Hosted Weblate (#1247)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Ahmed seif al-nasr <ahmdsyfalnsr2@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Howard Wu <HowardWu20@outlook.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: TawfikSharaf <tawfikahmed132.wa@gmail.com>
Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
Co-authored-by: Tim Schneeberger <thebone.main@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2024-08-17 20:27:46 -04:00
akir45 426ef65102 Add japanese Translation (#1248)
* Add plurals.xml

* Add string.xml
2024-08-17 20:27:36 -04:00
Shamicen 95c834581b Libarchive refactor (#1249)
* Refactor archive support with libarchive

* Refactor archive support with libarchive

* Revert string resource changs

* Only mark archive formats as supported

Comic book archives should not be compressed.

* Fixup

* Remove epub from archive format list

* Move to mihon package

* Format

* Cleanup

Co-authored-by: Shamicen <84282253+Shamicen@users.noreply.github.com>
(cherry picked from commit 239c38982c4fd55d4d86b37fd9c3c51c3b47d098)

* handle incorrect passwords

* lint

* fixed broken encryption detection + small tweaks

* Add safeguard to prevent ArchiveInputStream from being closed twice (#967)

* fix: Add safeguard to prevent ArchiveInputStream from being closed twice

* detekt

* lint: Make detekt happy

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit e620665dda9eb5cc39f09e6087ea4f60a3cbe150)

* fixed ArchiveReaderMode CACHE_TO_DISK

* Added some missing SY --> comments

---------

Co-authored-by: FooIbar <118464521+fooibar@users.noreply.github.com>
Co-authored-by: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com>
2024-08-17 20:25:25 -04:00
NGB-Was-Taken 71f2daf8f3 Delete duplicate downloaded chapters when they are automatically marked as read (#1252) 2024-08-17 20:24:29 -04:00
Jobobby04 7ec14cd9f0 Fix preview 2024-08-11 20:42:40 -04:00
AntsyLich c23c9491fc Handle Android SDK 35 API collision
(cherry picked from commit fdb96179c6373eb0a8e7d6daea671a315d5ce5f0)
2024-08-11 19:41:15 -04:00
Jobobby04 29f3766c87 Update version code 2024-08-11 19:37:48 -04:00
Jobobby04 07c89890bc Fix SY migrations 2024-08-11 19:37:05 -04:00
Weblate (bot) 64a54f55b3 Translations update from Hosted Weblate (#1228)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY
Translation: Mihon/TachiyomiSY Plurals

Co-authored-by: Ahmed seif al-nasr <ahmdsyfalnsr2@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Howard Wu <HowardWu20@outlook.com>
Co-authored-by: TawfikSharaf <tawfikahmed132.wa@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
2024-08-11 18:39:57 -04:00
Tim Schneeberger f7202e67cc feat(migration): add option to only show entries with new chapters (#1238) 2024-08-11 18:30:19 -04:00
renovate[bot] 155b03c176 fix(deps): update dependency com.elvishew:xlog to v1.11.1 (#1239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-11 18:29:49 -04:00
renovate[bot] 6b0482576b chore(deps): update gradle/actions action to v4 (#1243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-11 18:29:42 -04:00
AntsyLich c137bafd68 Fix UI freeze after migration
Fixes #938

(cherry picked from commit 3f1d28c3833e6b868152149ed02b3fb8c54eccef)
2024-08-11 18:09:02 -04:00
AntsyLich 49bdffdc28 Add a button to select all scanlators
Resolves #943
Closes #1109

(cherry picked from commit 84b2164787a795f3fd757c325cbfb6ef660ac3a3)
2024-08-11 18:08:43 -04:00
Catting f1b32d531a Add Copy Tracker URL on icon long press (#1101)
* Add Copy Tracker URL on icon long press

Signed-off-by: Catt0s <5874051+mm12@users.noreply.github.com>

* Add 'Copy To Clipboard' to tracker item menu

Signed-off-by: Catt0s <5874051+mm12@users.noreply.github.com>

* Add 'Copy link' to locales.

Signed-off-by: Catt0s <5874051+mm12@users.noreply.github.com>

* Implement code review suggestions
>
> Co-authored-by: AntsyLich  <59261191+AntsyLich@users.noreply.github.com>

Signed-off-by: Catt0s <5874051+mm12@users.noreply.github.com>

* Update app/src/main/java/eu/kanade/presentation/track/components/TrackLogoIcon.kt

---------

Signed-off-by: Catt0s <5874051+mm12@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 200d39e023af79b02276554a1bef1d7d53e3b903)
2024-08-11 18:02:09 -04:00
Weblate (bot) a5ec6c5cdd Translations update from Hosted Weblate (#939)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
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/be/
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/cs/
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/eo/
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/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
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/hi/
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/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/km/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
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/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/mr/
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/nl/
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/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
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/sdh/
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/te/
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/uz/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Ahmed seif al-nasr <ahmdsyfalnsr2@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Co-authored-by: Animeboynz <40583749+Animeboynz@users.noreply.github.com>
Co-authored-by: David Katrinka <davidkatrinka1995@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eji-san <ejierubani@gmail.com>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Iker Lerones <ikerlero@hotmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Matyáš Caras <matyas@caras.wtf>
Co-authored-by: Norsze <norbert.szabo7+github@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: gekka <1778962971@qq.com>
Co-authored-by: sebastians17 <sebastians117.ss@gmail.com>
Co-authored-by: vodkapmp <vodkapmp@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: Артём Голуб <artemtirax2001@gmail.com>
(cherry picked from commit b1b15a93eec15a82e2e83650abf97c1b9f0c501c)
2024-08-11 18:02:02 -04:00
MajorTanya 9c56cdb1c1 Fix MAL search results not showing start dates (#1098)
The previous approach would always throw an Exception because
`SimpleDateFormat.format()` expects the input to be of type `Date` or
`Number`, not `String`.

(cherry picked from commit 97c81fadb426d71ac99c9443ab0e89f4089046ef)
2024-08-11 18:01:51 -04:00
MajorTanya 543de065a6 Change Kitsu to kitsu.app domain (#1106)
cf. https://github.com/hummingbird-me/kitsu-server/commit/244fdccca9754d8579c049e738832843001b33b1

(cherry picked from commit 9240eceedc5e2b065dd680819c4180c1ae09512b)

# Conflicts:
#	README.md
2024-08-11 18:01:43 -04:00
Jobobby04 33296e1faf Translations readme 2024-08-11 18:00:59 -04:00
Catting d1a90c0bb7 Contributing: ktLintFormat -> detekt (#1102)
* Contributing: ktLintFormat -> detekt

update Contributing info to use detekt instead of ktLintFormat

* Update CONTRIBUTING.md

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 14ae57d78b31f0bb3b58d19c1d8cfcebcc8e2253)
2024-08-11 18:00:34 -04:00
renovate[bot] 9fa61d33be fix(deps): update dependency com.android.tools.build:gradle to v8.5.2 (#1099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 4828c54245dd6532c0e7a2b6c8cf5d8a703d3376)
2024-08-11 18:00:26 -04:00
renovate[bot] 0e9dcc7855 fix(deps): update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha10 (#1092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e8b7c3e24bb677d289554b972ef2496a976c79aa)
2024-08-11 18:00:18 -04:00
renovate[bot] 6738c6072d fix(deps): update dependency androidx.work:work-runtime to v2.9.1 (#1091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit af77083660000e7378587dbc8d44e44bd8b196ec)
2024-08-11 18:00:11 -04:00
renovate[bot] 29033c539c fix(deps): update dependency androidx.annotation:annotation to v1.8.2 (#1090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 36b9caeea8baf15f0d0ed37abc12638d44194c09)
2024-08-11 18:00:04 -04:00
renovate[bot] eaa3413c37 fix(deps): update paging.version to v3.3.2 (#1093)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 8e40146f96704c3dc98bbb4f9f89d470ffa32f69)
2024-08-11 17:59:57 -04:00
AntsyLich 73d9d1d46d ExpandableMangaDescription: Adjust size transform anim spec
Co-authored-by: ivan <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit 1c16fc79c2ac4c4be30308fed84ffb371dab5902)
2024-08-11 17:59:47 -04:00
Roshan Varughese 94f9aaf351 Add Backup and Restore of Extension Repos (#1057)
* Backup/Restore Extension Repos

* Refactor

* Moving to Under App Settings

* Sort by URL, Check existing by SHA and Error Logging

Untested. Currently in a lecture and can't test if the changes really work.

* Changes to logic

* Don't ask me what's happening here

* Renaming Variables

* Fixing restoreAmount & changes to logic

Co-Authored-By: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

---------

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

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/BackupRestorer.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt
2024-08-11 17:59:35 -04:00
AntsyLich e21149cb37 Rename backup restore error log file
(cherry picked from commit 2858ef835fec8d7278b1d0cad1b5664104d1e4b0)
2024-08-11 17:39:41 -04:00
renovate[bot] 11aad16f59 chore(deps): update kotlin monorepo to v2.0.10 (#1085)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit edb8201f74e516c296b62e04a13802e1bd9e0b6b)
2024-08-11 17:39:30 -04:00
FooIbar 33a3918e86 Don't crash on ill-formed URLs (#1084)
(cherry picked from commit 854474f85ffc41eccdc2b3a6cf105fa2805ebc3c)
2024-08-11 17:39:21 -04:00
Tran M. Cuong d655b8ecdf fix: drawScrollbar crash on list with 0 item but only sticky header (#1083)
(cherry picked from commit 04db46fe75c2406fe9750e97da65774a6b268f27)
2024-08-11 17:39:12 -04:00
FooIbar 70a8bef7a5 Match extra layout space with scroll distance (#1076)
And increase recycler item view cache size.

(cherry picked from commit a3dfd2efe6ace7a2a4d79bd09fb1a729989f1094)
2024-08-11 17:38:58 -04:00
Vetle Ledaal 999a8613cf Improve error message if restoring from JSON file (#1056)
* Improve error message if restoring from JSON file

* Replace Exception with IOException

* Use more generic error message if protobuf fails

* fix lint

(cherry picked from commit de8ef6dad7c89afb7041ccb489d68539a4849cb5)
2024-08-11 17:38:48 -04:00
AntsyLich 5721a02bca Bump default user agent string
(cherry picked from commit 8160b47ff5fbbd9b32caeb462b5be881fabd3449)
2024-08-11 17:38:39 -04:00
AntsyLich e303b88b90 Cleanup backup/restore related code
(cherry picked from commit c201b341a716b90d378dcda4bd9b8ac4a343d4fc)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt
2024-08-11 17:38:13 -04:00
AntsyLich a62dd5821a Fix library is backed up when disabled and make categories backup/restore independent
(cherry picked from commit 56fb4f62a152e87a71892aa68c78cac51a2c8596)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupOptions.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/RestoreOptions.kt
2024-08-11 17:34:04 -04:00
Roshan Varughese a0786d9b09 Adds Option to Copy Panel to Clipboard (#1003)
* Add Copy to Clipboard

* Removing Unused Import

* Reusing onShare function

* Commit Suggestion

* Early Return on null

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

---------

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

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
2024-08-11 17:25:33 -04:00
renovate[bot] 04580ce357 fix(deps): update paging.version to v3.3.1 (#1046)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 41e2dc7ae80250d9166fc637c1170667afdb0a9e)
2024-08-11 15:39:47 -04:00
Roshan Varughese b759f2f02a Format Category String on Subtitle Display (#1030)
* Fixes #1029

* Max Line Length Fix

* Update SettingsLibraryScreen.kt

No idea how this works.

Co-authored-by: Foolbar <118464521+Foolbar@users.noreply.github.com>

---------

Co-authored-by: Foolbar <118464521+Foolbar@users.noreply.github.com>
(cherry picked from commit 88efde8796b0e1cc8fba6cd987bdc487bd97f248)
2024-08-11 15:39:38 -04:00
renovate[bot] 8ae8068ecd fix(deps): update lifecycle.version to v2.8.4 (#1045)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b7849d714698900a25188bdbfd77bf24936f2dd7)
2024-08-11 15:39:31 -04:00
renovate[bot] eecd9367d4 fix(deps): update dependency androidx.annotation:annotation to v1.8.1 (#1043)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 602b58f364b95b83a3148be34cd4c90d95d7d405)
2024-08-11 15:39:24 -04:00
renovate[bot] 55dee69838 fix(deps): update dependency androidx.activity:activity-compose to v1.9.1 (#1042)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e48dbdbf2356c0e6e148313dc6610e865cd8e995)
2024-08-11 15:39:16 -04:00
FooIbar a730ca5444 Remove obsolete workaround (#1021)
(cherry picked from commit 51b68cd25ff4bf556de88cb31525c55dd7eb7027)
2024-08-11 15:39:06 -04:00
renovate[bot] cebd8fe0a8 fix(deps): update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha09 (#1039)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit ca784cbe3267e94e652e4c54f91b7107cc53c307)
2024-08-11 15:38:54 -04:00
renovate[bot] 55a979c5f7 fix(deps): update dependency io.mockk:mockk to v1.13.12 (#1016)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 4f61b2e4e89bc257cf5e629823904805907bf75c)
2024-08-11 15:38:37 -04:00
renovate[bot] 728f3fc349 chore(deps): update dependency gradle to v8.9 (#1007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f63e95091013320b27bfc3c7c975c4bdd4a983c5)
2024-08-11 15:38:17 -04:00
renovate[bot] a9a3ed1d16 fix(deps): update dependency org.jsoup:jsoup to v1.18.1 (#999)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f3f2bd41c3974878bcf0e3a62d99ee89bf92fb41)
2024-08-11 15:38:08 -04:00
renovate[bot] 36f13a7c6a fix(deps): update dependency com.android.tools.build:gradle to v8.5.1 (#1010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7a2ca4bf4d41764705637d61c6d86249f8815b7b)
2024-08-11 15:37:37 -04:00
renovate[bot] 37a2ccc678 Bump coil version and some cleanup
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e65634cb427eafe9e3bd192f9e8bf71f2243ce6c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
2024-08-11 15:37:16 -04:00
FooIbar bb39088dd7 Fix some issues when reading/saving images (#993)
* Fix unsupported mime type error when saving images

Avoid using platform mime type map to get extensions as it may not have
all mime types we support.

* Fix jxl images downloading/reading

(cherry picked from commit daa47e049327c4d8b1fe4724ed1b84897d81fcf2)

# Conflicts:
#	core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
2024-08-11 15:34:57 -04:00
AntsyLich c5546e1095 Fix login prompts despite being logged in to trackers in Manga screen
(cherry picked from commit cbcd8bd6682023f728568f2b44da26124618aed7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2024-08-11 15:32:28 -04:00
AntsyLich 2d12c670db Observe tracker login state instead of fetching once (#987)
* Observe tracker login state instead of fetching once

* Review changes

(cherry picked from commit 2092c81bad59fd745a8514af320e534ecf40a5da)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2024-08-11 15:15:44 -04:00
AntsyLich 3db4bccebc Make global search "Has result" sticky
Closes #133

(cherry picked from commit 5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649)

# Conflicts:
#	app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt
2024-08-11 14:49:11 -04:00
Roshan Varughese 2f23ad6bfd Smart Update Dialog Tweak (#977)
* Smart Update Dialog Fix

* Build Fail Change 1

* Commit Suggested Change

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

* Build Fail Change 2

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit ddba71df37359e6abbbcc96b18685435961710dc)
2024-08-11 14:48:33 -04:00
CrepeTF de1898a2c9 Correct tako variable colours (#976)
(cherry picked from commit 75b5d966018aa917f57adf37370088a51e4914b2)
2024-08-11 14:48:18 -04:00
renovate[bot] eb135ec22d fix(deps): update lifecycle.version to v2.8.3 (#972)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 77db8873f6753cc9db8f67b39d53685563380cc6)
2024-08-11 14:48:10 -04:00
WerctFourth bf6c646dc7 Update image-decoder revision (#971)
(cherry picked from commit bff6183cf3ef400d8ddcdccf7180e4139816cc09)
2024-08-11 14:48:01 -04:00
renovate[bot] 9ce16d5e1c fix(deps): update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha07 (#960)
* fix(deps): update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha07

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit c0f9de88e70ef1db97c521993462ae27550b5790)
2024-08-11 14:47:41 -04:00
renovate[bot] 619ff726c8 fix(deps): update aboutlib.version to v11.2.2 (#965)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 80cdebcdf467ed00e530651aeed2b36cc63b8356)
2024-08-11 14:47:22 -04:00
renovate[bot] 730ceaaf49 fix(deps): update dependency org.junit.jupiter:junit-jupiter to v5.10.3 (#962)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 9e2f97eeb8a0f1d1b353dc3e77fb64d69b568674)
2024-08-11 14:47:13 -04:00
CrepeTF 07b701cb3c Theme fixes (#963)
* Fix theme issue with download progress indicator

* Fix theme issue with download progress indicator + better contrast

(cherry picked from commit e132cc405f23e18dd8d73626730821eae9051149)
2024-08-11 14:47:02 -04:00
Caio Oliveira b64c6b78ea buildSrc: Fix strange warning in ci build (#952)
* buildSrc: Fix strange warning

´Project accessors enabled, but root project name not explicitly set for 'buildSrc'. Checking out the project in different folders will impact the generated code and implicitly the buildscript classpath, breaking caching.´

* Update settings.gradle.kts

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 2674b849746f20c051dab3fd6edfad1594e41b42)
2024-08-11 14:46:50 -04:00
renovate[bot] 521bce5c08 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.1 (#958)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f34702d4fcc10f24953b21e883fb454778bbae77)
2024-08-11 14:46:36 -04:00
renovate[bot] a719ed8c9e fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.1 (#959)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7823966ddf2289fee743feaa58b906ab9179a4ed)
2024-08-11 14:46:22 -04:00
renovate[bot] f6fc2d7e2f fix(deps): update dependency net.zetetic:sqlcipher-android to v4.6.0 (#1221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 09:55:53 -04:00
renovate[bot] 48d43c4f07 chore(deps): update actions/upload-artifact action to v4 (#1222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 09:55:42 -04:00
renovate[bot] f4fa86b2dc chore(deps): update softprops/action-gh-release action to v2 (#1223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 09:55:29 -04:00
renovate[bot] 37db0dc1f6 fix(deps): update dependency com.google.oauth-client:google-oauth-client to v1.36.0 (#1220)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-26 20:55:33 -04:00
renovate[bot] 1ada03b07a chore(deps): update actions/setup-java action to v4 (#1217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-26 20:54:25 -04:00
renovate[bot] f4c1e7c2d5 chore(deps): update damianreeves/write-file-action action to v1.3 (#1216)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-26 20:54:11 -04:00
Jobobby04 6c5282c598 Another 2024-06-26 20:39:26 -04:00
Jobobby04 7899474a36 Fix build errors 2024-06-26 20:38:12 -04:00
Jobobby04 225b419bba Update wrapper validation 2024-06-26 20:23:59 -04:00
Jobobby04 fa64103a1c Actual baseline 2024-06-26 20:20:12 -04:00
Jobobby04 57e0e99f06 Preview branch makes preview 2024-06-26 20:10:45 -04:00
Jobobby04 efde7afa8e Update baseline 2024-06-26 20:09:09 -04:00
Jobobby04 f929a4bc26 Move some libs to sylibs 2024-06-26 20:03:24 -04:00
renovate[bot] d35141c1cc chore: Configure Renovate (#1215)
* Add renovate.json

* Only update specific files

* Add a glob

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
2024-06-26 19:57:10 -04:00
renovate[bot] 6988966019 fix(deps): update serialization.version to v1.7.1 (#951)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit d8fe7d32ca6bacbb74e9e80173625a993621e4b2)
2024-06-26 19:32:52 -04:00
Maddie Witman f6d8ebbb0a Added configuration options to e-ink page flashes (#625)
* Recommit for e-ink pref changes

* Fixed state holder for flash interval

* Detekt

* Refactor suggested by Antsy

* inverted currentDisplayRefresh check for early exit

(cherry picked from commit 2f86f25d5b24c2054a604802dc65b8bc3a99c7c0)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt
#	app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt
2024-06-26 19:32:42 -04:00
renovate[bot] ae45df9fcf fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0 (#948)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 36e40c099772d2cb53d4ec87b2b00f97fe455c98)
2024-06-26 19:31:12 -04:00
renovate[bot] f332344681 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0 (#947)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 40754659a9e146626add56c494a6dc9873691f14)
2024-06-26 19:31:03 -04:00
AntsyLich c6abb340ca Cleanup in CommonMangaItem.kt
Closes #19

Co-authored-by: Roshan Varughese <40583749+Animeboynz@users.noreply.github.com>
(cherry picked from commit e17f70f7226ea031fc1f962c9dfea3e404ba53ad)
2024-06-26 19:30:50 -04:00
Tran M. Cuong 99dbb16a7a Fix Migrator test and also add the test to build script (#896)
* Fix MigratorTest after update to Kotlin 2.0.0

* add main module's test to build script

(cherry picked from commit e57638a49c759d36d25b92f26633df5bdfb0d2b3)

# Conflicts:
#	.github/workflows/build_pull_request.yml
#	.github/workflows/build_push.yml
2024-06-26 19:30:39 -04:00
FooIbar f62e8933d7 Fix unexpected skips in strong skipping mode (#940)
(cherry picked from commit 0ce1cf22cdbb7d82df3db1a901253b4973ab027f)

# Conflicts:
#	source-api/build.gradle.kts
2024-06-26 19:30:00 -04:00
renovate[bot] ee3c2fd79c fix(deps): update dependency io.github.fornewid:material-motion-compose-core to v2.0.1 (#945)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit f6ec53cdde32a17bc394e55b697a3b59bfd76e58)
2024-06-26 19:28:53 -04:00
renovate[bot] 6b08b873a8 fix(deps): update dependency com.google.firebase:firebase-analytics to v22.0.2 (#936)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b37357f9097730edb1d72f1297461e580286856c)
2024-06-26 19:28:43 -04:00
renovate[bot] a3f2f49ab8 fix(deps): update moko to v0.24.1 (#933)
* fix(deps): update moko to v0.24.1

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit f58a05e91828a69c01d49d629e5bfa9ec7ae3ffc)
2024-06-26 19:28:27 -04:00
Weblate (bot) 524f5cc6ab Translations update from Hosted Weblate (#904)
* Translated using Weblate (Malayalam)

Currently translated at 16.9% (136 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/

* Translated using Weblate (Swedish)

Currently translated at 99.1% (797 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/

* Translated using Weblate (Arabic)

Currently translated at 99.5% (800 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (18 of 18 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sv/

---------

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Co-authored-by: Norsze <norbert.szabo7+github@gmail.com>
Co-authored-by: Duh051 <duhduh272@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
(cherry picked from commit cf02119da55c431d0fb4c42ecfec3681d466ae43)
2024-06-26 19:16:39 -04:00
FooIbar a35e084b9e Fix R8 version configuration not working (#916)
This reverts commit f3226fb278cab87422255e04e647c50095b61529.

(cherry picked from commit 4182ae89a036525c5575961a68371df249ce384f)

# Conflicts:
#	build.gradle.kts
2024-06-26 19:16:27 -04:00
FooIbar 78f7fba67b Update R8 to fix NoSuchMethodError crash (#914)
(cherry picked from commit f3226fb278cab87422255e04e647c50095b61529)

# Conflicts:
#	build.gradle.kts
2024-06-26 19:15:56 -04:00
renovate[bot] 69d1db3018 fix(deps): update dependency com.android.tools.build:gradle to v8.5.0 (#901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 2e78bceb30908aca8e585f91942849a6e4e7cb15)
2024-06-26 19:15:21 -04:00
Weblate (bot) d1b317e5c8 Translations update from Hosted Weblate (#878)
* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/

* Translated using Weblate (Croatian)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/

* Translated using Weblate (Malayalam)

Currently translated at 15.5% (125 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/

* Translated using Weblate (Malayalam)

Currently translated at 15.5% (125 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/

* Translated using Weblate (Malayalam)

Currently translated at 94.4% (17 of 18 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/

---------

Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Co-authored-by: Animeboynz <roshanvarughese@hotmail.com>
(cherry picked from commit aa1714b2acf0e5b16558ea703220f60d4ecd23e9)
2024-06-26 19:15:06 -04:00
AntsyLich fff40e031f Fix issue with creating and restoring backup
Fixes #881

(cherry picked from commit f696f209c6b3efb3148e1d587af9e42c71d8dc6f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt
2024-06-26 19:14:54 -04:00
renovate[bot] 5be2ec51ba fix(deps): update dependency androidx.glance:glance-appwidget to v1.1.0 (#890)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit af57e124f2113f78028771f1579a356884d7ead7)
2024-06-26 19:13:53 -04:00
renovate[bot] 1c2a7af13e fix(deps): update lifecycle.version to v2.8.2 (#889)
fix(deps): update dependency androidx.lifecycle:lifecycle-runtime-ktx to v2.8.2

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 8e8ee69bbacb2260d0ae52808c02684e567119b9)
2024-06-26 19:13:44 -04:00
renovate[bot] 182158acb0 fix(deps): update dependency com.android.tools.build:gradle to v8.4.2 (#883)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e9d69a83febccf8840dad03597e3ac2a6aa3f972)
2024-06-26 19:13:34 -04:00
AntsyLich 21f92bfb3a Fix chapter number parsing when number is after unwanted tag
Fixes #554

Co-authored-by: Naputt1 <94742489+Naputt1@users.noreply.github.com>
(cherry picked from commit 6a80305d6c572da6c08c0c69f5c25ff26ecf7383)
2024-06-26 19:13:24 -04:00
AntsyLich a5522ef732 Check category order before restoring from backup
Closes #632

Co-authored-by: Cologler <10906962+Cologler@users.noreply.github.com>
(cherry picked from commit 119bcbf8ed2415664922ea77fadf0da1165d1732)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt
2024-06-26 19:13:14 -04:00
Weblate (bot) 239793f7fd Translations update from Hosted Weblate (#611)
* Translated using Weblate (Malayalam)

Currently translated at 12.9% (104 of 803 strings)

Translated using Weblate (Malayalam)

Currently translated at 94.4% (17 of 18 strings)

Translated using Weblate (Malayalam)

Currently translated at 11.8% (95 of 803 strings)

Added translation using Weblate (Malayalam)

Added translation using Weblate (Malayalam)

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

* Translated using Weblate (Italian)

Currently translated at 99.6% (800 of 803 strings)

Co-authored-by: Federico Pierantoni <federico.pieranton@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translation: Mihon/Mihon

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (803 of 803 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: B4LiN7 <B4LiN7@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hu/
Translation: Mihon/Mihon

* Translated using Weblate (Javanese)

Currently translated at 38.7% (311 of 803 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (803 of 803 strings)

Translated using Weblate (Indonesian)

Currently translated at 98.7% (793 of 803 strings)

Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
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/jv/
Translation: Mihon/Mihon

* Translated using Weblate (Greek)

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translation: Mihon/Mihon

* Translated using Weblate (Serbian)

Currently translated at 99.2% (797 of 803 strings)

Co-authored-by: Rikishaaa <jebote90@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/
Translation: Mihon/Mihon

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: Blackiezin <mcperenan134@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translation: Mihon/Mihon

* Translated using Weblate (French)

Currently translated at 100.0% (18 of 18 strings)

Translated using Weblate (French)

Currently translated at 99.0% (795 of 803 strings)

Co-authored-by: LaQuiche426 <loic.dossantos42630@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

* Translated using Weblate (Portuguese)

Currently translated at 99.8% (802 of 803 strings)

Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translation: Mihon/Mihon

* Translated using Weblate (Vietnamese)

Currently translated at 100.0% (18 of 18 strings)

Translated using Weblate (Vietnamese)

Currently translated at 96.8% (778 of 803 strings)

Co-authored-by: Karuto <nguyenthaison609@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

* Translated using Weblate (Croatian)

Currently translated at 99.5% (799 of 803 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translation: Mihon/Mihon

* Translated using Weblate (Indonesian)

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: Eji-san <ejierubani@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translation: Mihon/Mihon

* Translated using Weblate (Galician)

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: kevans <albapazpi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/gl/
Translation: Mihon/Mihon

* Translated using Weblate (Ukrainian)

Currently translated at 99.8% (802 of 803 strings)

Co-authored-by: Kodekiro Kodekihara <lolbitoklol@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
Translation: Mihon/Mihon

* Translated using Weblate (Malay)

Currently translated at 98.6% (792 of 803 strings)

Co-authored-by: Farith <mail2@farithadnan.net>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translation: Mihon/Mihon

* Translated using Weblate (Nepali)

Currently translated at 100.0% (18 of 18 strings)

Translated using Weblate (Nepali)

Currently translated at 100.0% (803 of 803 strings)

Co-authored-by: FateXBlood <fatexblood@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

* Translated using Weblate (Vietnamese)

Currently translated at 100.0% (803 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/

* Translated using Weblate (Croatian)

Currently translated at 100.0% (803 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (18 of 18 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/

* Translated using Weblate (Romanian)

Currently translated at 99.6% (800 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/

* Translated using Weblate (Romanian)

Currently translated at 100.0% (18 of 18 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ro/

* Translated using Weblate (Italian)

Currently translated at 100.0% (803 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/

* Translated using Weblate (Polish)

Currently translated at 99.5% (799 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (803 of 803 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/

* Translated using Weblate (German)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/

* Translated using Weblate (Russian)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/

* Translated using Weblate (French)

Currently translated at 99.5% (800 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/

* Translated using Weblate (Filipino)

Currently translated at 99.8% (803 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/

* Translated using Weblate (Nepali)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (804 of 804 strings)

Translation: Mihon/Mihon
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (18 of 18 strings)

Translation: Mihon/Mihon Plurals
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/

---------

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Co-authored-by: Federico Pierantoni <federico.pieranton@gmail.com>
Co-authored-by: B4LiN7 <B4LiN7@users.noreply.hosted.weblate.org>
Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rikishaaa <jebote90@gmail.com>
Co-authored-by: Blackiezin <mcperenan134@gmail.com>
Co-authored-by: LaQuiche426 <loic.dossantos42630@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Karuto <nguyenthaison609@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Eji-san <ejierubani@gmail.com>
Co-authored-by: kevans <albapazpi@gmail.com>
Co-authored-by: Kodekiro Kodekihara <lolbitoklol@gmail.com>
Co-authored-by: Farith <mail2@farithadnan.net>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Chrono Lux <amber_c001@protonmail.com>
Co-authored-by: Saft Octavian <saftoctavian@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: sebastians17 <sebastians117.ss@gmail.com>
Co-authored-by: Tim Schneeberger <thebone.main@gmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Naga <yz2000.pro@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
(cherry picked from commit 87fe64468ca08466af5b9fcc7f9e17e9a23021e6)

# Conflicts:
#	i18n/src/commonMain/resources/MR/gl/strings.xml
#	i18n/src/commonMain/resources/MR/ro/strings.xml
2024-06-26 19:12:15 -04:00
renovate[bot] 4e9cfe4602 fix(deps): update dependency io.github.fornewid:material-motion-compose-core to v2 (#873)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit bdce3c39f1475dc77dad300a0bf3702e85d32916)
2024-06-26 19:11:01 -04:00
AntsyLich f548c85e7a MangaChapterListItem: Don't use alpha modifier
Possibly fixes #822

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit 15d999229fcce865001d5fa77d0163e6e80e38db)
2024-06-26 19:10:39 -04:00
renovate[bot] 576349c446 fix(deps): update okhttp monorepo to v5.0.0-alpha.14 (#688)
* fix(deps): update okhttp monorepo to v5.0.0-alpha.14

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 1edd55c981aa72faf49c06173f33bf0c2f99fe60)
2024-06-26 19:10:28 -04:00
renovate[bot] 9b00e0458b fix(deps): update serialization.version to v1.7.0 (#870)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 71b558cb34c4e2da435877f391e57b6d49c4ef4f)
2024-06-26 19:06:15 -04:00
renovate[bot] 6a1ff99441 chore(deps): update kotlin and compose compiler to v2 (major) (#819)
* chore(deps): update kotlin and compose compiler to v2

* Update .gitignore

* Fix build

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 46003ec25139319079abc9fde89b3afd344a1a11)

# Conflicts:
#	.github/renovate.json5
#	gradle/compose.versions.toml
#	source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt
2024-06-26 19:06:10 -04:00
renovate[bot] 0121fe9397 fix(deps): update dependency io.kotest:kotest-assertions-core to v5.9.1 (#869)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 1f7574bd4fc0471b7f974cffdf166c2551b2749b)
2024-06-26 17:41:20 -04:00
Cuong M. Tran 5c47c7a409 Fix MigratorTest after update to io.mockk v1.13.11 (#814)
* Fix MigratorTest after update to io.mockk v1.13.11

Causing error: io.mockk.MockKException: was not can only be called on a mocked object

* remove import

(cherry picked from commit da62c7a21a81f513988fa64df6253376f85228ef)
2024-06-26 17:40:33 -04:00
renovate[bot] 8bb4f33f2e fix(deps): update dependency io.github.fornewid:material-motion-compose-core to v1.2.1 (#858)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 0870cffba121c0cc7db9c79f83f770a43d9c32e7)
2024-06-26 17:40:17 -04:00
Sven 5f5fd51668 fix: storage permission request for non-conforming devices (#726)
* fix: storage permission request for non-conforming devices

* fix: catch more specific exception

* chore: add toast message to indicate missing persistent permissions

* chore: correct newly introduced translaction string

* Change error toast message

---------

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 8632ba85ee1ed080d7baa70050d460807c8edfcf)
2024-06-26 17:40:03 -04:00
Jobobby04 c7bbad93b2 Fix some MDLang issues 2024-06-26 17:38:53 -04:00
Jobobby04 1a4a2506f4 Codestral(ChatGPT) cleanup of some double pages code 2024-06-26 17:38:51 -04:00
gelionexists 7b7a594ddb Update LewdMangaChecker.kt (#1204)
- Added the `mature` tag
- Added `doujins` (doujins.com) and `luscious` (luscious.net) as filter keywords
2024-06-26 17:31:41 -04:00
KaiserBh c2eece0fff chore: improve google drive sync. (#1200)
improve google drive sync, removes the lock, change to protobuf, and potentially fix deviceId not being unique, since it wasn't appState...
2024-06-26 17:31:12 -04:00
ɴᴇᴋᴏ d29a4ff381 Update TW strings.xml (#1202)
Add Google sync strings
2024-06-26 17:30:06 -04:00
Tim Schneeberger 31f967235f Add Namicomi support for external chapters on MD (#1188) 2024-06-01 18:26:05 -04:00
Cuong M. Tran 7d6e746257 Fix build warning: remove non-default string resources (#1180) 2024-06-01 18:25:53 -04:00
Timo d306139047 Update README.md (#1174)
Changed DO and DON'T for bug repports to a link with the issue template.
2024-06-01 18:25:37 -04:00
Jobobby04 7e6811692e Fix WorkManager cancellation issues 2024-06-01 17:51:03 -04:00
renovate[bot] 8df0446020 chore(deps): update dependency gradle to v8.8 (#856)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 116579d38c08c203fe2c4996419e277c7bf9c165)
2024-06-01 17:40:26 -04:00
renovate[bot] 8ccf8fc74d fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0-rc01 (#855)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 098f925519502a99aef9eb33c9b88b1280351a73)
2024-06-01 17:40:18 -04:00
Jobobby04 4deaa41c53 Cleanup some build warnings 2024-06-01 14:52:54 -04:00
renovate[bot] c843789f66 fix(deps): update aboutlib.version to v11.2.1 (#846)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 9f5db70572cee99243c850d2da55fe0a10d52809)
2024-06-01 14:02:25 -04:00
renovate[bot] 78da81fa42 fix(deps): update dependency com.google.firebase:firebase-analytics to v22.0.1 (#848)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 1f286f1a357a6c0f8cf2a7274b11b175315de4ea)
2024-06-01 14:02:17 -04:00
renovate[bot] 4fc96f263d fix(deps): update dependency com.google.gms:google-services to v4.4.2 (#849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 7ab7f5ac37e8c102ca259144f136616b59172e6d)
2024-06-01 14:02:02 -04:00
renovate[bot] 6a12b54ecb fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0-rc01 (#851)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit e567250b17c231abee151541fea1f97b269b516a)
2024-06-01 14:01:52 -04:00
renovate[bot] 8ffcd5efec fix(deps): update dependency androidx.appcompat:appcompat to v1.7.0 (#845)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 095da924b9a1d724e6a228cb4abcc4dfe948db7d)
2024-06-01 14:01:44 -04:00
renovate[bot] 26121efeb4 fix(deps): update lifecycle.version to v2.8.1 (#844)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit b9da98b527e3d9ac56e263f7b595768239fa718e)
2024-06-01 14:01:36 -04:00
renovate[bot] 1ecf3a567b fix(deps): update paging.version to v3.3.0 (#810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit af8696cb9060e652f41220ea4a6074568df8bc3d)
2024-06-01 14:01:28 -04:00
renovate[bot] 20fff5798d fix(deps): update dependency org.apache.commons:commons-compress to v1.26.2 (#826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit de5a64aa73c8a2229fa87e9baba9981197474f51)
2024-06-01 14:01:20 -04:00
renovate[bot] 0ba580ba30 fix(deps): update aboutlib.version to v11.2.0 (#823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 9b944092c75046b68ac38e2d9d3a5e49033d7588)
2024-06-01 14:01:10 -04:00
renovate[bot] aaf28ee4f1 fix(deps): update dependency com.android.tools.build:gradle to v8.4.1 (#818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 0cb1794a445c8c7f4e9c163bd850f1681cdbef6a)
2024-06-01 14:01:03 -04:00
renovate[bot] 8558c110a9 chore(deps): update kotlin and compose compiler (#800)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 2f243fae11e48799437b1672ec1a8571552caacb)

# Conflicts:
#	gradle/compose.versions.toml
2024-06-01 14:00:54 -04:00
renovate[bot] 3865583c28 fix(deps): update lifecycle.version to v2.8.0 (#809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit d2e5c78074829ca7c7bace3e5bfee25ea430e44c)
2024-06-01 14:00:15 -04:00
renovate[bot] b2cc61f6fd fix(deps): update dependency androidx.annotation:annotation to v1.8.0 (#808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 5912d6b08f3624a385f820582e49f1dde1b55864)
2024-06-01 14:00:05 -04:00
renovate[bot] 47dd58de2a fix(deps): update dependency io.mockk:mockk to v1.13.11 (#803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 99b550ae0d9eaf530d644f7755162d9705c302f4)

# Conflicts:
#	gradle/libs.versions.toml
2024-06-01 13:59:55 -04:00
renovate[bot] 67d42c9c2b fix(deps): update dependency androidx.test.ext:junit-ktx to v1.2.0-beta01 (#801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 8d187f786594356dd47899dad660d602b52099ef)
2024-06-01 13:59:25 -04:00
renovate[bot] b97f322d6f fix(deps): update dependency com.google.android.material:material to v1.12.0 (#754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit 653d5d3e252ff2508b100b0f291fdcfaf2a98097)
2024-06-01 13:59:17 -04:00
renovate[bot] d8cc4f8b45 fix(deps): update dependency androidx.test.espresso:espresso-core to v3.6.0-beta01 (#797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
(cherry picked from commit ce497003e38d838e846b6e4ce88aafe4610d522b)
2024-06-01 13:57:49 -04:00
CrepeTF f78752fbdf Update themes to follow new compose update changes (#766)
* Update Green Apple theme

* Add some Green Apple theme comments

* Update Lavender theme

* Update Midnight Dusk theme

* Update Nord theme

* Update Strawberry Daiquiri theme

* Update Tako theme

* Update Teal & Turquoise theme

* Update Lavender secondaryContainer and onSecondaryContainer colour

* Update M.Dusk secondaryContainer and onSecondaryContainer colour

* Update Tako secondaryContainer and onSecondaryContainer colour

* Comments

* Update Tidal Wave theme

* Update Yin Yang theme

* Update Yotsuba theme

* Fix navbar tinted background on pure black

* Add surfaceContainer levels to Lavender theme

* Resolve detekt issues

* Add surfaceContainer levels to Midnight Dusk theme

* Add surfaceContainer levels to Nord theme

* Add surfaceContainer levels to Tako theme

* Add surfaceContainer levels to Teal & Turquoise theme

* Add surfaceContainer levels to Tidal Wave theme

* Add surfaceContainer levels to Yin Yang theme

* Add surfaceContainer levels to Yotsuba theme

* Add dark theme surfaceContainer levels to Yotsuba theme

* surfaceContainer tweaks to Yotsuba theme

* surfaceContainer tweaks to Strawberry Daiquiri theme

* surfaceContainer tweaks to Nord theme

* surfaceContainer tweaks to Lavender theme

* Update Tachiyomi theme

* Update Pure Black theme

* Resolve detekt issues

* Oopsie

(cherry picked from commit 16392adcbba4027cbed0a44e2fc62df330af6385)
2024-06-01 13:57:37 -04:00
AwkwardPeak7 d968d58cd6 update r8 rules for MultipartBody.Builder in extensions (#783)
(cherry picked from commit f603db3f3fac72954997bfa0c2cd77a0b92cd7f0)
2024-06-01 13:57:27 -04:00
AntsyLich 54d5f9baaf Remove dependency on compose material 2 components
(cherry picked from commit fb9423028eb017c110cb805f2d0601e5b02e50f9)
2024-06-01 12:57:07 -04:00
FooIbar c1bf53e28a Fix tap control area shifting after zooming out (#767)
(cherry picked from commit 8e9396a9cfe5f5e87e4e5f2093421a3fa24d43db)
2024-06-01 12:56:57 -04:00
AntsyLich 517fd3a8f4 Use new SurfaceContainer color roles
Non-dynamic themes need to be updated

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit 1df87eabf2b301cf6fc60cfa5f9391756984b790)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt
2024-06-01 12:56:48 -04:00
AntsyLich 089d1aba57 Fix search bar style
(cherry picked from commit ca7391bbf3d49428f6321aa6baa03b0d2b5abff9)
2024-06-01 12:55:18 -04:00
380 changed files with 5880 additions and 2633 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"labels": ["Dependencies"],
"includePaths": [".github/workflows/*", "gradle/sy.versions.toml"],
}
+4 -4
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/actions/wrapper-validation@v4
build: build:
name: Build app name: Build app
@@ -27,19 +27,19 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: 17 java-version: 17
distribution: adopt distribution: adopt
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v4
- name: Build app - name: Build app
run: ./gradlew detekt assembleDevDebug run: ./gradlew detekt assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: TachiyomiSY-${{ github.sha }}.apk name: TachiyomiSY-${{ github.sha }}.apk
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
+6 -6
View File
@@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v2 uses: gradle/actions/wrapper-validation@v4
- name: Setup Android SDK - name: Setup Android SDK
run: | run: |
@@ -31,18 +31,18 @@ jobs:
distribution: adopt distribution: adopt
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v3 uses: gradle/actions/setup-gradle@v4
# SY <-- # SY <--
- name: Write google-services.json - name: Write google-services.json
uses: DamianReeves/write-file-action@v1.2 uses: DamianReeves/write-file-action@v1.3
with: with:
path: app/google-services.json path: app/google-services.json
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }} contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
write-mode: overwrite write-mode: overwrite
- name: Write client_secrets.json - name: Write client_secrets.json
uses: DamianReeves/write-file-action@v1.2 uses: DamianReeves/write-file-action@v1.3
with: with:
path: app/src/main/assets/client_secrets.json path: app/src/main/assets/client_secrets.json
contents: ${{ secrets.CLIENT_SECRETS_TEXT }} contents: ${{ secrets.CLIENT_SECRETS_TEXT }}
@@ -86,7 +86,7 @@ jobs:
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
- name: Create release - name: Create release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
tag_name: ${{ github.run_number }} tag_name: ${{ github.run_number }}
name: TachiyomiSY name: TachiyomiSY
+2 -3
View File
@@ -3,12 +3,11 @@ name: Remote Dispatch Action Initiator
on: on:
push: push:
branches: branches:
- 'master' - 'preview'
jobs: jobs:
trigger_preview_build: trigger_preview_build:
name: Trigger preview build name: Trigger preview build
if: ${{ github.ref == 'refs/heads/master' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@@ -16,7 +15,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1 uses: gradle/actions/wrapper-validation@v4
- name: Create Tag - name: Create Tag
run: | run: |
+1
View File
@@ -1,4 +1,5 @@
.gradle .gradle
.kotlin
/local.properties /local.properties
/.idea/workspace.xml /.idea/workspace.xml
.DS_Store .DS_Store
+1 -1
View File
@@ -26,7 +26,7 @@ Before you start, please note that the ability to use following technologies is
## Linting ## Linting
To auto-fix some linting errors, run the `ktlintFormat` Gradle task. Run the `detekt` gradle task. If the build fails, a report of issues can be found in `app/build/reports/detekt/`. The report is availble in several formats and details each issue that needs attention.
## Getting help ## Getting help
+11 -4
View File
@@ -14,7 +14,7 @@ Features of Mihon(original) include:
* Online reading from a variety of sources * Online reading from a variety of sources
* Local reading of downloaded content * Local reading of downloaded content
* A configurable reader with multiple viewers, reading directions and other settings. * A configurable reader with multiple viewers, reading directions and other settings.
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support * Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
* Categories to organize your library * Categories to organize your library
* Light and dark themes * Light and dark themes
* Schedule updating your library for new chapters * Schedule updating your library for new chapters
@@ -67,6 +67,15 @@ Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/re
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases). If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases).
## Translation
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/mihon/tachiyomisy/)
<details><summary>Translation Progress</summary>
<a href="https://hosted.weblate.org/engage/mihon/">
<img src="https://hosted.weblate.org/widgets/mihon/-/tachiyomisy/multi-auto.svg" alt="Translation status" />
</a>
</details>
## Issues, Feature Requests and Contributing ## Issues, Feature Requests and Contributing
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not. Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
@@ -88,9 +97,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
* If it could be device-dependent, try reproducing on another device (if possible) * If it could be device-dependent, try reproducing on another device (if possible)
* Don't group unrelated requests into one issue * Don't group unrelated requests into one issue
DO: https://github.com/tachiyomiorg/tachiyomi/issues/24 https://github.com/tachiyomiorg/tachiyomi/issues/71 Use the [issue forms](https://github.com/jobobby04/TachiyomiSY/issues/new/choose) to submit a bug.
DON'T: https://github.com/tachiyomiorg/tachiyomi/issues/75
</details> </details>
+9 -11
View File
@@ -29,7 +29,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 67 versionCode = 69
versionName = "1.10.5" versionName = "1.10.5"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@@ -155,7 +155,6 @@ dependencies {
implementation(compose.activity) implementation(compose.activity)
implementation(compose.foundation) implementation(compose.foundation)
implementation(compose.material3.core) implementation(compose.material3.core)
implementation(compose.material.core)
implementation(compose.material.icons) implementation(compose.material.icons)
implementation(compose.animation) implementation(compose.animation)
implementation(compose.animation.graphics) implementation(compose.animation.graphics)
@@ -163,13 +162,15 @@ dependencies {
implementation(compose.ui.tooling.preview) implementation(compose.ui.tooling.preview)
implementation(compose.ui.util) implementation(compose.ui.util)
implementation(compose.accompanist.systemuicontroller) implementation(compose.accompanist.systemuicontroller)
implementation(androidx.interpolator)
implementation(androidx.paging.runtime) implementation(androidx.paging.runtime)
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite) implementation(libs.bundles.sqlite)
// SY --> // SY -->
implementation(libs.sqlcipher) implementation(sylibs.sqlcipher)
// SY <-- // SY <--
implementation(kotlinx.reflect) implementation(kotlinx.reflect)
@@ -211,10 +212,6 @@ dependencies {
// Disk // Disk
implementation(libs.disklrucache) implementation(libs.disklrucache)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.bundles.archive)
// SY -->
implementation(libs.zip4j)
// SY <--
// Preferences // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)
@@ -247,9 +244,6 @@ dependencies {
implementation(libs.compose.grid) implementation(libs.compose.grid)
implementation(libs.google.api.services.drive)
implementation(libs.google.api.client.oauth)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)
@@ -282,6 +276,10 @@ dependencies {
// RatingBar (SY) // RatingBar (SY)
implementation(sylibs.ratingbar) implementation(sylibs.ratingbar)
implementation(sylibs.composeRatingbar) implementation(sylibs.composeRatingbar)
// Google drive
implementation(sylibs.google.api.services.drive)
implementation(sylibs.google.api.client.oauth)
} }
androidComponents { androidComponents {
@@ -303,7 +301,7 @@ androidComponents {
tasks { tasks {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers) // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
withType<KotlinCompile> { withType<KotlinCompile> {
kotlinOptions.freeCompilerArgs += listOf( compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers", "-Xcontext-receivers",
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
"-opt-in=androidx.compose.material.ExperimentalMaterialApi", "-opt-in=androidx.compose.material.ExperimentalMaterialApi",
+4 -3
View File
@@ -47,6 +47,10 @@
-dontnote rx.internal.util.PlatformDependent -dontnote rx.internal.util.PlatformDependent
##---------------End: proguard configuration for RxJava 1.x ---------- ##---------------End: proguard configuration for RxJava 1.x ----------
##---------------Begin: proguard configuration for okhttp ----------
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
##---------------End: proguard configuration for okhttp ----------
##---------------Begin: proguard configuration for kotlinx.serialization ---------- ##---------------Begin: proguard configuration for kotlinx.serialization ----------
-keepattributes *Annotation*, InnerClasses -keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.** # core serialization annotations -dontnote kotlinx.serialization.** # core serialization annotations
@@ -123,9 +127,6 @@
# XmlUtil # XmlUtil
-keep public enum nl.adaptivity.xmlutil.EventType { *; } -keep public enum nl.adaptivity.xmlutil.EventType { *; }
# Apache Commons Compress
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }
# Firebase # Firebase
-keep class com.google.firebase.installations.** { *; } -keep class com.google.firebase.installations.** { *; }
-keep interface com.google.firebase.installations.** { *; } -keep interface com.google.firebase.installations.** { *; }
@@ -49,6 +49,11 @@ class SourcePreferences(
emptySet(), emptySet(),
) )
fun globalSearchFilterState() = preferenceStore.getBoolean(
Preference.appStateKey("has_filters_toggle_state"),
false,
)
// SY --> // SY -->
fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true) fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true)
@@ -29,7 +29,7 @@ class SyncPreferences(
) )
fun uniqueDeviceID(): String { fun uniqueDeviceID(): String {
val uniqueIDPreference = preferenceStore.getString("unique_device_id", "") val uniqueIDPreference = preferenceStore.getString(Preference.appStateKey("unique_device_id"), "")
// Retrieve the current value of the preference // Retrieve the current value of the preference
var uniqueID = uniqueIDPreference.get() var uniqueID = uniqueIDPreference.get()
@@ -53,12 +53,14 @@ class SyncPreferences(
tracking = preferenceStore.getBoolean("tracking", true).get(), tracking = preferenceStore.getBoolean("tracking", true).get(),
history = preferenceStore.getBoolean("history", true).get(), history = preferenceStore.getBoolean("history", true).get(),
appSettings = preferenceStore.getBoolean("appSettings", true).get(), appSettings = preferenceStore.getBoolean("appSettings", true).get(),
extensionRepoSettings = preferenceStore.getBoolean("extensionRepoSettings", true).get(),
sourceSettings = preferenceStore.getBoolean("sourceSettings", true).get(), sourceSettings = preferenceStore.getBoolean("sourceSettings", true).get(),
privateSettings = preferenceStore.getBoolean("privateSettings", true).get(), privateSettings = preferenceStore.getBoolean("privateSettings", true).get(),
// SY --> // SY -->
customInfo = preferenceStore.getBoolean("customInfo", true).get(), customInfo = preferenceStore.getBoolean("customInfo", true).get(),
readEntries = preferenceStore.getBoolean("readEntries", true).get() readEntries = preferenceStore.getBoolean("readEntries", true).get(),
savedSearches = preferenceStore.getBoolean("savedSearches", true).get(),
// SY <-- // SY <--
) )
} }
@@ -70,12 +72,14 @@ class SyncPreferences(
preferenceStore.getBoolean("tracking", true).set(syncSettings.tracking) preferenceStore.getBoolean("tracking", true).set(syncSettings.tracking)
preferenceStore.getBoolean("history", true).set(syncSettings.history) preferenceStore.getBoolean("history", true).set(syncSettings.history)
preferenceStore.getBoolean("appSettings", true).set(syncSettings.appSettings) preferenceStore.getBoolean("appSettings", true).set(syncSettings.appSettings)
preferenceStore.getBoolean("extensionRepoSettings", true).set(syncSettings.extensionRepoSettings)
preferenceStore.getBoolean("sourceSettings", true).set(syncSettings.sourceSettings) preferenceStore.getBoolean("sourceSettings", true).set(syncSettings.sourceSettings)
preferenceStore.getBoolean("privateSettings", true).set(syncSettings.privateSettings) preferenceStore.getBoolean("privateSettings", true).set(syncSettings.privateSettings)
// SY --> // SY -->
preferenceStore.getBoolean("customInfo", true).set(syncSettings.customInfo) preferenceStore.getBoolean("customInfo", true).set(syncSettings.customInfo)
preferenceStore.getBoolean("readEntries", true).set(syncSettings.readEntries) preferenceStore.getBoolean("readEntries", true).set(syncSettings.readEntries)
preferenceStore.getBoolean("savedSearches", true).set(syncSettings.savedSearches)
// SY <-- // SY <--
} }
@@ -7,11 +7,13 @@ data class SyncSettings(
val tracking: Boolean = true, val tracking: Boolean = true,
val history: Boolean = true, val history: Boolean = true,
val appSettings: Boolean = true, val appSettings: Boolean = true,
val extensionRepoSettings: Boolean = true,
val sourceSettings: Boolean = true, val sourceSettings: Boolean = true,
val privateSettings: Boolean = false, val privateSettings: Boolean = false,
// SY --> // SY -->
val customInfo: Boolean = true, val customInfo: Boolean = true,
val readEntries: Boolean = true val readEntries: Boolean = true,
val savedSearches: Boolean = true,
// SY <-- // SY <--
) )
@@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
@@ -23,7 +21,6 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl
@Composable @Composable
fun NavigatorAdaptiveSheet( fun NavigatorAdaptiveSheet(
screen: Screen, screen: Screen,
tonalElevation: Dp = 1.dp,
enableSwipeDismiss: (Navigator) -> Boolean = { true }, enableSwipeDismiss: (Navigator) -> Boolean = { true },
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
) { ) {
@@ -31,7 +28,6 @@ fun NavigatorAdaptiveSheet(
screen = screen, screen = screen,
content = { sheetNavigator -> content = { sheetNavigator ->
AdaptiveSheet( AdaptiveSheet(
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator), enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
@@ -73,7 +69,6 @@ fun NavigatorAdaptiveSheet(
fun AdaptiveSheet( fun AdaptiveSheet(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
tonalElevation: Dp = 1.dp,
enableSwipeDismiss: Boolean = true, enableSwipeDismiss: Boolean = true,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@@ -86,7 +81,6 @@ fun AdaptiveSheet(
AdaptiveSheetImpl( AdaptiveSheetImpl(
modifier = modifier, modifier = modifier,
isTabletUi = isTabletUi, isTabletUi = isTabletUi,
tonalElevation = tonalElevation,
enableSwipeDismiss = enableSwipeDismiss, enableSwipeDismiss = enableSwipeDismiss,
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
) { ) {
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
@@ -21,6 +20,7 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
@@ -312,7 +312,7 @@ fun SearchToolbar(
visualTransformation = visualTransformation, visualTransformation = visualTransformation,
interactionSource = interactionSource, interactionSource = interactionSource,
decorationBox = { innerTextField -> decorationBox = { innerTextField ->
TextFieldDefaults.TextFieldDecorationBox( TextFieldDefaults.DecorationBox(
value = searchQuery, value = searchQuery,
innerTextField = innerTextField, innerTextField = innerTextField,
enabled = true, enabled = true,
@@ -331,6 +331,7 @@ fun SearchToolbar(
), ),
) )
}, },
container = {},
) )
}, },
) )
@@ -58,6 +58,7 @@ fun TabbedDialog(
PrimaryTabRow( PrimaryTabRow(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
divider = {}, divider = {},
) { ) {
tabTitles.fastForEachIndexed { index, tab -> tabTitles.fastForEachIndexed { index, tab ->
@@ -155,7 +155,7 @@ private fun ColumnScope.FilterPage(
) )
// SY <-- // SY <--
val trackers = remember { screenModel.trackers } val trackers by screenModel.trackersFlow.collectAsState()
when (trackers.size) { when (trackers.size) {
0 -> { 0 -> {
// No trackers // No trackers
@@ -188,6 +188,7 @@ private fun ColumnScope.SortPage(
category: Category?, category: Category?,
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
) { ) {
val trackers by screenModel.trackersFlow.collectAsState()
// SY --> // SY -->
val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState() val globalSortMode by screenModel.libraryPreferences.sortingMode().collectAsState()
val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) { val sortingMode = if (screenModel.grouping == LibraryGroup.BY_DEFAULT) {
@@ -206,12 +207,12 @@ private fun ColumnScope.SortPage(
}.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty()) }.collectAsState(initial = screenModel.libraryPreferences.sortTagsForLibrary().get().isNotEmpty())
// SY <-- // SY <--
val trackerSortOption =
if (screenModel.trackers.isEmpty()) { val trackerSortOption = if (trackers.isEmpty()) {
emptyList() emptyList()
} else { } else {
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean) listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
} }
listOfNotNull( listOfNotNull(
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical, MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
@@ -346,12 +347,13 @@ private fun ColumnScope.GroupPage(
screenModel: LibrarySettingsScreenModel, screenModel: LibrarySettingsScreenModel,
hasCategories: Boolean, hasCategories: Boolean,
) { ) {
val groups = remember(hasCategories, screenModel.trackers) { val trackers by screenModel.trackersFlow.collectAsState()
val groups = remember(hasCategories, trackers) {
buildList { buildList {
add(LibraryGroup.BY_DEFAULT) add(LibraryGroup.BY_DEFAULT)
add(LibraryGroup.BY_SOURCE) add(LibraryGroup.BY_SOURCE)
add(LibraryGroup.BY_STATUS) add(LibraryGroup.BY_STATUS)
if (screenModel.trackers.isNotEmpty()) { if (trackers.isNotEmpty()) {
add(LibraryGroup.BY_TRACK_STATUS) add(LibraryGroup.BY_TRACK_STATUS)
} }
if (hasCategories) { if (hasCategories) {
@@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
@@ -42,15 +43,22 @@ import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.components.BadgeGroup
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
import tachiyomi.domain.manga.model.MangaCover as MangaCoverModel
object CommonMangaItemDefaults { object CommonMangaItemDefaults {
val GridHorizontalSpacer = 4.dp val GridHorizontalSpacer = 4.dp
val GridVerticalSpacer = 4.dp val GridVerticalSpacer = 4.dp
@Suppress("ConstPropertyName")
const val BrowseFavoriteCoverAlpha = 0.34f const val BrowseFavoriteCoverAlpha = 0.34f
} }
private val ContinueReadingButtonSize = 28.dp private val ContinueReadingButtonSizeSmall = 28.dp
private val ContinueReadingButtonSizeLarge = 32.dp
private val ContinueReadingButtonIconSizeSmall = 16.dp
private val ContinueReadingButtonIconSizeLarge = 20.dp
private val ContinueReadingButtonGridPadding = 6.dp private val ContinueReadingButtonGridPadding = 6.dp
private val ContinueReadingButtonListSpacing = 8.dp private val ContinueReadingButtonListSpacing = 8.dp
@@ -62,7 +70,7 @@ private const val GridSelectedCoverAlpha = 0.76f
*/ */
@Composable @Composable
fun MangaCompactGridItem( fun MangaCompactGridItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
isSelected: Boolean = false, isSelected: Boolean = false,
@@ -96,10 +104,12 @@ fun MangaCompactGridItem(
) )
} else if (onClickContinueReading != null) { } else if (onClickContinueReading != null) {
ContinueReadingButton( ContinueReadingButton(
size = ContinueReadingButtonSizeLarge,
iconSize = ContinueReadingButtonIconSizeLarge,
onClick = onClickContinueReading,
modifier = Modifier modifier = Modifier
.padding(ContinueReadingButtonGridPadding) .padding(ContinueReadingButtonGridPadding)
.align(Alignment.BottomEnd), .align(Alignment.BottomEnd),
onClickContinueReading = onClickContinueReading,
) )
} }
}, },
@@ -148,11 +158,13 @@ private fun BoxScope.CoverTextOverlay(
) )
if (onClickContinueReading != null) { if (onClickContinueReading != null) {
ContinueReadingButton( ContinueReadingButton(
size = ContinueReadingButtonSizeSmall,
iconSize = ContinueReadingButtonIconSizeSmall,
onClick = onClickContinueReading,
modifier = Modifier.padding( modifier = Modifier.padding(
end = ContinueReadingButtonGridPadding, end = ContinueReadingButtonGridPadding,
bottom = ContinueReadingButtonGridPadding, bottom = ContinueReadingButtonGridPadding,
), ),
onClickContinueReading = onClickContinueReading,
) )
} }
} }
@@ -163,7 +175,7 @@ private fun BoxScope.CoverTextOverlay(
*/ */
@Composable @Composable
fun MangaComfortableGridItem( fun MangaComfortableGridItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
title: String, title: String,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
@@ -194,10 +206,12 @@ fun MangaComfortableGridItem(
content = { content = {
if (onClickContinueReading != null) { if (onClickContinueReading != null) {
ContinueReadingButton( ContinueReadingButton(
size = ContinueReadingButtonSizeLarge,
iconSize = ContinueReadingButtonIconSizeLarge,
onClick = onClickContinueReading,
modifier = Modifier modifier = Modifier
.padding(ContinueReadingButtonGridPadding) .padding(ContinueReadingButtonGridPadding)
.align(Alignment.BottomEnd), .align(Alignment.BottomEnd),
onClickContinueReading = onClickContinueReading,
) )
} }
}, },
@@ -309,14 +323,14 @@ private fun GridItemSelectable(
private fun Modifier.selectedOutline( private fun Modifier.selectedOutline(
isSelected: Boolean, isSelected: Boolean,
color: Color, color: Color,
) = this then drawBehind { if (isSelected) drawRect(color = color) } ) = drawBehind { if (isSelected) drawRect(color = color) }
/** /**
* Layout of list item. * Layout of list item.
*/ */
@Composable @Composable
fun MangaListItem( fun MangaListItem(
coverData: tachiyomi.domain.manga.model.MangaCover, coverData: MangaCoverModel,
title: String, title: String,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
@@ -354,8 +368,10 @@ fun MangaListItem(
BadgeGroup(content = badge) BadgeGroup(content = badge)
if (onClickContinueReading != null) { if (onClickContinueReading != null) {
ContinueReadingButton( ContinueReadingButton(
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing), size = ContinueReadingButtonSizeSmall,
onClickContinueReading = onClickContinueReading, iconSize = ContinueReadingButtonIconSizeSmall,
onClick = onClickContinueReading,
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing)
) )
} }
} }
@@ -363,23 +379,25 @@ fun MangaListItem(
@Composable @Composable
private fun ContinueReadingButton( private fun ContinueReadingButton(
size: Dp,
iconSize: Dp,
onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickContinueReading: () -> Unit,
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
FilledIconButton( FilledIconButton(
onClick = onClickContinueReading, onClick = onClick,
modifier = Modifier.size(ContinueReadingButtonSize),
shape = MaterialTheme.shapes.small, shape = MaterialTheme.shapes.small,
colors = IconButtonDefaults.filledIconButtonColors( colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f), containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer), contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
), ),
modifier = Modifier.size(size)
) { ) {
Icon( Icon(
imageVector = Icons.Filled.PlayArrow, imageVector = Icons.Filled.PlayArrow,
contentDescription = stringResource(MR.strings.action_resume), contentDescription = stringResource(MR.strings.action_resume),
modifier = Modifier.size(16.dp), modifier = Modifier.size(iconSize),
) )
} }
} }
@@ -89,7 +89,7 @@ fun MangaBottomActionMenu(
Surface( Surface(
modifier = modifier, modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) } val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
@@ -251,7 +251,7 @@ fun LibraryBottomActionMenu(
Surface( Surface(
modifier = modifier, modifier = modifier,
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize), shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = val confirm =
@@ -30,7 +30,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@@ -69,9 +68,6 @@ fun MangaChapterListItem(
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
val start = getSwipeAction( val start = getSwipeAction(
action = chapterSwipeStartAction, action = chapterSwipeStartAction,
read = read, read = read,
@@ -136,15 +132,20 @@ fun MangaChapterListItem(
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height }, onTextLayout = { textHeight = it.size.height },
color = LocalContentColor.current.copy(alpha = if (read) ReadItemAlpha else 1f),
) )
} }
Row(modifier = Modifier.alpha(textSubtitleAlpha)) { Row {
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) { val subtitleStyle = MaterialTheme.typography.bodySmall
.merge(
color = LocalContentColor.current
.copy(alpha = if (read) ReadItemAlpha else SecondaryItemAlpha)
)
ProvideTextStyle(value = subtitleStyle) {
if (date != null) { if (date != null) {
Text( Text(
text = date, text = date,
@@ -38,6 +38,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import coil3.asDrawable
import coil3.imageLoader import coil3.imageLoader
import coil3.request.CachePolicy import coil3.request.CachePolicy
import coil3.request.ImageRequest import coil3.request.ImageRequest
@@ -102,9 +102,12 @@ fun SetIntervalDialog(
), ),
), ),
) )
} else {
Spacer(Modifier.height(MaterialTheme.padding.small)) Text(
stringResource(MR.strings.manga_interval_expected_update_null),
)
} }
Spacer(Modifier.height(MaterialTheme.padding.small))
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) { if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
Text(stringResource(MR.strings.manga_interval_custom_amount)) Text(stringResource(MR.strings.manga_interval_custom_amount))
@@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.animation.graphics.vector.AnimatedImageVector
@@ -289,7 +290,8 @@ fun ExpandableMangaDescription(
modifier = Modifier modifier = Modifier
.padding(top = 8.dp) .padding(top = 8.dp)
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
.animateContentSize(), .animateContentSize(animationSpec = spring())
.fillMaxWidth(),
) { ) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
var tagSelected by remember { mutableStateOf("") } var tagSelected by remember { mutableStateOf("") }
@@ -111,8 +111,14 @@ fun ScanlatorFilterDialog(
} }
} else { } else {
FlowRow { FlowRow {
TextButton(onClick = mutableExcludedScanlators::clear) { if (mutableExcludedScanlators.isEmpty()) {
Text(text = stringResource(MR.strings.action_reset)) TextButton(onClick = { mutableExcludedScanlators.addAll(availableScanlators) }) {
Text(text = stringResource(MR.strings.action_select_all))
}
} else {
TextButton(onClick = mutableExcludedScanlators::clear) {
Text(text = stringResource(MR.strings.action_reset))
}
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TextButton(onClick = onDismissRequest) { TextButton(onClick = onDismissRequest) {
@@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.runtime.structuralEqualityPolicy
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
import eu.kanade.presentation.more.settings.widget.InfoWidget import eu.kanade.presentation.more.settings.widget.InfoWidget
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
@@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp } val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
@@ -156,16 +154,14 @@ internal fun PreferenceItem(
) )
} }
is Preference.PreferenceItem.TrackerPreference -> { is Preference.PreferenceItem.TrackerPreference -> {
val uName by Injekt.get<TrackPreferences>() val isLoggedIn by item.tracker.let { tracker ->
.trackUsername(item.tracker) tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
.collectAsState()
item.tracker.run {
TrackingPreferenceWidget(
tracker = this,
checked = uName.isNotEmpty(),
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
} }
TrackingPreferenceWidget(
tracker = item.tracker,
checked = isLoggedIn,
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
} }
is Preference.PreferenceItem.InfoPreference -> { is Preference.PreferenceItem.InfoPreference -> {
InfoWidget(text = item.title) InfoWidget(text = item.title)
@@ -127,7 +127,17 @@ object SettingsDataScreen : SearchableSettings {
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags) // For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not
// work. However, simply retrieving the URI and using it works fine for these devices. Access is not
// revoked after the app is closed or the device is restarted.
// This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and
// ignore the exception if it is thrown.
try {
context.contentResolver.takePersistableUriPermission(uri, flags)
} catch (e: SecurityException) {
logcat(LogPriority.ERROR, e)
context.toast(MR.strings.file_picker_uri_permission_unsupported)
}
UniFile.fromUri(context, uri)?.let { UniFile.fromUri(context, uri)?.let {
storageDirPref.set(it.uri.toString()) storageDirPref.set(it.uri.toString())
@@ -84,9 +84,6 @@ object SettingsLibraryScreen : SearchableSettings {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
val defaultCategory by libraryPreferences.defaultCategory().collectAsState()
val selectedCategory = allCategories.find { it.id == defaultCategory.toLong() }
// For default category // For default category
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) + val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
allCategories.fastMap { it.id.toInt() } allCategories.fastMap { it.id.toInt() }
@@ -108,7 +105,6 @@ object SettingsLibraryScreen : SearchableSettings {
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.defaultCategory(), pref = libraryPreferences.defaultCategory(),
title = stringResource(MR.strings.default_category), title = stringResource(MR.strings.default_category),
subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary),
entries = ids.zip(labels).toMap().toImmutableMap(), entries = ids.zip(labels).toMap().toImmutableMap(),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
@@ -17,6 +17,7 @@ import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@@ -88,12 +89,8 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
SY <-- */ SY <-- */
Preference.PreferenceItem.SwitchPreference(
pref = readerPref.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
getDisplayGroup(readerPreferences = readerPref), getDisplayGroup(readerPreferences = readerPref),
getEInkGroup(readerPreferences = readerPref),
getReadingGroup(readerPreferences = readerPref), getReadingGroup(readerPreferences = readerPref),
getPagedGroup(readerPreferences = readerPref), getPagedGroup(readerPreferences = readerPref),
getWebtoonGroup(readerPreferences = readerPref), getWebtoonGroup(readerPreferences = readerPref),
@@ -156,6 +153,65 @@ object SettingsReaderScreen : SearchableSettings {
) )
} }
@Composable
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
val flashMillisPref = readerPreferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = readerPreferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor()
return Preference.PreferenceGroup(
title = "E-Ink",
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
Preference.PreferenceItem.SliderPreference(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
min = 1,
max = 15,
title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onValueChanged = {
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.SliderPreference(
value = flashInterval,
min = 1,
max = 10,
title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onValueChanged = {
flashIntervalPref.set(it)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.ListPreference(
pref = flashColorPref,
title = stringResource(MR.strings.pref_flash_with),
entries = persistentMapOf(
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
ReaderPreferences.FlashColor.WHITE_BLACK
to stringResource(MR.strings.pref_flash_style_white_black),
),
enabled = flashPageState,
),
),
)
}
@Composable @Composable
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
@@ -6,7 +6,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -69,7 +68,7 @@ class CreateBackupScreen : Screen() {
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.action_create), actionLabel = stringResource(MR.strings.action_create),
actionEnabled = state.options.anyEnabled(), actionEnabled = state.options.canCreate(),
onClickAction = { onClickAction = {
if (!BackupCreateJob.isManualJobRunning(context)) { if (!BackupCreateJob.isManualJobRunning(context)) {
try { try {
@@ -104,7 +103,7 @@ class CreateBackupScreen : Screen() {
} }
@Composable @Composable
private fun ColumnScope.Options( private fun Options(
options: ImmutableList<BackupOptions.Entry>, options: ImmutableList<BackupOptions.Entry>,
state: CreateBackupScreenModel.State, state: CreateBackupScreenModel.State,
model: CreateBackupScreenModel, model: CreateBackupScreenModel,
@@ -63,7 +63,7 @@ class RestoreBackupScreen(
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(MR.strings.action_restore), actionLabel = stringResource(MR.strings.action_restore),
actionEnabled = state.canRestore && state.options.anyEnabled(), actionEnabled = state.canRestore && state.options.canRestore(),
onClickAction = { onClickAction = {
model.startRestore() model.startRestore()
navigator.pop() navigator.pop()
@@ -49,7 +49,7 @@ class SyncSettingsSelector : Screen() {
LazyColumnWithAction( LazyColumnWithAction(
contentPadding = contentPadding, contentPadding = contentPadding,
actionLabel = stringResource(SYMR.strings.label_sync), actionLabel = stringResource(SYMR.strings.label_sync),
actionEnabled = state.options.anyEnabled(), actionEnabled = state.options.canCreate(),
onClickAction = { onClickAction = {
if (!SyncDataJob.isRunning(context)) { if (!SyncDataJob.isRunning(context)) {
model.syncNow(context) model.syncNow(context)
@@ -122,12 +122,14 @@ private class SyncSettingsSelectorModel(
tracking = syncSettings.tracking, tracking = syncSettings.tracking,
history = syncSettings.history, history = syncSettings.history,
appSettings = syncSettings.appSettings, appSettings = syncSettings.appSettings,
extensionRepoSettings = syncSettings.extensionRepoSettings,
sourceSettings = syncSettings.sourceSettings, sourceSettings = syncSettings.sourceSettings,
privateSettings = syncSettings.privateSettings, privateSettings = syncSettings.privateSettings,
// SY --> // SY -->
customInfo = syncSettings.customInfo, customInfo = syncSettings.customInfo,
readEntries = syncSettings.readEntries, readEntries = syncSettings.readEntries,
savedSearches = syncSettings.savedSearches,
// SY <-- // SY <--
) )
} }
@@ -140,12 +142,14 @@ private class SyncSettingsSelectorModel(
tracking = backupOptions.tracking, tracking = backupOptions.tracking,
history = backupOptions.history, history = backupOptions.history,
appSettings = backupOptions.appSettings, appSettings = backupOptions.appSettings,
extensionRepoSettings = backupOptions.extensionRepoSettings,
sourceSettings = backupOptions.sourceSettings, sourceSettings = backupOptions.sourceSettings,
privateSettings = backupOptions.privateSettings, privateSettings = backupOptions.privateSettings,
// SY --> // SY -->
customInfo = backupOptions.customInfo, customInfo = backupOptions.customInfo,
readEntries = backupOptions.readEntries, readEntries = backupOptions.readEntries,
savedSearches = backupOptions.savedSearches,
// SY <-- // SY <--
) )
} }
@@ -223,13 +223,12 @@ fun AppThemePreviewItem(
contentAlignment = Alignment.BottomCenter, contentAlignment = Alignment.BottomCenter,
) { ) {
Surface( Surface(
tonalElevation = 3.dp, color = MaterialTheme.colorScheme.surfaceContainer,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.height(32.dp) .height(32.dp)
.fillMaxWidth() .fillMaxWidth()
.background(MaterialTheme.colorScheme.surfaceVariant)
.padding(horizontal = 8.dp), .padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.milliseconds
@Stable @Stable
class DisplayRefreshHost { class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false) internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis()
internal val flashMode = readerPreferences.flashColor()
internal val flashIntervalPref = readerPreferences.flashPageInterval()
// Internal State for Flash
private var flashInterval = flashIntervalPref.get()
private var timesCalled = 0
fun flash() { fun flash() {
currentDisplayRefresh = true if (timesCalled % flashInterval == 0) {
currentDisplayRefresh = true
}
timesCalled += 1
}
fun setInterval(interval: Int) {
flashInterval = interval
timesCalled = 0
} }
} }
@@ -29,18 +52,39 @@ fun DisplayRefreshHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val currentDisplayRefresh = hostState.currentDisplayRefresh val currentDisplayRefresh = hostState.currentDisplayRefresh
val refreshDuration by hostState.flashMillis.collectAsState()
val flashMode by hostState.flashMode.collectAsState()
val flashInterval by hostState.flashIntervalPref.collectAsState()
var currentColor by remember { mutableStateOf<Color?>(null) }
LaunchedEffect(currentDisplayRefresh) { LaunchedEffect(currentDisplayRefresh) {
if (currentDisplayRefresh) { if (!currentDisplayRefresh) {
delay(1.5.seconds) currentColor = null
hostState.currentDisplayRefresh = false return@LaunchedEffect
} }
val refreshDurationHalf = refreshDuration.milliseconds / 2
currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) {
Color.Black
} else {
Color.White
}
delay(refreshDurationHalf)
if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) {
currentColor = Color.Black
}
delay(refreshDurationHalf)
hostState.currentDisplayRefresh = false
}
LaunchedEffect(flashInterval) {
hostState.setInterval(flashInterval)
} }
Canvas( Canvas(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
) { ) {
if (currentDisplayRefresh) { currentColor?.let { drawRect(it) }
drawRect(Color.Black)
}
} }
} }
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.Photo import androidx.compose.material.icons.outlined.Photo
import androidx.compose.material.icons.outlined.Save import androidx.compose.material.icons.outlined.Save
import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Share
@@ -31,9 +32,9 @@ fun ReaderPageActionsDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
// SY --> // SY -->
onSetAsCover: (useExtraPage: Boolean) -> Unit, onSetAsCover: (useExtraPage: Boolean) -> Unit,
onShare: (useExtraPage: Boolean) -> Unit, onShare: (copy: Boolean, useExtraPage: Boolean) -> Unit,
onSave: (useExtraPage: Boolean) -> Unit, onSave: (useExtraPage: Boolean) -> Unit,
onShareCombined: () -> Unit, onShareCombined: (copy: Boolean) -> Unit,
onSaveCombined: () -> Unit, onSaveCombined: () -> Unit,
hasExtraPage: Boolean, hasExtraPage: Boolean,
// SY <-- // SY <--
@@ -43,9 +44,7 @@ fun ReaderPageActionsDialog(
var useExtraPage by remember { mutableStateOf(false) } var useExtraPage by remember { mutableStateOf(false) }
// SY <-- // SY <--
AdaptiveSheet( AdaptiveSheet(onDismissRequest = onDismissRequest) {
onDismissRequest = onDismissRequest,
) {
Column(modifier = Modifier.padding(vertical = 16.dp)) { Column(modifier = Modifier.padding(vertical = 16.dp)) {
Row( Row(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
@@ -64,6 +63,25 @@ fun ReaderPageActionsDialog(
icon = Icons.Outlined.Photo, icon = Icons.Outlined.Photo,
onClick = { showSetCoverDialog = true }, onClick = { showSetCoverDialog = true },
) )
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(
// SY -->
if (hasExtraPage) {
SYMR.strings.action_copy_first_page
} else {
MR.strings.action_copy_to_clipboard
},
// SY <--
),
icon = Icons.Outlined.ContentCopy,
onClick = {
// SY -->
onShare(true, false)
// SY <--
onDismissRequest()
},
)
ActionButton( ActionButton(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
title = stringResource( title = stringResource(
@@ -78,7 +96,7 @@ fun ReaderPageActionsDialog(
icon = Icons.Outlined.Share, icon = Icons.Outlined.Share,
onClick = { onClick = {
// SY --> // SY -->
onShare(false) onShare(false, false)
// SY <-- // SY <--
onDismissRequest() onDismissRequest()
}, },
@@ -116,12 +134,21 @@ fun ReaderPageActionsDialog(
showSetCoverDialog = true showSetCoverDialog = true
}, },
) )
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(SYMR.strings.action_copy_second_page),
icon = Icons.Outlined.ContentCopy,
onClick = {
onShare(true, true)
onDismissRequest()
},
)
ActionButton( ActionButton(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
title = stringResource(SYMR.strings.action_share_second_page), title = stringResource(SYMR.strings.action_share_second_page),
icon = Icons.Outlined.Share, icon = Icons.Outlined.Share,
onClick = { onClick = {
onShare(true) onShare(false, true)
onDismissRequest() onDismissRequest()
}, },
) )
@@ -138,12 +165,21 @@ fun ReaderPageActionsDialog(
Row( Row(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
ActionButton(
modifier = Modifier.weight(1f),
title = stringResource(SYMR.strings.action_copy_combined_page),
icon = Icons.Outlined.ContentCopy,
onClick = {
onShareCombined(true)
onDismissRequest()
},
)
ActionButton( ActionButton(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
title = stringResource(SYMR.strings.action_share_combined_page), title = stringResource(SYMR.strings.action_share_combined_page),
icon = Icons.Outlined.Share, icon = Icons.Outlined.Share,
onClick = { onClick = {
onShareCombined() onShareCombined(false)
onDismissRequest() onDismissRequest()
}, },
) )
@@ -5,11 +5,14 @@ import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SettingsChipRow import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
@@ -20,9 +23,27 @@ private val themes = listOf(
MR.strings.automatic_background to 3, MR.strings.automatic_background to 3,
) )
private val flashColors = listOf(
MR.strings.pref_flash_style_black to ReaderPreferences.FlashColor.BLACK,
MR.strings.pref_flash_style_white to ReaderPreferences.FlashColor.WHITE,
MR.strings.pref_flash_style_white_black to ReaderPreferences.FlashColor.WHITE_BLACK,
)
@Composable @Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState() val readerTheme by screenModel.preferences.readerTheme().collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState()
val flashMillisPref = screenModel.preferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = screenModel.preferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor()
val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) { SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) -> themes.map { (labelRes, value) ->
FilterChip( FilterChip(
@@ -95,6 +116,35 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
label = stringResource(MR.strings.pref_flash_page), label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(), pref = screenModel.preferences.flashOnPageChange(),
) )
if (flashPageState) {
SliderItem(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
label = stringResource(MR.strings.pref_flash_duration),
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
min = 1,
max = 15,
)
SliderItem(
value = flashInterval,
label = stringResource(MR.strings.pref_flash_page_interval),
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = {
flashIntervalPref.set(it)
},
min = 1,
max = 10,
)
SettingsChipRow(MR.strings.pref_flash_with) {
flashColors.map { (labelRes, value) ->
FilterChip(
selected = flashColor == value,
onClick = { flashColorPref.set(value) },
label = { Text(stringResource(labelRes)) },
)
}
}
}
// SY --> // SY -->
CheckboxItem( CheckboxItem(
@@ -8,6 +8,12 @@ internal abstract class BaseColorScheme {
abstract val darkScheme: ColorScheme abstract val darkScheme: ColorScheme
abstract val lightScheme: ColorScheme abstract val lightScheme: ColorScheme
// Cannot be pure black as there's content scrolling behind it
// https://m3.material.io/components/navigation-bar/guidelines#90615a71-607e-485e-9e09-778bfc080563
private val surfaceContainer = Color(0xFF0C0C0C)
private val surfaceContainerHigh = Color(0xFF131313)
private val surfaceContainerHighest = Color(0xFF1B1B1B)
fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme {
if (!isDark) return lightScheme if (!isDark) return lightScheme
@@ -18,6 +24,12 @@ internal abstract class BaseColorScheme {
onBackground = Color.White, onBackground = Color.White,
surface = Color.Black, surface = Color.Black,
onSurface = Color.White, onSurface = Color.White,
surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget)
surfaceContainerLowest = surfaceContainer,
surfaceContainerLow = surfaceContainer,
surfaceContainer = surfaceContainer, // Navigation bar background
surfaceContainerHigh = surfaceContainerHigh,
surfaceContainerHighest = surfaceContainerHighest,
) )
} }
} }
@@ -19,53 +19,77 @@ internal object GreenAppleColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFF7ADB8F), primary = Color(0xFF7ADB8F),
onPrimary = Color(0xFF003915), onPrimary = Color(0xFF003917),
primaryContainer = Color(0xFF005322), primaryContainer = Color(0xFF017737),
onPrimaryContainer = Color(0xFF96F8A9), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFF7ADB8F), // Unread badge
secondary = Color(0xFF7ADB8F), onSecondary = Color(0xFF003917), // Unread badge text
onSecondary = Color(0xFF003915), secondaryContainer = Color(0xFF017737), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF005322), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF96F8A9), tertiary = Color(0xFFFFB3AC), // Downloaded badge
tertiary = Color(0xFFFFB3AA), onTertiary = Color(0xFF680008), // Downloaded badge text
onTertiary = Color(0xFF680006), tertiaryContainer = Color(0xFFC7282A),
tertiaryContainer = Color(0xFF93000D), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFFFFDAD5), error = Color(0xFFFFB4AB),
background = Color(0xFF1A1C19), onError = Color(0xFF690005),
onBackground = Color(0xFFE1E3DD), errorContainer = Color(0xFF93000A),
surface = Color(0xFF1A1C19), onErrorContainer = Color(0xFFFFDAD6),
onSurface = Color(0xFFE1E3DD), background = Color(0xFF0F1510),
surfaceVariant = Color(0xFF414941), onBackground = Color(0xFFDFE4DB),
onSurfaceVariant = Color(0xFFC1C8BE), surface = Color(0xFF0F1510),
surfaceTint = Color(0xFF7ADB8F), onSurface = Color(0xFFDFE4DB),
inverseSurface = Color(0xFFE1E3DD), surfaceVariant = Color(0xFF3F493F), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFF1A1C19), onSurfaceVariant = Color(0xFFBECABC),
outline = Color(0xFF8B9389), outline = Color(0xFF889487),
outlineVariant = Color(0xFF3F493F),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFFDFE4DB),
inverseOnSurface = Color(0xFF2C322C),
inversePrimary = Color(0xFF006D32),
surfaceDim = Color(0xFF0F1510),
surfaceBright = Color(0xFF353B35),
surfaceContainerLowest = Color(0xFF0A0F0B),
surfaceContainerLow = Color(0xFF181D18),
surfaceContainer = Color(0xFF1C211C), // Navigation bar background
surfaceContainerHigh = Color(0xFF262B26),
surfaceContainerHighest = Color(0xFF313630),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF006D2F), primary = Color(0xFF005927),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF96F8A9), primaryContainer = Color(0xFF188140),
onPrimaryContainer = Color(0xFF002109), onPrimaryContainer = Color(0xFFFFFFFF),
secondary = Color(0xFF005927), // Unread badge
onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFF97f7a9), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF000000), // Navigation bar selected icon
tertiary = Color(0xFF9D0012), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFD33131),
onTertiaryContainer = Color(0xFFFFFFFF),
error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFF6FBF2),
onBackground = Color(0xFF181D18),
surface = Color(0xFFF6FBF2),
onSurface = Color(0xFF181D18),
surfaceVariant = Color(0xFFDAE6D7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF3F493F),
outline = Color(0xFF6F7A6E),
outlineVariant = Color(0xFFBECABC),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF2C322C),
inverseOnSurface = Color(0xFFEDF2E9),
inversePrimary = Color(0xFF7ADB8F), inversePrimary = Color(0xFF7ADB8F),
secondary = Color(0xFF006D2F), surfaceDim = Color(0xFFD6DCD3),
onSecondary = Color(0xFFFFFFFF), surfaceBright = Color(0xFFF6FBF2),
secondaryContainer = Color(0xFF96F8A9), surfaceContainerLowest = Color(0xFFFFFFFF),
onSecondaryContainer = Color(0xFF002109), surfaceContainerLow = Color(0xFFF0F5EC),
tertiary = Color(0xFFB91D22), surfaceContainer = Color(0xFFEAEFE6), // Navigation bar background
onTertiary = Color(0xFFFFFFFF), surfaceContainerHigh = Color(0xFFE4EAE1),
tertiaryContainer = Color(0xFFFFDAD5), surfaceContainerHighest = Color(0xFFDFE4DB),
onTertiaryContainer = Color(0xFF410003),
background = Color(0xFFFBFDF7),
onBackground = Color(0xFF1A1C19),
surface = Color(0xFFFBFDF7),
onSurface = Color(0xFF1A1C19),
surfaceVariant = Color(0xFFDDE5DA),
onSurfaceVariant = Color(0xFF414941),
surfaceTint = Color(0xFF006D2F),
inverseSurface = Color(0xFF2F312E),
inverseOnSurface = Color(0xFFF0F2EC),
outline = Color(0xFF717970),
) )
} }
@@ -18,53 +18,77 @@ internal object LavenderColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFA177FF), primary = Color(0xFFA177FF),
onPrimary = Color(0xFF111129), onPrimary = Color(0xFF3D0090),
primaryContainer = Color(0xFFA177FF), primaryContainer = Color(0xFFA177FF),
onPrimaryContainer = Color(0xFF111129), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF006D2F), secondary = Color(0xFFA177FF), // Unread badge
secondary = Color(0xFFA177FF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFF111129), secondaryContainer = Color(0xFF423271), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFA177FF), onSecondaryContainer = Color(0xFFA177FF), // Navigation bar selected icon
onSecondaryContainer = Color(0xFF111129), tertiary = Color(0xFFCDBDFF), // Downloaded badge
tertiary = Color(0xFF5E25E1), onTertiary = Color(0xFF360096), // Downloaded badge text
onTertiary = Color(0xFFE8E8E8), tertiaryContainer = Color(0xFF5512D8),
tertiaryContainer = Color(0xFF111129), onTertiaryContainer = Color(0xFFEFE6FF),
onTertiaryContainer = Color(0xFFDEE8FF), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF111129), background = Color(0xFF111129),
onBackground = Color(0xFFDEE8FF), onBackground = Color(0xFFE7E0EC),
surface = Color(0xFF111129), surface = Color(0xFF111129),
onSurface = Color(0xFFDEE8FF), onSurface = Color(0xFFE7E0EC),
surfaceVariant = Color(0x2CB6B6B6), surfaceVariant = Color(0xFF3D2F6B), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFE8E8E8), onSurfaceVariant = Color(0xFFCBC3D6),
surfaceTint = Color(0xFFA177FF), outline = Color(0xFF958E9F),
inverseSurface = Color(0xFF221247), outlineVariant = Color(0xFF4A4453),
inverseOnSurface = Color(0xFFDEE8FF), scrim = Color(0xFF000000),
outline = Color(0xA8905FFF), inverseSurface = Color(0xFFE7E0EC),
inverseOnSurface = Color(0xFF322F38),
inversePrimary = Color(0xFF6D41C8),
surfaceDim = Color(0xFF111129),
surfaceBright = Color(0xFF3B3841),
surfaceContainerLowest = Color(0xFF15132d),
surfaceContainerLow = Color(0xFF171531),
surfaceContainer = Color(0xFF1D193B), // Navigation bar background
surfaceContainerHigh = Color(0xFF241f41),
surfaceContainerHighest = Color(0xFF282446),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFF7B46AF), primary = Color(0xFF6D41C8),
onPrimary = Color(0xFFEDE2FF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFF7B46AF), primaryContainer = Color(0xFF7B46AF),
onPrimaryContainer = Color(0xFFEDE2FF), onPrimaryContainer = Color(0xFF130038),
inversePrimary = Color(0xFFD6BAFF), secondary = Color(0xFF7B46AF), // Unread badge
secondary = Color(0xFF7B46AF), onSecondary = Color(0xFFEDE2FF), // Unread badge text
onSecondary = Color(0xFFEDE2FF), secondaryContainer = Color(0xFFC9B0E6), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF7B46AF), onSecondaryContainer = Color(0xFF7B46AF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFEDE2FF), tertiary = Color(0xFFEDE2FF), // Downloaded badge
tertiary = Color(0xFFEDE2FF), onTertiary = Color(0xFF7B46AF), // Downloaded badge text
onTertiary = Color(0xFF7B46AF), tertiaryContainer = Color(0xFF6D3BF0),
tertiaryContainer = Color(0xFFEDE2FF), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF7B46AF), error = Color(0xFFBA1A1A),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFEDE2FF), background = Color(0xFFEDE2FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1D1A22),
surface = Color(0xFFEDE2FF), surface = Color(0xFFEDE2FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1D1A22),
surfaceVariant = Color(0xFFB9B0CC), surfaceVariant = Color(0xFFE4D5F8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xD849454E), onSurfaceVariant = Color(0xFF4A4453),
surfaceTint = Color(0xFF7B46AF), outline = Color(0xFF7B7485),
inverseSurface = Color(0xFF313033), outlineVariant = Color(0xFFCBC3D6),
inverseOnSurface = Color(0xFFF3EFF4), scrim = Color(0xFF000000),
outline = Color(0xFF7B46AF), inverseSurface = Color(0xFF322F38),
inverseOnSurface = Color(0xFFF5EEFA),
inversePrimary = Color(0xFFA177FF),
surfaceDim = Color(0xFFDED7E3),
surfaceBright = Color(0xFFEDE2FF),
surfaceContainerLowest = Color(0xFFDACCEC),
surfaceContainerLow = Color(0xFFDED0F1),
surfaceContainer = Color(0xFFE4D5F8), // Navigation bar background
surfaceContainerHigh = Color(0xFFEADCFD),
surfaceContainerHighest = Color(0xFFEEE2FF),
) )
} }
@@ -23,24 +23,29 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFBD1C5C), primaryContainer = Color(0xFFBD1C5C),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFF02475), inversePrimary = Color(0xFFF02475),
secondary = Color(0xFFF02475), secondary = Color(0xFFF02475), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFF16151D), // Unread badge text
secondaryContainer = Color(0xFFF02475), secondaryContainer = Color(0xFF66183C), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFFFFF), onSecondaryContainer = Color(0xFFF02475), // Navigation bar selector icon
tertiary = Color(0xFF55971C), tertiary = Color(0xFF55971C), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFF16151D), // Downloaded badge text
tertiaryContainer = Color(0xFF386412), tertiaryContainer = Color(0xFF386412),
onTertiaryContainer = Color(0xFFE5E1E5), onTertiaryContainer = Color(0xFFE5E1E5),
background = Color(0xFF16151D), background = Color(0xFF16151D),
onBackground = Color(0xFFE5E1E5), onBackground = Color(0xFFE5E1E5),
surface = Color(0xFF16151D), surface = Color(0xFF16151D),
onSurface = Color(0xFFE5E1E5), onSurface = Color(0xFFE5E1E5),
surfaceVariant = Color(0xFF524346), surfaceVariant = Color(0xFF281624), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD6C1C4), onSurfaceVariant = Color(0xFFD6C1C4),
surfaceTint = Color(0xFFF02475), surfaceTint = Color(0xFFF02475),
inverseSurface = Color(0xFF333043), inverseSurface = Color(0xFF333043),
inverseOnSurface = Color(0xFFFFFFFF), inverseOnSurface = Color(0xFFFFFFFF),
outline = Color(0xFF9F8C8F), outline = Color(0xFF9F8C8F),
surfaceContainerLowest = Color(0xFF221320),
surfaceContainerLow = Color(0xFF251522),
surfaceContainer = Color(0xFF281624), // Navigation bar background
surfaceContainerHigh = Color(0xFF2D1C2A),
surfaceContainerHighest = Color(0xFF2F1F2C),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object MidnightDuskColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFD9E1), primaryContainer = Color(0xFFFFD9E1),
onPrimaryContainer = Color(0xFF3F0017), onPrimaryContainer = Color(0xFF3F0017),
inversePrimary = Color(0xFFFFB1C4), inversePrimary = Color(0xFFFFB1C4),
secondary = Color(0xFFBB0054), secondary = Color(0xFFBB0054), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFD9E1), secondaryContainer = Color(0xFFEFBAD4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3F0017), onSecondaryContainer = Color(0xFFD1377C), // Navigation bar selector icon
tertiary = Color(0xFF006638), tertiary = Color(0xFF006638), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00894b), tertiaryContainer = Color(0xFF00894b),
onTertiaryContainer = Color(0xFF2D1600), onTertiaryContainer = Color(0xFF2D1600),
background = Color(0xFFFFFBFF), background = Color(0xFFFFFBFF),
onBackground = Color(0xFF1C1B1F), onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFF), surface = Color(0xFFFFFBFF),
onSurface = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFF3DDE0), surfaceVariant = Color(0xFFF9E6F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF524346), onSurfaceVariant = Color(0xFF524346),
surfaceTint = Color(0xFFBB0054), surfaceTint = Color(0xFFBB0054),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF4F0F4), inverseOnSurface = Color(0xFFF4F0F4),
outline = Color(0xFF847376), outline = Color(0xFF847376),
surfaceContainerLowest = Color(0xFFDAC0CD),
surfaceContainerLow = Color(0xFFE8D1DD),
surfaceContainer = Color(0xFFF9E6F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF3F8),
surfaceContainerHighest = Color(0xFFFEF9FC),
) )
} }
@@ -17,19 +17,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF88C0D0), primaryContainer = Color(0xFF88C0D0),
onPrimaryContainer = Color(0xFF2E3440), onPrimaryContainer = Color(0xFF2E3440),
inversePrimary = Color(0xFF397E91), inversePrimary = Color(0xFF397E91),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF506275), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF88C0D0), // Navigation bar selector icon
tertiary = Color(0xFF5E81AC), tertiary = Color(0xFF5E81AC), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF5E81AC), tertiaryContainer = Color(0xFF5E81AC),
onTertiaryContainer = Color(0xFF000000), onTertiaryContainer = Color(0xFF000000),
background = Color(0xFF2E3440), background = Color(0xFF2E3440),
onBackground = Color(0xFFECEFF4), onBackground = Color(0xFFECEFF4),
surface = Color(0xFF3B4252), surface = Color(0xFF2E3440),
onSurface = Color(0xFFECEFF4), onSurface = Color(0xFFECEFF4),
surfaceVariant = Color(0xFF2E3440), surfaceVariant = Color(0xFF414C5C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFECEFF4), onSurfaceVariant = Color(0xFFECEFF4),
surfaceTint = Color(0xFF88C0D0), surfaceTint = Color(0xFF88C0D0),
inverseSurface = Color(0xFFD8DEE9), inverseSurface = Color(0xFFD8DEE9),
@@ -39,6 +39,11 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFF2E3440), onError = Color(0xFF2E3440),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFF373F4D),
surfaceContainerLow = Color(0xFF3E4756),
surfaceContainer = Color(0xFF414C5C),
surfaceContainerHigh = Color(0xFF4E5766),
surfaceContainerHighest = Color(0xFF505968), // Navigation bar background
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -47,19 +52,19 @@ internal object NordColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF5E81AC), primaryContainer = Color(0xFF5E81AC),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF8CA8CD), inversePrimary = Color(0xFF8CA8CD),
secondary = Color(0xFF81A1C1), secondary = Color(0xFF81A1C1), // Unread badge
onSecondary = Color(0xFF2E3440), onSecondary = Color(0xFF2E3440), // Unread badge text
secondaryContainer = Color(0xFF81A1C1), secondaryContainer = Color(0xFF91B4D7), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF2E3440), onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon
tertiary = Color(0xFF88C0D0), tertiary = Color(0xFF88C0D0), // Downloaded badge
onTertiary = Color(0xFF2E3440), onTertiary = Color(0xFF2E3440), // Downloaded badge text
tertiaryContainer = Color(0xFF88C0D0), tertiaryContainer = Color(0xFF88C0D0),
onTertiaryContainer = Color(0xFF2E3440), onTertiaryContainer = Color(0xFF2E3440),
background = Color(0xFFECEFF4), background = Color(0xFFECEFF4),
onBackground = Color(0xFF2E3440), onBackground = Color(0xFF2E3440),
surface = Color(0xFFE5E9F0), surface = Color(0xFFE5E9F0),
onSurface = Color(0xFF2E3440), onSurface = Color(0xFF2E3440),
surfaceVariant = Color(0xFFffffff), surfaceVariant = Color(0xFFDAE0EA), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF2E3440), onSurfaceVariant = Color(0xFF2E3440),
surfaceTint = Color(0xFF5E81AC), surfaceTint = Color(0xFF5E81AC),
inverseSurface = Color(0xFF3B4252), inverseSurface = Color(0xFF3B4252),
@@ -68,5 +73,10 @@ internal object NordColorScheme : BaseColorScheme() {
onError = Color(0xFFECEFF4), onError = Color(0xFFECEFF4),
errorContainer = Color(0xFFBF616A), errorContainer = Color(0xFFBF616A),
onErrorContainer = Color(0xFF000000), onErrorContainer = Color(0xFF000000),
surfaceContainerLowest = Color(0xFFD1D7E0),
surfaceContainerLow = Color(0xFFD6DCE6),
surfaceContainer = Color(0xFFDAE0EA), // Navigation bar background
surfaceContainerHigh = Color(0xFFE9EDF3),
surfaceContainerHighest = Color(0xFFF2F4F8),
) )
} }
@@ -18,54 +18,78 @@ import androidx.compose.ui.graphics.Color
internal object StrawberryColorScheme : BaseColorScheme() { internal object StrawberryColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme( override val darkScheme = darkColorScheme(
primary = Color(0xFFFFB2B9), primary = Color(0xFFFFB2B8),
onPrimary = Color(0xFF67001B), onPrimary = Color(0xFF67001D),
primaryContainer = Color(0xFF91002A), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFFFFDADD), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFB61E40), secondary = Color(0xFFED4A65), // Unread badge
secondary = Color(0xFFFFB2B9), onSecondary = Color(0xFF201A1A), // Unread badge text
onSecondary = Color(0xFF67001B), secondaryContainer = Color(0xFF91002A), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFF91002A), onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selector icon
onSecondaryContainer = Color(0xFFFFDADD), tertiary = Color(0xFFE8C08E), // Downloaded badge
tertiary = Color(0xFFE8C08E), onTertiary = Color(0xFF201A1A), // Downloaded badge text
onTertiary = Color(0xFF432C06), tertiaryContainer = Color(0xFF775930),
tertiaryContainer = Color(0xFF5D421B), onTertiaryContainer = Color(0xFFFFF7F1),
onTertiaryContainer = Color(0xFFFFDDB1), error = Color(0xFFFFB4AB),
onError = Color(0xFF690005),
errorContainer = Color(0xFF93000A),
onErrorContainer = Color(0xFFFFDAD6),
background = Color(0xFF201A1A), background = Color(0xFF201A1A),
onBackground = Color(0xFFECDFDF), onBackground = Color(0xFFF7DCDD),
surface = Color(0xFF201A1A), surface = Color(0xFF201A1A),
onSurface = Color(0xFFECDFDF), onSurface = Color(0xFFF7DCDD),
surfaceVariant = Color(0xFF534344), surfaceVariant = Color(0xFF322727), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD7C1C2), onSurfaceVariant = Color(0xFFE1BEC0),
surfaceTint = Color(0xFFFFB2B9), outline = Color(0xFFA9898B),
inverseSurface = Color(0xFFECDFDF), outlineVariant = Color(0xFF594042),
inverseOnSurface = Color(0xFF201A1A), scrim = Color(0xFF000000),
outline = Color(0xFFA08C8D), inverseSurface = Color(0xFFF7DCDD),
inverseOnSurface = Color(0xFF3D2C2D),
inversePrimary = Color(0xFFB61F40),
surfaceDim = Color(0xFF1D1011),
surfaceBright = Color(0xFF463536),
surfaceContainerLowest = Color(0xFF2C2222),
surfaceContainerLow = Color(0xFF302525),
surfaceContainer = Color(0xFF322727), // Navigation bar background
surfaceContainerHigh = Color(0xFF3C2F2F),
surfaceContainerHighest = Color(0xFF463737),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
primary = Color(0xFFB61E40), primary = Color(0xFFA10833),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFFFDADD), primaryContainer = Color(0xFFD53855),
onPrimaryContainer = Color(0xFF40000D), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFFFB2B9), secondary = Color(0xFFA10833), // Unread badge
secondary = Color(0xFFB61E40), onSecondary = Color(0xFFFFFFFF), // Unread badge text
onSecondary = Color(0xFFFFFFFF), secondaryContainer = Color(0xFFD53855), // Navigation bar selector pill & progress indicator (remaining)
secondaryContainer = Color(0xFFFFDADD), onSecondaryContainer = Color(0xFFF6EAED), // Navigation bar selector icon
onSecondaryContainer = Color(0xFF40000D), tertiary = Color(0xFF5F441D), // Downloaded badge
tertiary = Color(0xFF775930), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
onTertiary = Color(0xFFFFFFFF), tertiaryContainer = Color(0xFF87683D),
tertiaryContainer = Color(0xFFFFDDB1), onTertiaryContainer = Color(0xFFFFFFFF),
onTertiaryContainer = Color(0xFF2A1800), error = Color(0xFFBA1A1A),
background = Color(0xFFFCFCFC), onError = Color(0xFFFFFFFF),
onBackground = Color(0xFF201A1A), errorContainer = Color(0xFFFFDAD6),
surface = Color(0xFFFCFCFC), onErrorContainer = Color(0xFF410002),
onSurface = Color(0xFF201A1A), background = Color(0xFFFAFAFA),
surfaceVariant = Color(0xFFF4DDDD), onBackground = Color(0xFF261819),
onSurfaceVariant = Color(0xFF534344), surface = Color(0xFFFAFAFA),
surfaceTint = Color(0xFFB61E40), onSurface = Color(0xFF261819),
inverseSurface = Color(0xFF362F2F), surfaceVariant = Color(0xFFF6EAED), // Navigation bar background (ThemePrefWidget)
inverseOnSurface = Color(0xFFFBEDED), onSurfaceVariant = Color(0xFF594042),
outline = Color(0xFF857374), outline = Color(0xFF8D7071),
outlineVariant = Color(0xFFE1BEC0),
scrim = Color(0xFF000000),
inverseSurface = Color(0xFF3D2C2D),
inverseOnSurface = Color(0xFFFFECED),
inversePrimary = Color(0xFFFFB2B8),
surfaceDim = Color(0xFFEED4D5),
surfaceBright = Color(0xFFFFF8F7),
surfaceContainerLowest = Color(0xFFF7DCDD),
surfaceContainerLow = Color(0xFFFDE2E3),
surfaceContainer = Color(0xFFF6EAED), // Navigation bar background
surfaceContainerHigh = Color(0xFFFFF0F0),
surfaceContainerHighest = Color(0xFFFFFFFF),
) )
} }
@@ -22,19 +22,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF00429B), primaryContainer = Color(0xFF00429B),
onPrimaryContainer = Color(0xFFD9E2FF), onPrimaryContainer = Color(0xFFD9E2FF),
inversePrimary = Color(0xFF0058CA), inversePrimary = Color(0xFF0058CA),
secondary = Color(0xFFB0C6FF), secondary = Color(0xFFB0C6FF), // Unread badge
onSecondary = Color(0xFF002D6E), onSecondary = Color(0xFF002D6E), // Unread badge text
secondaryContainer = Color(0xFF00429B), secondaryContainer = Color(0xFF00429B), // Navigation bar selector pill & pro
onSecondaryContainer = Color(0xFFD9E2FF), onSecondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector icon
tertiary = Color(0xFF7ADC77), tertiary = Color(0xFF7ADC77), // Downloaded badge
onTertiary = Color(0xFF003909), onTertiary = Color(0xFF003909), // Downloaded badge text
tertiaryContainer = Color(0xFF005312), tertiaryContainer = Color(0xFF005312),
onTertiaryContainer = Color(0xFF95F990), onTertiaryContainer = Color(0xFF95F990),
background = Color(0xFF1B1B1F), background = Color(0xFF1B1B1F),
onBackground = Color(0xFFE3E2E6), onBackground = Color(0xFFE3E2E6),
surface = Color(0xFF1B1B1F), surface = Color(0xFF1B1B1F),
onSurface = Color(0xFFE3E2E6), onSurface = Color(0xFFE3E2E6),
surfaceVariant = Color(0xFF44464F), surfaceVariant = Color(0xFF211F26), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFC5C6D0), onSurfaceVariant = Color(0xFFC5C6D0),
surfaceTint = Color(0xFFB0C6FF), surfaceTint = Color(0xFFB0C6FF),
inverseSurface = Color(0xFFE3E2E6), inverseSurface = Color(0xFFE3E2E6),
@@ -45,6 +45,11 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFFFFDAD6), onErrorContainer = Color(0xFFFFDAD6),
outline = Color(0xFF8F9099), outline = Color(0xFF8F9099),
outlineVariant = Color(0xFF44464F), outlineVariant = Color(0xFF44464F),
surfaceContainerLowest = Color(0xFF1A181D),
surfaceContainerLow = Color(0xFF1E1C22),
surfaceContainer = Color(0xFF211F26), // Navigation bar background
surfaceContainerHigh = Color(0xFF292730),
surfaceContainerHighest = Color(0xFF302E38),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -53,19 +58,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFD9E2FF), primaryContainer = Color(0xFFD9E2FF),
onPrimaryContainer = Color(0xFF001945), onPrimaryContainer = Color(0xFF001945),
inversePrimary = Color(0xFFB0C6FF), inversePrimary = Color(0xFFB0C6FF),
secondary = Color(0xFF0058CA), secondary = Color(0xFF0058CA), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFD9E2FF), secondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001945), onSecondaryContainer = Color(0xFF001945), // Navigation bar selector icon
tertiary = Color(0xFF006E1B), tertiary = Color(0xFF006E1B), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF95F990), tertiaryContainer = Color(0xFF95F990),
onTertiaryContainer = Color(0xFF002203), onTertiaryContainer = Color(0xFF002203),
background = Color(0xFFFEFBFF), background = Color(0xFFFEFBFF),
onBackground = Color(0xFF1B1B1F), onBackground = Color(0xFF1B1B1F),
surface = Color(0xFFFEFBFF), surface = Color(0xFFFEFBFF),
onSurface = Color(0xFF1B1B1F), onSurface = Color(0xFF1B1B1F),
surfaceVariant = Color(0xFFE1E2EC), surfaceVariant = Color(0xFFF3EDF7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF44464F), onSurfaceVariant = Color(0xFF44464F),
surfaceTint = Color(0xFF0058CA), surfaceTint = Color(0xFF0058CA),
inverseSurface = Color(0xFF303034), inverseSurface = Color(0xFF303034),
@@ -76,5 +81,10 @@ internal object TachiyomiColorScheme : BaseColorScheme() {
onErrorContainer = Color(0xFF410002), onErrorContainer = Color(0xFF410002),
outline = Color(0xFF757780), outline = Color(0xFF757780),
outlineVariant = Color(0xFFC5C6D0), outlineVariant = Color(0xFFC5C6D0),
surfaceContainerLowest = Color(0xFFF5F1F8),
surfaceContainerLow = Color(0xFFF7F2FA),
surfaceContainer = Color(0xFFF3EDF7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFCF7FF),
surfaceContainerHighest = Color(0xFFFCF7FF),
) )
} }
@@ -23,24 +23,29 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFF3B375), primaryContainer = Color(0xFFF3B375),
onPrimaryContainer = Color(0xFF38294E), onPrimaryContainer = Color(0xFF38294E),
inversePrimary = Color(0xFF84531E), inversePrimary = Color(0xFF84531E),
secondary = Color(0xFFF3B375), secondary = Color(0xFFF3B375), // Unread badge
onSecondary = Color(0xFF38294E), onSecondary = Color(0xFF38294E), // Unread badge text
secondaryContainer = Color(0xFFF3B375), secondaryContainer = Color(0xFF5C4D4B), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF38294E), onSecondaryContainer = Color(0xFFF3B375), // Navigation bar selector icon
tertiary = Color(0xFF66577E), tertiary = Color(0xFF66577E), // Downloaded badge
onTertiary = Color(0xFFF3B375), onTertiary = Color(0xFFF3B375), // Downloaded badge text
tertiaryContainer = Color(0xFF4E4065), tertiaryContainer = Color(0xFF4E4065),
onTertiaryContainer = Color(0xFFEDDCFF), onTertiaryContainer = Color(0xFFEDDCFF),
background = Color(0xFF21212E), background = Color(0xFF21212E),
onBackground = Color(0xFFE3E0F2), onBackground = Color(0xFFE3E0F2),
surface = Color(0xFF21212E), surface = Color(0xFF21212E),
onSurface = Color(0xFFE3E0F2), onSurface = Color(0xFFE3E0F2),
surfaceVariant = Color(0xFF49454E), surfaceVariant = Color(0xFF2A2A3C), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFCBC4CE), onSurfaceVariant = Color(0xFFCBC4CE),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFFE5E1E6), inverseSurface = Color(0xFFE5E1E6),
inverseOnSurface = Color(0xFF1B1B1E), inverseOnSurface = Color(0xFF1B1B1E),
outline = Color(0xFF958F99), outline = Color(0xFF958F99),
surfaceContainerLowest = Color(0xFF20202E),
surfaceContainerLow = Color(0xFF262636),
surfaceContainer = Color(0xFF2A2A3C), // Navigation bar background
surfaceContainerHigh = Color(0xFF303044),
surfaceContainerHighest = Color(0xFF36364D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object TakoColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF66577E), primaryContainer = Color(0xFF66577E),
onPrimaryContainer = Color(0xFFF3B375), onPrimaryContainer = Color(0xFFF3B375),
inversePrimary = Color(0xFFD6BAFF), inversePrimary = Color(0xFFD6BAFF),
secondary = Color(0xFF66577E), secondary = Color(0xFF66577E), // Unread badge
onSecondary = Color(0xFFF3B375), onSecondary = Color(0xFFF3B375), // Unread badge text
secondaryContainer = Color(0xFF66577E), secondaryContainer = Color(0xFFC8BED0), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFF3B375), onSecondaryContainer = Color(0xFF66577E), // Navigation bar selector icon
tertiary = Color(0xFFF3B375), tertiary = Color(0xFFF3B375), // Downloaded badge
onTertiary = Color(0xFF574360), onTertiary = Color(0xFF574360), // Downloaded badge text
tertiaryContainer = Color(0xFFFDD6B0), tertiaryContainer = Color(0xFFFDD6B0),
onTertiaryContainer = Color(0xFF221437), onTertiaryContainer = Color(0xFF221437),
background = Color(0xFFF7F5FF), background = Color(0xFFF7F5FF),
onBackground = Color(0xFF1B1B22), onBackground = Color(0xFF1B1B22),
surface = Color(0xFFF7F5FF), surface = Color(0xFFF7F5FF),
onSurface = Color(0xFF1B1B22), onSurface = Color(0xFF1B1B22),
surfaceVariant = Color(0xFFE8E0EB), surfaceVariant = Color(0xFFE8E0EB), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF49454E), onSurfaceVariant = Color(0xFF49454E),
surfaceTint = Color(0xFF66577E), surfaceTint = Color(0xFF66577E),
inverseSurface = Color(0xFF313033), inverseSurface = Color(0xFF313033),
inverseOnSurface = Color(0xFFF3EFF4), inverseOnSurface = Color(0xFFF3EFF4),
outline = Color(0xFF7A757E), outline = Color(0xFF7A757E),
surfaceContainerLowest = Color(0xFFD7D0DA),
surfaceContainerLow = Color(0xFFDFD8E2),
surfaceContainer = Color(0xFFE8E0EB), // Navigation bar background
surfaceContainerHigh = Color(0xFFEEE6F1),
surfaceContainerHighest = Color(0xFFF7EEFA),
) )
} }
@@ -15,24 +15,29 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF40E0D0), primaryContainer = Color(0xFF40E0D0),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFF008080), inversePrimary = Color(0xFF008080),
secondary = Color(0xFF40E0D0), secondary = Color(0xFF40E0D0), // Unread badge
onSecondary = Color(0xFF000000), onSecondary = Color(0xFF000000), // Unread badge text
secondaryContainer = Color(0xFF18544E), secondaryContainer = Color(0xFF18544E), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF40E0D0), onSecondaryContainer = Color(0xFF40E0D0), // Navigation bar selector icon
tertiary = Color(0xFFBF1F2F), tertiary = Color(0xFFBF1F2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF200508), tertiaryContainer = Color(0xFF200508),
onTertiaryContainer = Color(0xFFBF1F2F), onTertiaryContainer = Color(0xFFBF1F2F),
background = Color(0xFF202125), background = Color(0xFF202125),
onBackground = Color(0xFFDFDEDA), onBackground = Color(0xFFDFDEDA),
surface = Color(0xFF202125), surface = Color(0xFF202125),
onSurface = Color(0xFFDFDEDA), onSurface = Color(0xFFDFDEDA),
surfaceVariant = Color(0xFF3F4947), surfaceVariant = Color(0xFF233133), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFDFDEDA), onSurfaceVariant = Color(0xFFDFDEDA),
surfaceTint = Color(0xFF40E0D0), surfaceTint = Color(0xFF40E0D0),
inverseSurface = Color(0xFFDFDEDA), inverseSurface = Color(0xFFDFDEDA),
inverseOnSurface = Color(0xFF202125), inverseOnSurface = Color(0xFF202125),
outline = Color(0xFF899391), outline = Color(0xFF899391),
surfaceContainerLowest = Color(0xFF202C2E),
surfaceContainerLow = Color(0xFF222F31),
surfaceContainer = Color(0xFF233133), // Navigation bar background
surfaceContainerHigh = Color(0xFF28383A),
surfaceContainerHighest = Color(0xFF2F4244),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -41,23 +46,28 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF008080), primaryContainer = Color(0xFF008080),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFF40E0D0), inversePrimary = Color(0xFF40E0D0),
secondary = Color(0xFF008080), secondary = Color(0xFF008080), // Unread badge text
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFBFDFDF), secondaryContainer = Color(0xFFCFE5E4), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF008080), onSecondaryContainer = Color(0xFF008080), // Navigation bar selector icon
tertiary = Color(0xFFFF7F7F), tertiary = Color(0xFFFF7F7F), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFF2A1616), tertiaryContainer = Color(0xFF2A1616),
onTertiaryContainer = Color(0xFFFF7F7F), onTertiaryContainer = Color(0xFFFF7F7F),
background = Color(0xFFFAFAFA), background = Color(0xFFFAFAFA),
onBackground = Color(0xFF050505), onBackground = Color(0xFF050505),
surface = Color(0xFFFAFAFA), surface = Color(0xFFFAFAFA),
onSurface = Color(0xFF050505), onSurface = Color(0xFF050505),
surfaceVariant = Color(0xFFDAE5E2), surfaceVariant = Color(0xFFEBF3F1), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF050505), onSurfaceVariant = Color(0xFF050505),
surfaceTint = Color(0xFFBFDFDF), surfaceTint = Color(0xFFBFDFDF),
inverseSurface = Color(0xFF050505), inverseSurface = Color(0xFF050505),
inverseOnSurface = Color(0xFFFAFAFA), inverseOnSurface = Color(0xFFFAFAFA),
outline = Color(0xFF6F7977), outline = Color(0xFF6F7977),
surfaceContainerLowest = Color(0xFFE1E9E7),
surfaceContainerLow = Color(0xFFE6EEEC),
surfaceContainer = Color(0xFFEBF3F1), // Navigation bar background
surfaceContainerHigh = Color(0xFFF0F8F6),
surfaceContainerHighest = Color(0xFFF7FFFD),
) )
} }
@@ -22,24 +22,29 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF004d61), primaryContainer = Color(0xFF004d61),
onPrimaryContainer = Color(0xFFb8eaff), onPrimaryContainer = Color(0xFFb8eaff),
inversePrimary = Color(0xFFa12b03), inversePrimary = Color(0xFFa12b03),
secondary = Color(0xFF5ed4fc), secondary = Color(0xFF5ed4fc), // Unread badge
onSecondary = Color(0xFF003544), onSecondary = Color(0xFF003544), // Unread badge text
secondaryContainer = Color(0xFF004d61), secondaryContainer = Color(0xFF004d61), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFb8eaff), onSecondaryContainer = Color(0xFFb8eaff), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFF001c3b), background = Color(0xFF001c3b),
onBackground = Color(0xFFd5e3ff), onBackground = Color(0xFFd5e3ff),
surface = Color(0xFF001c3b), surface = Color(0xFF001c3b),
onSurface = Color(0xFFd5e3ff), onSurface = Color(0xFFd5e3ff),
surfaceVariant = Color(0xFF40484c), surfaceVariant = Color(0xFF082b4b), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFbfc8cc), onSurfaceVariant = Color(0xFFbfc8cc),
surfaceTint = Color(0xFF5ed4fc), surfaceTint = Color(0xFF5ed4fc),
inverseSurface = Color(0xFFffe3c4), inverseSurface = Color(0xFFffe3c4),
inverseOnSurface = Color(0xFF001c3b), inverseOnSurface = Color(0xFF001c3b),
outline = Color(0xFF8a9296), outline = Color(0xFF8a9296),
surfaceContainerLowest = Color(0xFF072642),
surfaceContainerLow = Color(0xFF072947),
surfaceContainer = Color(0xFF082b4b), // Navigation bar background
surfaceContainerHigh = Color(0xFF093257),
surfaceContainerHighest = Color(0xFF0A3861),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -48,23 +53,28 @@ internal object TidalWaveColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFB4D4DF), primaryContainer = Color(0xFFB4D4DF),
onPrimaryContainer = Color(0xFF001f28), onPrimaryContainer = Color(0xFF001f28),
inversePrimary = Color(0xFFff987f), inversePrimary = Color(0xFFff987f),
secondary = Color(0xFF006780), secondary = Color(0xFF006780), // Unread badge
onSecondary = Color(0xFFffffff), onSecondary = Color(0xFFffffff), // Unread badge text
secondaryContainer = Color(0xFFb8eaff), secondaryContainer = Color(0xFF9AE1FF), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF001f28), onSecondaryContainer = Color(0xFF001f28), // Navigation bar selector icon
tertiary = Color(0xFF92f7bc), tertiary = Color(0xFF92f7bc), // Downloaded badge
onTertiary = Color(0xFF001c3b), onTertiary = Color(0xFF001c3b), // Downloaded badge text
tertiaryContainer = Color(0xFFc3fada), tertiaryContainer = Color(0xFFc3fada),
onTertiaryContainer = Color(0xFF78ffd6), onTertiaryContainer = Color(0xFF78ffd6),
background = Color(0xFFfdfbff), background = Color(0xFFfdfbff),
onBackground = Color(0xFF001c3b), onBackground = Color(0xFF001c3b),
surface = Color(0xFFfdfbff), surface = Color(0xFFfdfbff),
onSurface = Color(0xFF001c3b), onSurface = Color(0xFF001c3b),
surfaceVariant = Color(0xFFdce4e8), surfaceVariant = Color(0xFFe8eff5), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF40484c), onSurfaceVariant = Color(0xFF40484c),
surfaceTint = Color(0xFF006780), surfaceTint = Color(0xFF006780),
inverseSurface = Color(0xFF020400), inverseSurface = Color(0xFF020400),
inverseOnSurface = Color(0xFFffe3c4), inverseOnSurface = Color(0xFFffe3c4),
outline = Color(0xFF70787c), outline = Color(0xFF70787c),
surfaceContainerLowest = Color(0xFFe2e8ec),
surfaceContainerLow = Color(0xFFe5ecf1),
surfaceContainer = Color(0xFFe8eff5), // Navigation bar background
surfaceContainerHigh = Color(0xFFedf4fA),
surfaceContainerHighest = Color(0xFFf5faff),
) )
} }
@@ -17,24 +17,29 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFFFFF), primaryContainer = Color(0xFFFFFFFF),
onPrimaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFF000000),
inversePrimary = Color(0xFFCECECE), inversePrimary = Color(0xFFCECECE),
secondary = Color(0xFFFFFFFF), secondary = Color(0xFFFFFFFF), // Unread badge
onSecondary = Color(0xFF5A5A5A), onSecondary = Color(0xFF5A5A5A), // Unread badge text
secondaryContainer = Color(0xFF717171), secondaryContainer = Color(0xFF717171), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFE4E4E4), onSecondaryContainer = Color(0xFFE4E4E4), // Navigation bar selector icon
tertiary = Color(0xFF000000), tertiary = Color(0xFF000000), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFF00419E), tertiaryContainer = Color(0xFF00419E),
onTertiaryContainer = Color(0xFFD8E2FF), onTertiaryContainer = Color(0xFFD8E2FF),
background = Color(0xFF1E1E1E), background = Color(0xFF1E1E1E),
onBackground = Color(0xFFE6E6E6), onBackground = Color(0xFFE6E6E6),
surface = Color(0xFF1E1E1E), surface = Color(0xFF1E1E1E),
onSurface = Color(0xFFE6E6E6), onSurface = Color(0xFFE6E6E6),
surfaceVariant = Color(0xFF4E4E4E), surfaceVariant = Color(0xFF313131), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD1D1D1), onSurfaceVariant = Color(0xFFD1D1D1),
surfaceTint = Color(0xFFFFFFFF), surfaceTint = Color(0xFFFFFFFF),
inverseSurface = Color(0xFFE6E6E6), inverseSurface = Color(0xFFE6E6E6),
inverseOnSurface = Color(0xFF1E1E1E), inverseOnSurface = Color(0xFF1E1E1E),
outline = Color(0xFF999999), outline = Color(0xFF999999),
surfaceContainerLowest = Color(0xFF2A2A2A),
surfaceContainerLow = Color(0xFF2D2D2D),
surfaceContainer = Color(0xFF313131), // Navigation bar background
surfaceContainerHigh = Color(0xFF383838),
surfaceContainerHighest = Color(0xFF3F3F3F),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -43,23 +48,28 @@ internal object YinYangColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF000000), primaryContainer = Color(0xFF000000),
onPrimaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFFFFFFFF),
inversePrimary = Color(0xFFA6A6A6), inversePrimary = Color(0xFFA6A6A6),
secondary = Color(0xFF000000), secondary = Color(0xFF000000), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFDDDDDD), secondaryContainer = Color(0xFFDDDDDD), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF0C0C0C), onSecondaryContainer = Color(0xFF0C0C0C), // Navigation bar selector icon
tertiary = Color(0xFFFFFFFF), tertiary = Color(0xFFFFFFFF), // Downloaded badge
onTertiary = Color(0xFF000000), onTertiary = Color(0xFF000000), // Downloaded badge text
tertiaryContainer = Color(0xFFD8E2FF), tertiaryContainer = Color(0xFFD8E2FF),
onTertiaryContainer = Color(0xFF001947), onTertiaryContainer = Color(0xFF001947),
background = Color(0xFFFDFDFD), background = Color(0xFFFDFDFD),
onBackground = Color(0xFF222222), onBackground = Color(0xFF222222),
surface = Color(0xFFFDFDFD), surface = Color(0xFFFDFDFD),
onSurface = Color(0xFF222222), onSurface = Color(0xFF222222),
surfaceVariant = Color(0xFFEDEDED), surfaceVariant = Color(0xFFE8E8E8), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF515151), onSurfaceVariant = Color(0xFF515151),
surfaceTint = Color(0xFF000000), surfaceTint = Color(0xFF000000),
inverseSurface = Color(0xFF333333), inverseSurface = Color(0xFF333333),
inverseOnSurface = Color(0xFFF4F4F4), inverseOnSurface = Color(0xFFF4F4F4),
outline = Color(0xFF838383), outline = Color(0xFF838383),
surfaceContainerLowest = Color(0xFFCFCFCF),
surfaceContainerLow = Color(0xFFDADADA),
surfaceContainer = Color(0xFFE8E8E8), // Navigation bar background
surfaceContainerHigh = Color(0xFFECECEC),
surfaceContainerHighest = Color(0xFFEFEFEF),
) )
} }
@@ -23,24 +23,29 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFF862200), primaryContainer = Color(0xFF862200),
onPrimaryContainer = Color(0xFFFFDBCF), onPrimaryContainer = Color(0xFFFFDBCF),
inversePrimary = Color(0xFFAE3200), inversePrimary = Color(0xFFAE3200),
secondary = Color(0xFFFFB59D), secondary = Color(0xFFFFB59D), // Unread badge
onSecondary = Color(0xFF5F1600), onSecondary = Color(0xFF5F1600), // Unread badge text
secondaryContainer = Color(0xFF862200), secondaryContainer = Color(0xFF862200), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFFFDBCF), onSecondaryContainer = Color(0xFFFFDBCF), // Navigation bar selector icon
tertiary = Color(0xFFD7C68D), tertiary = Color(0xFFD7C68D), // Downloaded badge
onTertiary = Color(0xFF3A2F05), onTertiary = Color(0xFF3A2F05), // Downloaded badge text
tertiaryContainer = Color(0xFF524619), tertiaryContainer = Color(0xFF524619),
onTertiaryContainer = Color(0xFFF5E2A7), onTertiaryContainer = Color(0xFFF5E2A7),
background = Color(0xFF211A18), background = Color(0xFF211A18),
onBackground = Color(0xFFEDE0DD), onBackground = Color(0xFFEDE0DD),
surface = Color(0xFF211A18), surface = Color(0xFF211A18),
onSurface = Color(0xFFEDE0DD), onSurface = Color(0xFFEDE0DD),
surfaceVariant = Color(0xFF53433F), surfaceVariant = Color(0xFF332723), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFD8C2BC), onSurfaceVariant = Color(0xFFD8C2BC),
surfaceTint = Color(0xFFFFB59D), surfaceTint = Color(0xFFFFB59D),
inverseSurface = Color(0xFFEDE0DD), inverseSurface = Color(0xFFEDE0DD),
inverseOnSurface = Color(0xFF211A18), inverseOnSurface = Color(0xFF211A18),
outline = Color(0xFFA08C87), outline = Color(0xFFA08C87),
surfaceContainerLowest = Color(0xFF2E221F),
surfaceContainerLow = Color(0xFF312521),
surfaceContainer = Color(0xFF332723), // Navigation bar background
surfaceContainerHigh = Color(0xFF413531),
surfaceContainerHighest = Color(0xFF4C403D),
) )
override val lightScheme = lightColorScheme( override val lightScheme = lightColorScheme(
@@ -49,23 +54,28 @@ internal object YotsubaColorScheme : BaseColorScheme() {
primaryContainer = Color(0xFFFFDBCF), primaryContainer = Color(0xFFFFDBCF),
onPrimaryContainer = Color(0xFF3B0A00), onPrimaryContainer = Color(0xFF3B0A00),
inversePrimary = Color(0xFFFFB59D), inversePrimary = Color(0xFFFFB59D),
secondary = Color(0xFFAE3200), secondary = Color(0xFFAE3200), // Unread badge
onSecondary = Color(0xFFFFFFFF), onSecondary = Color(0xFFFFFFFF), // Unread badge text
secondaryContainer = Color(0xFFFFDBCF), secondaryContainer = Color(0xFFEBCDC2), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF3B0A00), onSecondaryContainer = Color(0xFF3B0A00), // Navigation bar selector icon
tertiary = Color(0xFF6B5E2F), tertiary = Color(0xFF6B5E2F), // Downloaded badge
onTertiary = Color(0xFFFFFFFF), onTertiary = Color(0xFFFFFFFF), // Downloaded badge text
tertiaryContainer = Color(0xFFF5E2A7), tertiaryContainer = Color(0xFFF5E2A7),
onTertiaryContainer = Color(0xFF231B00), onTertiaryContainer = Color(0xFF231B00),
background = Color(0xFFFCFCFC), background = Color(0xFFFCFCFC),
onBackground = Color(0xFF211A18), onBackground = Color(0xFF211A18),
surface = Color(0xFFFCFCFC), surface = Color(0xFFFCFCFC),
onSurface = Color(0xFF211A18), onSurface = Color(0xFF211A18),
surfaceVariant = Color(0xFFF5DED8), surfaceVariant = Color(0xFFF6EBE7), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF53433F), onSurfaceVariant = Color(0xFF53433F),
surfaceTint = Color(0xFFAE3200), surfaceTint = Color(0xFFAE3200),
inverseSurface = Color(0xFF362F2D), inverseSurface = Color(0xFF362F2D),
inverseOnSurface = Color(0xFFFBEEEB), inverseOnSurface = Color(0xFFFBEEEB),
outline = Color(0xFF85736E), outline = Color(0xFF85736E),
surfaceContainerLowest = Color(0xFFECE3E0),
surfaceContainerLow = Color(0xFFF1E7E4),
surfaceContainer = Color(0xFFF6EBE7), // Navigation bar background
surfaceContainerHigh = Color(0xFFFAF4F2),
surfaceContainerHighest = Color(0xFFFBF6F4),
) )
} }
@@ -72,6 +72,7 @@ fun TrackInfoDialogHome(
onNewSearch: (TrackItem) -> Unit, onNewSearch: (TrackItem) -> Unit,
onOpenInBrowser: (TrackItem) -> Unit, onOpenInBrowser: (TrackItem) -> Unit,
onRemoved: (TrackItem) -> Unit, onRemoved: (TrackItem) -> Unit,
onCopyLink: (TrackItem) -> Unit,
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier
@@ -116,6 +117,7 @@ fun TrackInfoDialogHome(
onNewSearch = { onNewSearch(item) }, onNewSearch = { onNewSearch(item) },
onOpenInBrowser = { onOpenInBrowser(item) }, onOpenInBrowser = { onOpenInBrowser(item) },
onRemoved = { onRemoved(item) }, onRemoved = { onRemoved(item) },
onCopyLink = { onCopyLink(item) },
) )
} else { } else {
TrackInfoItemEmpty( TrackInfoItemEmpty(
@@ -144,6 +146,7 @@ private fun TrackInfoItem(
onNewSearch: () -> Unit, onNewSearch: () -> Unit,
onOpenInBrowser: () -> Unit, onOpenInBrowser: () -> Unit,
onRemoved: () -> Unit, onRemoved: () -> Unit,
onCopyLink: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column { Column {
@@ -153,6 +156,7 @@ private fun TrackInfoItem(
TrackLogoIcon( TrackLogoIcon(
tracker = tracker, tracker = tracker,
onClick = onOpenInBrowser, onClick = onOpenInBrowser,
onLongClick = onCopyLink,
) )
Box( Box(
modifier = Modifier modifier = Modifier
@@ -179,6 +183,7 @@ private fun TrackInfoItem(
TrackInfoItemMenu( TrackInfoItemMenu(
onOpenInBrowser = onOpenInBrowser, onOpenInBrowser = onOpenInBrowser,
onRemoved = onRemoved, onRemoved = onRemoved,
onCopyLink = onCopyLink,
) )
} }
@@ -186,7 +191,7 @@ private fun TrackInfoItem(
modifier = Modifier modifier = Modifier
.padding(top = 12.dp) .padding(top = 12.dp)
.clip(MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium)
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surfaceContainerHighest)
.padding(8.dp) .padding(8.dp)
.clip(RoundedCornerShape(6.dp)), .clip(RoundedCornerShape(6.dp)),
) { ) {
@@ -287,6 +292,7 @@ private fun TrackInfoItemEmpty(
private fun TrackInfoItemMenu( private fun TrackInfoItemMenu(
onOpenInBrowser: () -> Unit, onOpenInBrowser: () -> Unit,
onRemoved: () -> Unit, onRemoved: () -> Unit,
onCopyLink: () -> Unit,
) { ) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) { Box(modifier = Modifier.wrapContentSize(Alignment.TopStart)) {
@@ -307,6 +313,13 @@ private fun TrackInfoItemMenu(
expanded = false expanded = false
}, },
) )
DropdownMenuItem(
text = { Text(stringResource(MR.strings.action_copy_link)) },
onClick = {
onCopyLink()
expanded = false
},
)
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(MR.strings.action_remove)) }, text = { Text(stringResource(MR.strings.action_remove)) },
onClick = { onClick = {
@@ -56,6 +56,7 @@ internal class TrackInfoDialogHomePreviewProvider :
onNewSearch = {}, onNewSearch = {},
onOpenInBrowser = {}, onOpenInBrowser = {},
onRemoved = {}, onRemoved = {},
onCopyLink = {},
) )
} }
@@ -71,6 +72,7 @@ internal class TrackInfoDialogHomePreviewProvider :
onNewSearch = {}, onNewSearch = {},
onOpenInBrowser = {}, onOpenInBrowser = {},
onRemoved = {}, onRemoved = {},
onCopyLink = {},
) )
} }
@@ -22,9 +22,10 @@ import tachiyomi.presentation.core.util.clickableNoIndication
fun TrackLogoIcon( fun TrackLogoIcon(
tracker: Tracker, tracker: Tracker,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
onLongClick: (() -> Unit)? = null,
) { ) {
val modifier = if (onClick != null) { val modifier = if (onClick != null) {
Modifier.clickableNoIndication(onClick = onClick) Modifier.clickableNoIndication(onClick = onClick, onLongClick = onLongClick)
} else { } else {
Modifier Modifier
} }
@@ -53,6 +54,7 @@ private fun TrackLogoIconPreviews(
TrackLogoIcon( TrackLogoIcon(
tracker = tracker, tracker = tracker,
onClick = null, onClick = null,
onLongClick = null,
) )
} }
} }
+17 -8
View File
@@ -199,30 +199,39 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
) )
} }
@Suppress("MagicNumber")
override fun newImageLoader(context: Context): ImageLoader { override fun newImageLoader(context: Context): ImageLoader {
return ImageLoader.Builder(this).apply { return ImageLoader.Builder(this).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client } val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
components { components {
// NetworkFetcher.Factory
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value)) add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
// Decoder.Factory
add(TachiyomiImageDecoder.Factory()) add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.MangaFactory(callFactoryLazy)) // Fetcher.Factory
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaKeyer())
add(MangaCoverKeyer())
add(BufferedSourceFetcher.Factory()) add(BufferedSourceFetcher.Factory())
add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy))
add(MangaCoverFetcher.MangaFactory(callFactoryLazy))
// SY --> // SY -->
add(PagePreviewKeyer())
add(PagePreviewFetcher.Factory(callFactoryLazy)) add(PagePreviewFetcher.Factory(callFactoryLazy))
// SY <-- // SY <--
// Keyer
add(MangaCoverKeyer())
add(MangaKeyer())
// SY -->
add(PagePreviewKeyer())
// SY <--
} }
crossfade((300 * this@App.animatorDurationScale).toInt()) crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(DeviceUtil.isLowRamDevice(this@App)) allowRgb565(DeviceUtil.isLowRamDevice(this@App))
if (networkPreferences.verboseLogging().get()) logger(DebugLogger()) if (networkPreferences.verboseLogging().get()) logger(DebugLogger())
// Coil spawns a new thread for every image load by default // Coil spawns a new thread for every image load by default
fetcherDispatcher(Dispatchers.IO.limitedParallelism(8)) fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8))
decoderDispatcher(Dispatchers.IO.limitedParallelism(2)) decoderCoroutineContext(Dispatchers.IO.limitedParallelism(3))
}.build() }
.build()
} }
override fun onStart(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {
@@ -3,19 +3,21 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import kotlinx.serialization.SerializationException
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.source import okio.source
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.IOException
class BackupDecoder( class BackupDecoder(
private val context: Context, private val context: Context,
private val parser: ProtoBuf = Injekt.get(), private val parser: ProtoBuf = Injekt.get(),
) { ) {
/** /**
* Decode a potentially-gzipped backup. * Decode a potentially-gzipped backup.
*/ */
@@ -27,13 +29,25 @@ class BackupDecoder(
require(2) require(2)
} }
val id1id2 = peeked.readShort() val id1id2 = peeked.readShort()
val backupString = if (id1id2.toInt() == 0x1f8b) { // 0x1f8b is gzip magic bytes val backupString = when (id1id2.toInt()) {
source.gzip().buffer() 0x1f8b -> source.gzip().buffer() // 0x1f8b is gzip magic bytes
} else { MAGIC_JSON_SIGNATURE1, MAGIC_JSON_SIGNATURE2, MAGIC_JSON_SIGNATURE3 -> {
source throw IOException(context.stringResource(MR.strings.invalid_backup_file_json))
}
else -> source
}.use { it.readByteArray() } }.use { it.readByteArray() }
parser.decodeFromByteArray(BackupSerializer, backupString) try {
parser.decodeFromByteArray(Backup.serializer(), backupString)
} catch (_: SerializationException) {
throw IOException(context.stringResource(MR.strings.invalid_backup_file_unknown))
}
} }
} }
companion object {
private const val MAGIC_JSON_SIGNATURE1 = 0x7b7d // `{}`
private const val MAGIC_JSON_SIGNATURE2 = 0x7b22 // `{"`
private const val MAGIC_JSON_SIGNATURE3 = 0x7b0a // `{\n`
}
} }
@@ -6,16 +6,17 @@ import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.backup.BackupFileValidator import eu.kanade.tachiyomi.data.backup.BackupFileValidator
import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.ExtensionRepoBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.SavedSearchBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.SavedSearchBackupCreator
import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
@@ -51,6 +52,7 @@ class BackupCreator(
private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(), private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(),
private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(), private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(),
private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(), private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(),
private val extensionRepoBackupCreator: ExtensionRepoBackupCreator = ExtensionRepoBackupCreator(),
private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(), private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(),
// SY --> // SY -->
private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(), private val savedSearchBackupCreator: SavedSearchBackupCreator = SavedSearchBackupCreator(),
@@ -62,47 +64,49 @@ class BackupCreator(
suspend fun backup(uri: Uri, options: BackupOptions): String { suspend fun backup(uri: Uri, options: BackupOptions): String {
var file: UniFile? = null var file: UniFile? = null
try { try {
file = ( file = if (isAutoBackup) {
if (isAutoBackup) { // Get dir of file and create
// Get dir of file and create val dir = UniFile.fromUri(context, uri)
val dir = UniFile.fromUri(context, uri)
// Delete older backups // Delete older backups
dir?.listFiles { _, filename -> FILENAME_REGEX.matches(filename) } dir?.listFiles { _, filename -> FILENAME_REGEX.matches(filename) }
.orEmpty() .orEmpty()
.sortedByDescending { it.name } .sortedByDescending { it.name }
.drop(MAX_AUTO_BACKUPS - 1) .drop(MAX_AUTO_BACKUPS - 1)
.forEach { it.delete() } .forEach { it.delete() }
// Create new file to place backup // Create new file to place backup
dir?.createFile(getFilename()) dir?.createFile(getFilename())
} else { } else {
UniFile.fromUri(context, uri) UniFile.fromUri(context, uri)
} }
)
if (file == null || !file.isFile) { if (file == null || !file.isFile) {
throw IllegalStateException(context.stringResource(MR.strings.create_backup_file_error)) throw IllegalStateException(context.stringResource(MR.strings.create_backup_file_error))
} }
val databaseManga = getFavorites.await() /* SY --> */ + val backupManga = backupMangas(
if (options.readEntries) { getFavorites.await() /* SY --> */ +
handler.awaitList { mangasQueries.getReadMangaNotInLibrary(MangaMapper::mapManga) } if (options.readEntries) {
} else { handler.awaitList { mangasQueries.getReadMangaNotInLibrary(MangaMapper::mapManga) }
emptyList() } else {
} + getMergedManga.await() // SY <-- emptyList()
} + getMergedManga.await(), // SY <--
options
)
val backup = Backup( val backup = Backup(
backupManga = backupMangas(databaseManga, options), backupManga = backupManga,
backupCategories = backupCategories(options), backupCategories = backupCategories(options),
backupSources = backupSources(databaseManga), backupSources = backupSources(backupManga),
backupPreferences = backupAppPreferences(options), backupPreferences = backupAppPreferences(options),
backupExtensionRepo = backupExtensionRepos(options),
backupSourcePreferences = backupSourcePreferences(options), backupSourcePreferences = backupSourcePreferences(options),
// SY --> // SY -->
backupSavedSearches = backupSavedSearches(), backupSavedSearches = backupSavedSearches(options),
// SY <-- // SY <--
) )
val byteArray = parser.encodeToByteArray(BackupSerializer, backup) val byteArray = parser.encodeToByteArray(Backup.serializer(), backup)
if (byteArray.isEmpty()) { if (byteArray.isEmpty()) {
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error)) throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
} }
@@ -135,32 +139,42 @@ class BackupCreator(
suspend fun backupCategories(options: BackupOptions): List<BackupCategory> { suspend fun backupCategories(options: BackupOptions): List<BackupCategory> {
if (!options.categories) return emptyList() if (!options.categories) return emptyList()
return categoriesBackupCreator.backupCategories() return categoriesBackupCreator()
} }
suspend fun backupMangas(mangas: List<Manga>, options: BackupOptions): List<BackupManga> { suspend fun backupMangas(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
return mangaBackupCreator.backupMangas(mangas, options) if (!options.libraryEntries) return emptyList()
return mangaBackupCreator(mangas, options)
} }
fun backupSources(mangas: List<Manga>): List<BackupSource> { fun backupSources(mangas: List<BackupManga>): List<BackupSource> {
return sourcesBackupCreator.backupSources(mangas) return sourcesBackupCreator(mangas)
} }
fun backupAppPreferences(options: BackupOptions): List<BackupPreference> { fun backupAppPreferences(options: BackupOptions): List<BackupPreference> {
if (!options.appSettings) return emptyList() if (!options.appSettings) return emptyList()
return preferenceBackupCreator.backupAppPreferences(includePrivatePreferences = options.privateSettings) return preferenceBackupCreator.createApp(includePrivatePreferences = options.privateSettings)
} }
fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> { fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
if (!options.sourceSettings) return emptyList() if (!options.sourceSettings) return emptyList()
return preferenceBackupCreator.backupSourcePreferences(includePrivatePreferences = options.privateSettings) return preferenceBackupCreator.createSource(includePrivatePreferences = options.privateSettings)
}
suspend fun backupExtensionRepos(options: BackupOptions): List<BackupExtensionRepos> {
if (!options.extensionRepoSettings) return emptyList()
return extensionRepoBackupCreator()
} }
// SY --> // SY -->
suspend fun backupSavedSearches(): List<BackupSavedSearch> { suspend fun backupSavedSearches(options: BackupOptions): List<BackupSavedSearch> {
return savedSearchBackupCreator.backupSavedSearches() if (!options.savedSearches) return emptyList()
return savedSearchBackupCreator()
} }
// SY <-- // SY <--
@@ -12,11 +12,13 @@ data class BackupOptions(
val tracking: Boolean = true, val tracking: Boolean = true,
val history: Boolean = true, val history: Boolean = true,
val appSettings: Boolean = true, val appSettings: Boolean = true,
val extensionRepoSettings: Boolean = true,
val sourceSettings: Boolean = true, val sourceSettings: Boolean = true,
val privateSettings: Boolean = false, val privateSettings: Boolean = false,
// SY --> // SY -->
val customInfo: Boolean = true, val customInfo: Boolean = true,
val readEntries: Boolean = true, val readEntries: Boolean = true,
val savedSearches: Boolean = true,
// SY <-- // SY <--
) { ) {
@@ -27,15 +29,17 @@ data class BackupOptions(
tracking, tracking,
history, history,
appSettings, appSettings,
extensionRepoSettings,
sourceSettings, sourceSettings,
privateSettings, privateSettings,
// SY --> // SY -->
customInfo, customInfo,
readEntries, readEntries,
savedSearches,
// SY <-- // SY <--
) )
fun anyEnabled() = libraryEntries || appSettings || sourceSettings fun canCreate() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings || savedSearches
companion object { companion object {
val libraryOptions = persistentListOf( val libraryOptions = persistentListOf(
@@ -44,12 +48,6 @@ data class BackupOptions(
getter = BackupOptions::libraryEntries, getter = BackupOptions::libraryEntries,
setter = { options, enabled -> options.copy(libraryEntries = enabled) }, setter = { options, enabled -> options.copy(libraryEntries = enabled) },
), ),
Entry(
label = MR.strings.categories,
getter = BackupOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
enabled = { it.libraryEntries },
),
Entry( Entry(
label = MR.strings.chapters, label = MR.strings.chapters,
getter = BackupOptions::chapters, getter = BackupOptions::chapters,
@@ -68,6 +66,11 @@ data class BackupOptions(
setter = { options, enabled -> options.copy(history = enabled) }, setter = { options, enabled -> options.copy(history = enabled) },
enabled = { it.libraryEntries }, enabled = { it.libraryEntries },
), ),
Entry(
label = MR.strings.categories,
getter = BackupOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
),
// SY --> // SY -->
Entry( Entry(
label = SYMR.strings.custom_entry_info, label = SYMR.strings.custom_entry_info,
@@ -81,6 +84,11 @@ data class BackupOptions(
setter = { options, enabled -> options.copy(readEntries = enabled) }, setter = { options, enabled -> options.copy(readEntries = enabled) },
enabled = { it.libraryEntries }, enabled = { it.libraryEntries },
), ),
Entry(
label = SYMR.strings.saved_searches,
getter = BackupOptions::savedSearches,
setter = { options, enabled -> options.copy(savedSearches = enabled) },
),
// SY <-- // SY <--
) )
@@ -90,6 +98,11 @@ data class BackupOptions(
getter = BackupOptions::appSettings, getter = BackupOptions::appSettings,
setter = { options, enabled -> options.copy(appSettings = enabled) }, setter = { options, enabled -> options.copy(appSettings = enabled) },
), ),
Entry(
label = MR.strings.extensionRepo_settings,
getter = BackupOptions::extensionRepoSettings,
setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) },
),
Entry( Entry(
label = MR.strings.source_settings, label = MR.strings.source_settings,
getter = BackupOptions::sourceSettings, getter = BackupOptions::sourceSettings,
@@ -110,11 +123,13 @@ data class BackupOptions(
tracking = array[3], tracking = array[3],
history = array[4], history = array[4],
appSettings = array[5], appSettings = array[5],
sourceSettings = array[6], extensionRepoSettings = array[6],
privateSettings = array[7], sourceSettings = array[7],
privateSettings = array[8],
// SY --> // SY -->
customInfo = array[8], customInfo = array[9],
readEntries = array[9], readEntries = array[10],
savedSearches = array[11],
// SY <-- // SY <--
) )
} }
@@ -11,7 +11,7 @@ class CategoriesBackupCreator(
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
) { ) {
suspend fun backupCategories(): List<BackupCategory> { suspend operator fun invoke(): List<BackupCategory> {
return getCategories.await() return getCategories.await()
.filterNot(Category::isSystemCategory) .filterNot(Category::isSystemCategory)
.map(backupCategoryMapper) .map(backupCategoryMapper)
@@ -0,0 +1,17 @@
package eu.kanade.tachiyomi.data.backup.create.creators
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
import eu.kanade.tachiyomi.data.backup.models.backupExtensionReposMapper
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionRepoBackupCreator(
private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
) {
suspend operator fun invoke(): List<BackupExtensionRepos> {
return getExtensionRepos.getAll()
.map(backupExtensionReposMapper)
}
}
@@ -34,7 +34,7 @@ class MangaBackupCreator(
// SY <-- // SY <--
) { ) {
suspend fun backupMangas(mangas: List<Manga>, options: BackupOptions): List<BackupManga> { suspend operator fun invoke(mangas: List<Manga>, options: BackupOptions): List<BackupManga> {
return mangas.map { return mangas.map {
backupManga(it, options) backupManga(it, options)
} }
@@ -22,12 +22,12 @@ class PreferenceBackupCreator(
private val preferenceStore: PreferenceStore = Injekt.get(), private val preferenceStore: PreferenceStore = Injekt.get(),
) { ) {
fun backupAppPreferences(includePrivatePreferences: Boolean): List<BackupPreference> { fun createApp(includePrivatePreferences: Boolean): List<BackupPreference> {
return preferenceStore.getAll().toBackupPreferences() return preferenceStore.getAll().toBackupPreferences()
.withPrivatePreferences(includePrivatePreferences) .withPrivatePreferences(includePrivatePreferences)
} }
fun backupSourcePreferences(includePrivatePreferences: Boolean): List<BackupSourcePreferences> { fun createSource(includePrivatePreferences: Boolean): List<BackupSourcePreferences> {
return sourceManager.getCatalogueSources() return sourceManager.getCatalogueSources()
.filterIsInstance<ConfigurableSource>() .filterIsInstance<ConfigurableSource>()
.map { .map {
@@ -10,7 +10,7 @@ class SavedSearchBackupCreator(
private val handler: DatabaseHandler = Injekt.get() private val handler: DatabaseHandler = Injekt.get()
) { ) {
suspend fun backupSavedSearches(): List<BackupSavedSearch> { suspend operator fun invoke(): List<BackupSavedSearch> {
return handler.awaitList { saved_searchQueries.selectAll(backupSavedSearchMapper) } return handler.awaitList { saved_searchQueries.selectAll(backupSavedSearchMapper) }
} }
} }
@@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.backup.create.creators package eu.kanade.tachiyomi.data.backup.create.creators
import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -11,10 +11,10 @@ class SourcesBackupCreator(
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
) { ) {
fun backupSources(mangas: List<Manga>): List<BackupSource> { operator fun invoke(mangas: List<BackupManga>): List<BackupSource> {
return mangas return mangas
.asSequence() .asSequence()
.map(Manga::source) .map(BackupManga::source)
.distinct() .distinct()
.map(sourceManager::getOrStub) .map(sourceManager::getOrStub)
.map { it.toBackupSource() } .map { it.toBackupSource() }
@@ -1,12 +1,9 @@
package eu.kanade.tachiyomi.data.backup.models package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@Serializer(forClass = Backup::class) @Suppress("MagicNumber")
object BackupSerializer
@Serializable @Serializable
data class Backup( data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>, @ProtoNumber(1) val backupManga: List<BackupManga>,
@@ -15,6 +12,7 @@ data class Backup(
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(), @ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
@ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(), @ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(),
@ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(), @ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(),
@ProtoNumber(106) var backupExtensionRepo: List<BackupExtensionRepos> = emptyList(),
// SY specific values // SY specific values
@ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList(), @ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList(),
) )
@@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.data.backup.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import mihon.domain.extensionrepo.model.ExtensionRepo
@Suppress("MagicNumber")
@Serializable
class BackupExtensionRepos(
@ProtoNumber(1) var baseUrl: String,
@ProtoNumber(2) var name: String,
@ProtoNumber(3) var shortName: String?,
@ProtoNumber(4) var website: String,
@ProtoNumber(5) var signingKeyFingerprint: String,
)
val backupExtensionReposMapper = { repo: ExtensionRepo ->
BackupExtensionRepos(
baseUrl = repo.baseUrl,
name = repo.name,
shortName = repo.shortName,
website = repo.website,
signingKeyFingerprint = repo.signingKeyFingerprint,
)
}
@@ -5,11 +5,13 @@ import android.net.Uri
import eu.kanade.tachiyomi.data.backup.BackupDecoder import eu.kanade.tachiyomi.data.backup.BackupDecoder
import eu.kanade.tachiyomi.data.backup.BackupNotifier import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupPreference import eu.kanade.tachiyomi.data.backup.models.BackupPreference
import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch import eu.kanade.tachiyomi.data.backup.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.ExtensionRepoRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.SavedSearchRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.SavedSearchRestorer
@@ -34,6 +36,7 @@ class BackupRestorer(
private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(), private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(),
private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context), private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context),
private val extensionRepoRestorer: ExtensionRepoRestorer = ExtensionRepoRestorer(),
private val mangaRestorer: MangaRestorer = MangaRestorer(isSync), private val mangaRestorer: MangaRestorer = MangaRestorer(isSync),
// SY --> // SY -->
private val savedSearchRestorer: SavedSearchRestorer = SavedSearchRestorer(), private val savedSearchRestorer: SavedSearchRestorer = SavedSearchRestorer(),
@@ -74,8 +77,11 @@ class BackupRestorer(
val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() } val backupMaps = backup.backupSources + backup.backupBrokenSources.map { it.toBackupSource() }
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
if (options.library) { if (options.libraryEntries) {
restoreAmount += backup.backupManga.size + 1 // +1 for categories restoreAmount += backup.backupManga.size
}
if (options.categories) {
restoreAmount += 1
} }
// SY --> // SY -->
if (options.savedSearches) { if (options.savedSearches) {
@@ -85,12 +91,15 @@ class BackupRestorer(
if (options.appSettings) { if (options.appSettings) {
restoreAmount += 1 restoreAmount += 1
} }
if (options.extensionRepoSettings) {
restoreAmount += backup.backupExtensionRepo.size
}
if (options.sourceSettings) { if (options.sourceSettings) {
restoreAmount += 1 restoreAmount += 1
} }
coroutineScope { coroutineScope {
if (options.library) { if (options.categories) {
restoreCategories(backup.backupCategories) restoreCategories(backup.backupCategories)
} }
// SY --> // SY -->
@@ -104,8 +113,11 @@ class BackupRestorer(
if (options.sourceSettings) { if (options.sourceSettings) {
restoreSourcePreferences(backup.backupSourcePreferences) restoreSourcePreferences(backup.backupSourcePreferences)
} }
if (options.library) { if (options.libraryEntries) {
restoreManga(backup.backupManga, backup.backupCategories) restoreManga(backup.backupManga, if (options.categories) backup.backupCategories else emptyList())
}
if (options.extensionRepoSettings) {
restoreExtensionRepos(backup.backupExtensionRepo)
} }
// TODO: optionally trigger online library + tracker update // TODO: optionally trigger online library + tracker update
@@ -114,7 +126,7 @@ class BackupRestorer(
private fun CoroutineScope.restoreCategories(backupCategories: List<BackupCategory>) = launch { private fun CoroutineScope.restoreCategories(backupCategories: List<BackupCategory>) = launch {
ensureActive() ensureActive()
categoriesRestorer.restoreCategories(backupCategories) categoriesRestorer(backupCategories)
restoreProgress += 1 restoreProgress += 1
notifier.showRestoreProgress( notifier.showRestoreProgress(
@@ -150,7 +162,7 @@ class BackupRestorer(
ensureActive() ensureActive()
try { try {
mangaRestorer.restoreManga(it, backupCategories) mangaRestorer.restore(it, backupCategories)
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[it.source] ?: it.source.toString() val sourceName = sourceMapping[it.source] ?: it.source.toString()
errors.add(Date() to "${it.title} [$sourceName]: ${e.message}") errors.add(Date() to "${it.title} [$sourceName]: ${e.message}")
@@ -163,7 +175,7 @@ class BackupRestorer(
private fun CoroutineScope.restoreAppPreferences(preferences: List<BackupPreference>) = launch { private fun CoroutineScope.restoreAppPreferences(preferences: List<BackupPreference>) = launch {
ensureActive() ensureActive()
preferenceRestorer.restoreAppPreferences(preferences) preferenceRestorer.restoreApp(preferences)
restoreProgress += 1 restoreProgress += 1
notifier.showRestoreProgress( notifier.showRestoreProgress(
@@ -176,7 +188,7 @@ class BackupRestorer(
private fun CoroutineScope.restoreSourcePreferences(preferences: List<BackupSourcePreferences>) = launch { private fun CoroutineScope.restoreSourcePreferences(preferences: List<BackupSourcePreferences>) = launch {
ensureActive() ensureActive()
preferenceRestorer.restoreSourcePreferences(preferences) preferenceRestorer.restoreSource(preferences)
restoreProgress += 1 restoreProgress += 1
notifier.showRestoreProgress( notifier.showRestoreProgress(
@@ -187,10 +199,33 @@ class BackupRestorer(
) )
} }
private fun CoroutineScope.restoreExtensionRepos(
backupExtensionRepo: List<BackupExtensionRepos>
) = launch {
backupExtensionRepo
.forEach {
ensureActive()
try {
extensionRepoRestorer(it)
} catch (e: Exception) {
errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}")
}
restoreProgress += 1
notifier.showRestoreProgress(
context.stringResource(MR.strings.extensionRepo_settings),
restoreProgress,
restoreAmount,
isSync,
)
}
}
private fun writeErrorLog(): File { private fun writeErrorLog(): File {
try { try {
if (errors.isNotEmpty()) { if (errors.isNotEmpty()) {
val file = context.createFileInCacheDir("tachiyomi_restore.txt") val file = context.createFileInCacheDir("mihon_restore_error.txt")
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
file.bufferedWriter().use { out -> file.bufferedWriter().use { out ->
@@ -6,8 +6,10 @@ import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
data class RestoreOptions( data class RestoreOptions(
val library: Boolean = true, val libraryEntries: Boolean = true,
val categories: Boolean = true,
val appSettings: Boolean = true, val appSettings: Boolean = true,
val extensionRepoSettings: Boolean = true,
val sourceSettings: Boolean = true, val sourceSettings: Boolean = true,
// SY --> // SY -->
val savedSearches: Boolean = true, val savedSearches: Boolean = true,
@@ -15,28 +17,40 @@ data class RestoreOptions(
) { ) {
fun asBooleanArray() = booleanArrayOf( fun asBooleanArray() = booleanArrayOf(
library, libraryEntries,
categories,
appSettings, appSettings,
extensionRepoSettings,
sourceSettings, sourceSettings,
// SY --> // SY -->
savedSearches savedSearches,
// SY <-- // SY <--
) )
fun anyEnabled() = library || appSettings || sourceSettings /* SY --> */ || savedSearches /* SY <-- */ fun canRestore() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings /* SY --> */ || savedSearches /* SY <-- */
companion object { companion object {
val options = persistentListOf( val options = persistentListOf(
Entry( Entry(
label = MR.strings.label_library, label = MR.strings.label_library,
getter = RestoreOptions::library, getter = RestoreOptions::libraryEntries,
setter = { options, enabled -> options.copy(library = enabled) }, setter = { options, enabled -> options.copy(libraryEntries = enabled) },
),
Entry(
label = MR.strings.categories,
getter = RestoreOptions::categories,
setter = { options, enabled -> options.copy(categories = enabled) },
), ),
Entry( Entry(
label = MR.strings.app_settings, label = MR.strings.app_settings,
getter = RestoreOptions::appSettings, getter = RestoreOptions::appSettings,
setter = { options, enabled -> options.copy(appSettings = enabled) }, setter = { options, enabled -> options.copy(appSettings = enabled) },
), ),
Entry(
label = MR.strings.extensionRepo_settings,
getter = RestoreOptions::extensionRepoSettings,
setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) },
),
Entry( Entry(
label = MR.strings.source_settings, label = MR.strings.source_settings,
getter = RestoreOptions::sourceSettings, getter = RestoreOptions::sourceSettings,
@@ -52,11 +66,13 @@ data class RestoreOptions(
) )
fun fromBooleanArray(array: BooleanArray) = RestoreOptions( fun fromBooleanArray(array: BooleanArray) = RestoreOptions(
library = array[0], libraryEntries = array[0],
appSettings = array[1], categories = array[1],
sourceSettings = array[2], appSettings = array[2],
extensionRepoSettings = array[3],
sourceSettings = array[4],
// SY --> // SY -->
savedSearches = array[3] savedSearches = array[5]
// SY <-- // SY <--
) )
} }
@@ -13,7 +13,7 @@ class CategoriesRestorer(
private val libraryPreferences: LibraryPreferences = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(),
) { ) {
suspend fun restoreCategories(backupCategories: List<BackupCategory>) { suspend operator fun invoke(backupCategories: List<BackupCategory>) {
if (backupCategories.isNotEmpty()) { if (backupCategories.isNotEmpty()) {
val dbCategories = getCategories.await() val dbCategories = getCategories.await()
val dbCategoriesByName = dbCategories.associateBy { it.name } val dbCategoriesByName = dbCategories.associateBy { it.name }
@@ -21,14 +21,15 @@ class CategoriesRestorer(
val categories = backupCategories val categories = backupCategories
.sortedBy { it.order } .sortedBy { it.order }
.distinctBy { it.name }
.map { .map {
val newOrder = nextOrder++ val dbCategory = dbCategoriesByName[it.name]
dbCategoriesByName[it.name] if (dbCategory != null) return@map dbCategory
?: handler.awaitOneExecutable { val order = nextOrder++
categoriesQueries.insert(it.name, newOrder, it.flags) handler.awaitOneExecutable {
categoriesQueries.selectLastInsertedRowId() categoriesQueries.insert(it.name, order, it.flags)
}.let { id -> it.toCategory(id).copy(order = newOrder) } categoriesQueries.selectLastInsertedRowId()
}
.let { id -> it.toCategory(id).copy(order = order) }
} }
libraryPreferences.categorizedDisplaySettings().set( libraryPreferences.categorizedDisplaySettings().set(
@@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import tachiyomi.data.DatabaseHandler
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionRepoRestorer(
private val handler: DatabaseHandler = Injekt.get(),
private val getExtensionRepos: GetExtensionRepo = Injekt.get()
) {
suspend operator fun invoke(
backupRepo: BackupExtensionRepos,
) {
val dbRepos = getExtensionRepos.getAll()
val existingReposBySHA = dbRepos.associateBy { it.signingKeyFingerprint }
val existingReposByUrl = dbRepos.associateBy { it.baseUrl }
val urlExists = existingReposByUrl[backupRepo.baseUrl]
val shaExists = existingReposBySHA[backupRepo.signingKeyFingerprint]
if (urlExists != null && urlExists.signingKeyFingerprint != backupRepo.signingKeyFingerprint) {
error("Already Exists with different signing key fingerprint")
} else if (shaExists != null) {
error("${shaExists.name} has the same signing key fingerprint")
} else {
handler.await {
extension_reposQueries.insert(
backupRepo.baseUrl,
backupRepo.name,
backupRepo.shortName,
backupRepo.website,
backupRepo.signingKeyFingerprint
)
}
}
}
}
@@ -68,7 +68,7 @@ class MangaRestorer(
) )
} }
suspend fun restoreManga( suspend fun restore(
backupManga: BackupManga, backupManga: BackupManga,
backupCategories: List<BackupCategory>, backupCategories: List<BackupCategory>,
) { ) {
@@ -22,14 +22,14 @@ class PreferenceRestorer(
private val preferenceStore: PreferenceStore = Injekt.get(), private val preferenceStore: PreferenceStore = Injekt.get(),
) { ) {
fun restoreAppPreferences(preferences: List<BackupPreference>) { fun restoreApp(preferences: List<BackupPreference>) {
restorePreferences(preferences, preferenceStore) restorePreferences(preferences, preferenceStore)
LibraryUpdateJob.setupTask(context) LibraryUpdateJob.setupTask(context)
BackupCreateJob.setupTask(context) BackupCreateJob.setupTask(context)
} }
fun restoreSourcePreferences(preferences: List<BackupSourcePreferences>) { fun restoreSource(preferences: List<BackupSourcePreferences>) {
preferences.forEach { preferences.forEach {
val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey)) val sourcePrefs = AndroidPreferenceStore(context, sourcePreferences(it.sourceKey))
restorePreferences(it.prefs, sourcePrefs) restorePreferences(it.prefs, sourcePrefs)
@@ -21,7 +21,6 @@ import okhttp3.CacheControl
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.http.HTTP_NOT_MODIFIED
import okio.FileSystem import okio.FileSystem
import okio.Path.Companion.toOkioPath import okio.Path.Companion.toOkioPath
import okio.Source import okio.Source
@@ -348,5 +347,7 @@ class MangaCoverFetcher(
private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build() private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build()
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build() private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
private const val HTTP_NOT_MODIFIED = 304
} }
} }
@@ -18,7 +18,6 @@ import okhttp3.CacheControl
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.http.HTTP_NOT_MODIFIED
import okio.FileSystem import okio.FileSystem
import okio.Path.Companion.toOkioPath import okio.Path.Companion.toOkioPath
import okio.Source import okio.Source
@@ -260,5 +259,7 @@ class PagePreviewFetcher(
companion object { companion object {
private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build() private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build()
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build() private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
private const val HTTP_NOT_MODIFIED = 304
} }
} }
@@ -1,9 +1,10 @@
package eu.kanade.tachiyomi.data.coil package eu.kanade.tachiyomi.data.coil
import android.app.Application
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Build import android.os.Build
import coil3.ImageLoader import coil3.ImageLoader
import coil3.asCoilImage import coil3.asImage
import coil3.decode.DecodeResult import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils import coil3.decode.DecodeUtils
import coil3.decode.Decoder import coil3.decode.Decoder
@@ -11,37 +12,37 @@ import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult import coil3.fetch.SourceFetchResult
import coil3.request.Options import coil3.request.Options
import coil3.request.bitmapConfig import coil3.request.bitmapConfig
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.storage.CbzCrypto import eu.kanade.tachiyomi.util.storage.CbzCrypto
import eu.kanade.tachiyomi.util.storage.CbzCrypto.getCoverStream
import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.GLUtil
import net.lingala.zip4j.ZipFile import mihon.core.common.archive.archiveReader
import net.lingala.zip4j.model.FileHeader
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.BufferedInputStream
/** /**
* A [Decoder] that uses built-in [ImageDecoder] to decode images that is not supported by the system. * A [Decoder] that uses built-in [ImageDecoder] to decode images that is not supported by the system.
*/ */
class TachiyomiImageDecoder(private val resources: ImageSource, private val options: Options) : Decoder { class TachiyomiImageDecoder(private val resources: ImageSource, private val options: Options) : Decoder {
private val context = Injekt.get<Application>()
override suspend fun decode(): DecodeResult { override suspend fun decode(): DecodeResult {
// SY --> // SY -->
var zip4j: ZipFile? = null var coverStream: BufferedInputStream? = null
var entry: FileHeader? = null
if (resources.sourceOrNull()?.peek()?.use { CbzCrypto.detectCoverImageArchive(it.inputStream()) } == true) { if (resources.sourceOrNull()?.peek()?.use { CbzCrypto.detectCoverImageArchive(it.inputStream()) } == true) {
if (resources.source().peek().use { ImageUtil.findImageType(it.inputStream()) == null }) { if (resources.source().peek().use { ImageUtil.findImageType(it.inputStream()) == null }) {
zip4j = ZipFile(resources.file().toFile().absolutePath) coverStream = UniFile.fromFile(resources.file().toFile())
entry = zip4j.fileHeaders.firstOrNull { ?.archiveReader(context = context)
it.fileName.equals(CbzCrypto.DEFAULT_COVER_NAME, ignoreCase = true) ?.getCoverStream()
}
if (zip4j.isEncrypted) zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
} }
} }
val decoder = resources.sourceOrNull()?.use { val decoder = resources.sourceOrNull()?.use {
zip4j.use { zipFile -> coverStream.use { coverStream ->
ImageDecoder.newInstance(zipFile?.getInputStream(entry) ?: it.inputStream(), options.cropBorders, displayProfile) ImageDecoder.newInstance(coverStream ?: it.inputStream(), options.cropBorders, displayProfile)
} }
} }
// SY <-- // SY <--
@@ -80,7 +81,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
} }
return DecodeResult( return DecodeResult(
image = bitmap.asCoilImage(), image = bitmap.asImage(),
isSampled = sampleSize > 1, isSampled = sampleSize > 1,
) )
} }
@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import exh.log.xLogE import exh.log.xLogE
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
@@ -20,6 +21,7 @@ import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@@ -35,6 +37,7 @@ import uy.kohesive.injekt.api.get
* and retrieved through dependency injection. You can use this class to queue new chapters or query * and retrieved through dependency injection. You can use this class to queue new chapters or query
* downloaded chapters. * downloaded chapters.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class DownloadManager( class DownloadManager(
private val context: Context, private val context: Context,
private val provider: DownloadProvider = Injekt.get(), private val provider: DownloadProvider = Injekt.get(),
@@ -168,7 +171,7 @@ class DownloadManager(
source, source,
) )
val files = chapterDir?.listFiles().orEmpty() val files = chapterDir?.listFiles().orEmpty()
.filter { "image" in it.type.orEmpty() } .filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } }
if (files.isEmpty()) { if (files.isEmpty()) {
throw Exception(context.stringResource(MR.strings.page_list_empty_error)) throw Exception(context.stringResource(MR.strings.page_list_empty_error))
@@ -21,6 +21,7 @@ import exh.util.DataSaver
import exh.util.DataSaver.Companion.getImage import exh.util.DataSaver.Companion.getImage
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -43,10 +44,10 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import mihon.core.common.archive.ZipWriter
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response import okhttp3.Response
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.addFilesToZip
import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.launchNow
@@ -64,18 +65,15 @@ import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.BufferedOutputStream
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/** /**
* This class is the one in charge of downloading chapters. * This class is the one in charge of downloading chapters.
* *
* Its queue contains the list of chapters to download. * Its queue contains the list of chapters to download.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class Downloader( class Downloader(
private val context: Context, private val context: Context,
private val provider: DownloadProvider, private val provider: DownloadProvider,
@@ -551,14 +549,8 @@ class Downloader(
* @param file the file where the image is already downloaded. * @param file the file where the image is already downloaded.
*/ */
private fun getImageExtension(response: Response, file: UniFile): String { private fun getImageExtension(response: Response, file: UniFile): String {
// Read content type if available.
val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null } val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null }
// Else guess from the uri. return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() }
?: context.contentResolver.getType(file.uri)
// Else read magic numbers.
?: ImageUtil.findImageType { file.openInputStream() }?.mime
return ImageUtil.getExtensionFromMimeType(mime)
} }
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {
@@ -623,70 +615,19 @@ class Downloader(
tmpDir: UniFile, tmpDir: UniFile,
) { ) {
// SY --> // SY -->
if (CbzCrypto.getPasswordProtectDlPref() && CbzCrypto.isPasswordSet()) { val encrypt = CbzCrypto.getPasswordProtectDlPref() && CbzCrypto.isPasswordSet()
archiveEncryptedChapter(mangaDir, dirname, tmpDir)
return
}
// SY <-- // SY <--
val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!! val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!!
ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut -> ZipWriter(context, zip, /* SY --> */ encrypt /* SY <-- */).use { writer ->
zipOut.setMethod(ZipEntry.STORED) tmpDir.listFiles()?.forEach { file ->
writer.write(file)
tmpDir.listFiles()?.forEach { img ->
img.openInputStream().use { input ->
val data = input.readBytes()
val size = img.length()
val entry = ZipEntry(img.name).apply {
val crc = CRC32().apply {
update(data)
}
setCrc(crc.value)
compressedSize = size
setSize(size)
}
zipOut.putNextEntry(entry)
zipOut.write(data)
}
} }
} }
zip.renameTo("$dirname.cbz") zip.renameTo("$dirname.cbz")
tmpDir.delete() tmpDir.delete()
} }
// SY -->
private fun archiveEncryptedChapter(
mangaDir: UniFile,
dirname: String,
tmpDir: UniFile,
) {
tmpDir.filePath?.let { addPaddingToImage(File(it)) }
tmpDir.listFiles()?.toList()?.let { files ->
mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")
?.addFilesToZip(files, CbzCrypto.getDecryptedPasswordCbz())
}
mangaDir.findFile("$dirname.cbz$TMP_DIR_SUFFIX")?.renameTo("$dirname.cbz")
tmpDir.delete()
}
private fun addPaddingToImage(imageDir: File) {
imageDir.listFiles()
// using ImageUtils isImage and findImageType functions causes IO errors when deleting files to set Exif Metadata
// it should be safe to assume that all files with image extensions are actual images at this point
?.filter {
it.extension.equals("jpg", true) ||
it.extension.equals("jpeg", true) ||
it.extension.equals("png", true) ||
it.extension.equals("webp", true)
}
?.forEach { ImageUtil.addPaddingToImageExif(it) }
}
// SY <--
/** /**
* Creates a ComicInfo.xml file inside the given directory. * Creates a ComicInfo.xml file inside the given directory.
*/ */
@@ -9,6 +9,7 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import coil3.asDrawable
import coil3.imageLoader import coil3.imageLoader
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.transformations import coil3.request.transformations
@@ -27,6 +28,7 @@ import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.getBitmapOrNull
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify import eu.kanade.tachiyomi.util.system.notify
import kotlinx.coroutines.DelicateCoroutinesApi
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
@@ -41,6 +43,7 @@ import uy.kohesive.injekt.api.get
import java.math.RoundingMode import java.math.RoundingMode
import java.text.NumberFormat import java.text.NumberFormat
@OptIn(DelicateCoroutinesApi::class)
class LibraryUpdateNotifier( class LibraryUpdateNotifier(
private val context: Context, private val context: Context,
@@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
@@ -41,6 +42,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
* Pending Broadcasts should be made from here. * Pending Broadcasts should be made from here.
* NOTE: Use local broadcasts if possible. * NOTE: Use local broadcasts if possible.
*/ */
@OptIn(DelicateCoroutinesApi::class)
class NotificationReceiver : BroadcastReceiver() { class NotificationReceiver : BroadcastReceiver() {
private val getManga: GetManga by injectLazy() private val getManga: GetManga by injectLazy()
@@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import androidx.core.net.toUri import androidx.core.net.toUri
@@ -65,21 +66,26 @@ class ImageSaver(
filename: String, filename: String,
data: () -> InputStream, data: () -> InputStream,
): Uri { ): Uri {
val pictureDir = val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime)
val pictureDir = if (isMimeTypeSupported) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
}
val imageLocation = (image.location as Location.Pictures).relativePath val imageLocation = (image.location as Location.Pictures).relativePath
val relativePath = listOf( val relativePath = listOf(
Environment.DIRECTORY_PICTURES, if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS,
context.stringResource(MR.strings.app_name), context.stringResource(MR.strings.app_name),
imageLocation, imageLocation,
).joinToString(File.separator) ).joinToString(File.separator)
val contentValues = contentValuesOf( val contentValues = contentValuesOf(
MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
MediaStore.Images.Media.MIME_TYPE to type.mime, MediaStore.MediaColumns.MIME_TYPE to type.mime,
MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond, MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond,
) )
val picture = findUriOrDefault(relativePath, filename) { val picture = findUriOrDefault(relativePath, filename) {
@@ -45,7 +45,7 @@ class SyncDataJob(private val context: Context, workerParams: WorkerParameters)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
notifier.showSyncError(e.message) notifier.showSyncError(e.message)
Result.failure() Result.success() // try again next time
} finally { } finally {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS) context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
} }
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.backup.create.BackupOptions
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupChapter import eu.kanade.tachiyomi.data.backup.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.restore.BackupRestoreJob
import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions import eu.kanade.tachiyomi.data.backup.restore.RestoreOptions
import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer
@@ -85,6 +84,7 @@ class SyncManager(
chapters = syncOptions.chapters, chapters = syncOptions.chapters,
tracking = syncOptions.tracking, tracking = syncOptions.tracking,
history = syncOptions.history, history = syncOptions.history,
extensionRepoSettings = syncOptions.extensionRepoSettings,
appSettings = syncOptions.appSettings, appSettings = syncOptions.appSettings,
sourceSettings = syncOptions.sourceSettings, sourceSettings = syncOptions.sourceSettings,
privateSettings = syncOptions.privateSettings, privateSettings = syncOptions.privateSettings,
@@ -92,19 +92,22 @@ class SyncManager(
// SY --> // SY -->
customInfo = syncOptions.customInfo, customInfo = syncOptions.customInfo,
readEntries = syncOptions.readEntries, readEntries = syncOptions.readEntries,
savedSearches = syncOptions.savedSearches,
// SY <-- // SY <--
) )
logcat(LogPriority.DEBUG) { "Begin create backup" } logcat(LogPriority.DEBUG) { "Begin create backup" }
val backupManga = backupCreator.backupMangas(databaseManga, backupOptions)
val backup = Backup( val backup = Backup(
backupManga = backupCreator.backupMangas(databaseManga, backupOptions), backupManga = backupManga,
backupCategories = backupCreator.backupCategories(backupOptions), backupCategories = backupCreator.backupCategories(backupOptions),
backupSources = backupCreator.backupSources(databaseManga), backupSources = backupCreator.backupSources(backupManga),
backupPreferences = backupCreator.backupAppPreferences(backupOptions), backupPreferences = backupCreator.backupAppPreferences(backupOptions),
backupSourcePreferences = backupCreator.backupSourcePreferences(backupOptions), backupSourcePreferences = backupCreator.backupSourcePreferences(backupOptions),
backupExtensionRepo = backupCreator.backupExtensionRepos(backupOptions),
// SY --> // SY -->
backupSavedSearches = backupCreator.backupSavedSearches(), backupSavedSearches = backupCreator.backupSavedSearches(backupOptions),
// SY <-- // SY <--
) )
logcat(LogPriority.DEBUG) { "End create backup" } logcat(LogPriority.DEBUG) { "End create backup" }
@@ -153,7 +156,7 @@ class SyncManager(
} }
// Stop the sync early if the remote backup is null or empty // Stop the sync early if the remote backup is null or empty
if (remoteBackup.backupManga?.size == 0) { if (remoteBackup.backupManga.size == 0) {
notifier.showSyncError("No data found on remote server.") notifier.showSyncError("No data found on remote server.")
return return
} }
@@ -175,6 +178,7 @@ class SyncManager(
backupSources = remoteBackup.backupSources, backupSources = remoteBackup.backupSources,
backupPreferences = remoteBackup.backupPreferences, backupPreferences = remoteBackup.backupPreferences,
backupSourcePreferences = remoteBackup.backupSourcePreferences, backupSourcePreferences = remoteBackup.backupSourcePreferences,
backupExtensionRepo = remoteBackup.backupExtensionRepo,
// SY --> // SY -->
backupSavedSearches = remoteBackup.backupSavedSearches, backupSavedSearches = remoteBackup.backupSavedSearches,
@@ -199,7 +203,8 @@ class SyncManager(
options = RestoreOptions( options = RestoreOptions(
appSettings = true, appSettings = true,
sourceSettings = true, sourceSettings = true,
library = true, libraryEntries = true,
extensionRepoSettings = true,
), ),
) )
@@ -214,7 +219,7 @@ class SyncManager(
val cacheFile = File(context.cacheDir, "tachiyomi_sync_data.proto.gz") val cacheFile = File(context.cacheDir, "tachiyomi_sync_data.proto.gz")
return try { return try {
cacheFile.outputStream().use { output -> cacheFile.outputStream().use { output ->
output.write(ProtoBuf.encodeToByteArray(BackupSerializer, backup)) output.write(ProtoBuf.encodeToByteArray(Backup.serializer(), backup))
Uri.fromFile(cacheFile) Uri.fromFile(cacheFile)
} }
} catch (e: IOException) { } catch (e: IOException) {
@@ -10,7 +10,6 @@ import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeToken
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse
import com.google.api.client.http.ByteArrayContent
import com.google.api.client.http.InputStreamContent import com.google.api.client.http.InputStreamContent
import com.google.api.client.http.javanet.NetHttpTransport import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.JsonFactory import com.google.api.client.json.JsonFactory
@@ -20,11 +19,9 @@ import com.google.api.services.drive.DriveScopes
import com.google.api.services.drive.model.File import com.google.api.services.drive.model.File
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.json.encodeToStream
import logcat.LogPriority import logcat.LogPriority
import logcat.logcat import logcat.logcat
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
@@ -37,7 +34,6 @@ import uy.kohesive.injekt.api.get
import java.io.IOException import java.io.IOException
import java.io.PipedInputStream import java.io.PipedInputStream
import java.io.PipedOutputStream import java.io.PipedOutputStream
import java.time.Instant
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@@ -64,12 +60,12 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
private val appName = context.stringResource(MR.strings.app_name) private val appName = context.stringResource(MR.strings.app_name)
private val remoteFileName = "${appName}_sync_data.gz" private val remoteFileName = "${appName}_sync.proto.gz"
private val lockFileName = "${appName}_sync.lock"
private val googleDriveService = GoogleDriveService(context) private val googleDriveService = GoogleDriveService(context)
private val protoBuf: ProtoBuf = Injekt.get()
override suspend fun doSync(syncData: SyncData): Backup? { override suspend fun doSync(syncData: SyncData): Backup? {
beforeSync() beforeSync()
@@ -107,64 +103,12 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
} }
private suspend fun beforeSync() { private suspend fun beforeSync() {
try { googleDriveService.refreshToken()
googleDriveService.refreshToken()
val drive = googleDriveService.driveService
?: throw Exception(context.stringResource(SYMR.strings.google_drive_not_signed_in))
var backoff = 1000L
var retries = 0 // Retry counter
val maxRetries = 10 // Maximum number of retries
while (retries < maxRetries) {
val lockFiles = findLockFile(drive)
logcat(LogPriority.DEBUG) { "Found ${lockFiles.size} lock file(s)" }
when {
lockFiles.isEmpty() -> {
logcat(LogPriority.DEBUG) { "No lock file found, creating a new one" }
createLockFile(drive)
break
}
lockFiles.size == 1 -> {
val lockFile = lockFiles.first()
val createdTime = Instant.parse(lockFile.createdTime.toString())
val ageMinutes = java.time.Duration.between(createdTime, Instant.now()).toMinutes()
logcat(LogPriority.DEBUG) { "Lock file age: $ageMinutes minutes" }
if (ageMinutes <= 3) {
logcat(LogPriority.DEBUG) { "Lock file is new, proceeding with sync" }
break
} else {
logcat(LogPriority.DEBUG) { "Lock file is old, deleting and creating a new one" }
deleteLockFile(drive)
createLockFile(drive)
break
}
}
else -> {
logcat(LogPriority.DEBUG) { "Multiple lock files found, applying backoff" }
delay(backoff) // Apply backoff strategy
backoff = (backoff * 2).coerceAtMost(16000L)
logcat(LogPriority.DEBUG) { "Backoff increased to $backoff milliseconds" }
}
}
retries++ // Increment retry counter
logcat(LogPriority.DEBUG) { "Loop iteration complete, retry count: $retries, backoff time: $backoff" }
}
if (retries >= maxRetries) {
logcat(LogPriority.ERROR) { "Max retries reached, exiting sync process" }
throw Exception(context.stringResource(SYMR.strings.error_before_sync_gdrive) + ": Max retries reached.")
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, throwable = e) { "Error in GoogleDrive beforeSync" }
throw Exception(context.stringResource(SYMR.strings.error_before_sync_gdrive) + ": ${e.message}", e)
}
} }
private fun pullSyncData(): SyncData? { private fun pullSyncData(): SyncData? {
val drive = googleDriveService.driveService ?: val drive = googleDriveService.driveService
throw Exception(context.stringResource(SYMR.strings.google_drive_not_signed_in)) ?: throw Exception(context.stringResource(SYMR.strings.google_drive_not_signed_in))
val fileList = getAppDataFileList(drive) val fileList = getAppDataFileList(drive)
if (fileList.isEmpty()) { if (fileList.isEmpty()) {
@@ -178,7 +122,10 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
try { try {
drive.files().get(gdriveFileId).executeMediaAsInputStream().use { inputStream -> drive.files().get(gdriveFileId).executeMediaAsInputStream().use { inputStream ->
GZIPInputStream(inputStream).use { gzipInputStream -> GZIPInputStream(inputStream).use { gzipInputStream ->
return Json.decodeFromStream(SyncData.serializer(), gzipInputStream) val byteArray = gzipInputStream.readBytes()
val backup = protoBuf.decodeFromByteArray(Backup.serializer(), byteArray)
val deviceId = fileList[0].appProperties["deviceId"] ?: ""
return SyncData(deviceId = deviceId, backup = backup)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -192,29 +139,40 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
?: throw Exception(context.stringResource(SYMR.strings.google_drive_not_signed_in)) ?: throw Exception(context.stringResource(SYMR.strings.google_drive_not_signed_in))
val fileList = getAppDataFileList(drive) val fileList = getAppDataFileList(drive)
val backup = syncData.backup ?: return
val byteArray = protoBuf.encodeToByteArray(Backup.serializer(), backup)
if (byteArray.isEmpty()) {
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
}
PipedOutputStream().use { pos -> PipedOutputStream().use { pos ->
PipedInputStream(pos).use { pis -> PipedInputStream(pos).use { pis ->
withIOContext { withIOContext {
// Start a coroutine or a background thread to write JSON to the PipedOutputStream
launch { launch {
GZIPOutputStream(pos).use { gzipOutputStream -> GZIPOutputStream(pos).use { gzipOutputStream ->
Json.encodeToStream(SyncData.serializer(), syncData, gzipOutputStream) gzipOutputStream.write(byteArray)
} }
} }
val mediaContent = InputStreamContent("application/octet-stream", pis)
if (fileList.isNotEmpty()) { if (fileList.isNotEmpty()) {
val fileId = fileList[0].id val fileId = fileList[0].id
val mediaContent = InputStreamContent("application/gzip", pis) val fileMetadata = File().apply {
drive.files().update(fileId, null, mediaContent).execute() name = remoteFileName
mimeType = "application/octet-stream"
appProperties = mapOf("deviceId" to syncData.deviceId)
}
drive.files().update(fileId, fileMetadata, mediaContent).execute()
logcat(LogPriority.DEBUG) { "Updated existing sync data file in Google Drive with file ID: $fileId" } logcat(LogPriority.DEBUG) { "Updated existing sync data file in Google Drive with file ID: $fileId" }
} else { } else {
val fileMetadata = File().apply { val fileMetadata = File().apply {
name = remoteFileName name = remoteFileName
mimeType = "application/gzip" mimeType = "application/octet-stream"
parents = listOf("appDataFolder") parents = listOf("appDataFolder")
appProperties = mapOf("deviceId" to syncData.deviceId)
} }
val mediaContent = InputStreamContent("application/gzip", pis)
val uploadedFile = drive.files().create(fileMetadata, mediaContent) val uploadedFile = drive.files().create(fileMetadata, mediaContent)
.setFields("id") .setFields("id")
.execute() .execute()
@@ -228,12 +186,12 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
private fun getAppDataFileList(drive: Drive): MutableList<File> { private fun getAppDataFileList(drive: Drive): MutableList<File> {
try { try {
// Search for the existing file by name in the appData folder // Search for the existing file by name in the appData folder
val query = "mimeType='application/gzip' and name = '$remoteFileName'" val query = "mimeType='application/x-gzip' and name = '$remoteFileName'"
val fileList = drive.files() val fileList = drive.files()
.list() .list()
.setSpaces("appDataFolder") .setSpaces("appDataFolder")
.setQ(query) .setQ(query)
.setFields("files(id, name, createdTime)") .setFields("files(id, name, createdTime, appProperties)")
.execute() .execute()
.files .files
logcat { "AppData folder file list: $fileList" } logcat { "AppData folder file list: $fileList" }
@@ -245,62 +203,6 @@ class GoogleDriveSyncService(context: Context, json: Json, syncPreferences: Sync
} }
} }
private fun createLockFile(drive: Drive) {
try {
val fileMetadata = File().apply {
name = lockFileName
mimeType = "text/plain"
parents = listOf("appDataFolder")
}
// Create an empty content to upload as the lock file
val emptyContent = ByteArrayContent.fromString("text/plain", "")
val file = drive.files().create(fileMetadata, emptyContent)
.setFields("id, name, createdTime")
.execute()
logcat { "Created lock file with ID: ${file.id}" }
} catch (e: Exception) {
logcat(LogPriority.ERROR, throwable = e) { "Error creating lock file" }
throw Exception(e.message, e)
}
}
private fun findLockFile(drive: Drive): MutableList<File> {
return try {
val query = "mimeType='text/plain' and name = '$lockFileName'"
val fileList = drive.files()
.list()
.setSpaces("appDataFolder")
.setQ(query)
.setFields("files(id, name, createdTime)")
.execute().files
logcat { "Lock file search result: $fileList" }
fileList
} catch (e: Exception) {
logcat(LogPriority.ERROR, throwable = e) { "Error finding lock file" }
mutableListOf()
}
}
private fun deleteLockFile(drive: Drive) {
try {
val lockFiles = findLockFile(drive)
if (lockFiles.isNotEmpty()) {
for (file in lockFiles) {
drive.files().delete(file.id).execute()
logcat { "Deleted lock file with ID: ${file.id}" }
}
} else {
logcat { "No lock file found to delete." }
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, throwable = e) { "Error deleting lock file" }
throw Exception(context.stringResource(SYMR.strings.error_deleting_google_drive_lock_file), e)
}
}
suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus { suspend fun deleteSyncDataFromGoogleDrive(): DeleteSyncDataStatus {
val drive = googleDriveService.driveService val drive = googleDriveService.driveService
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.sync.service
import android.content.Context import android.content.Context
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.BackupSerializer
import eu.kanade.tachiyomi.data.sync.SyncNotifier import eu.kanade.tachiyomi.data.sync.SyncNotifier
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.PUT import eu.kanade.tachiyomi.network.PUT
@@ -104,7 +103,7 @@ class SyncYomiSyncService(
} }
return try { return try {
val backup = protoBuf.decodeFromByteArray(BackupSerializer, byteArray) val backup = protoBuf.decodeFromByteArray(Backup.serializer(), byteArray)
return Pair(SyncData(backup = backup), newETag) return Pair(SyncData(backup = backup), newETag)
} catch (_: SerializationException) { } catch (_: SerializationException) {
logcat(LogPriority.INFO) { logcat(LogPriority.INFO) {
@@ -147,7 +146,7 @@ class SyncYomiSyncService(
.writeTimeout(timeout, TimeUnit.SECONDS) .writeTimeout(timeout, TimeUnit.SECONDS)
.build() .build()
val byteArray = protoBuf.encodeToByteArray(BackupSerializer, backup) val byteArray = protoBuf.encodeToByteArray(Backup.serializer(), backup)
if (byteArray.isEmpty()) { if (byteArray.isEmpty()) {
throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error)) throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error))
} }
@@ -8,6 +8,8 @@ import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import logcat.LogPriority import logcat.LogPriority
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
@@ -53,6 +55,15 @@ abstract class BaseTracker(
get() = getUsername().isNotEmpty() && get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty() getPassword().isNotEmpty()
override val isLoggedInFlow: Flow<Boolean> by lazy {
combine(
trackPreferences.trackUsername(this).changes(),
trackPreferences.trackPassword(this).changes(),
) { username, password ->
username.isNotEmpty() && password.isNotEmpty()
}
}
override fun getUsername() = trackPreferences.trackUsername(this).get() override fun getUsername() = trackPreferences.trackUsername(this).get()
override fun getPassword() = trackPreferences.trackPassword(this).get() override fun getPassword() = trackPreferences.trackPassword(this).get()
@@ -7,6 +7,7 @@ import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import tachiyomi.domain.track.model.Track as DomainTrack import tachiyomi.domain.track.model.Track as DomainTrack
@@ -61,6 +62,8 @@ interface Tracker {
val isLoggedIn: Boolean val isLoggedIn: Boolean
val isLoggedInFlow: Flow<Boolean>
fun getUsername(): String fun getUsername(): String
fun getPassword(): String fun getPassword(): String
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi
import kotlinx.coroutines.flow.combine
class TrackerManager { class TrackerManager {
@@ -40,5 +41,13 @@ class TrackerManager {
fun loggedInTrackers() = trackers.filter { it.isLoggedIn } fun loggedInTrackers() = trackers.filter { it.isLoggedIn }
fun loggedInTrackersFlow() = combine(trackers.map { it.isLoggedInFlow }) {
it.mapIndexedNotNull { index, isLoggedIn ->
if (isLoggedIn) trackers[index] else null
}
}
fun get(id: Long) = trackers.find { it.id == id } fun get(id: Long) = trackers.find { it.id == id }
fun getAll(ids: Set<Long>) = trackers.filter { it.id in ids }
} }
@@ -270,10 +270,10 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
private const val clientSecret = private const val clientSecret =
"54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151" "54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151"
private const val baseUrl = "https://kitsu.io/api/edge/" private const val baseUrl = "https://kitsu.app/api/edge/"
private const val loginUrl = "https://kitsu.io/api/oauth/token" private const val loginUrl = "https://kitsu.app/api/oauth/token"
private const val baseMangaUrl = "https://kitsu.io/manga/" private const val baseMangaUrl = "https://kitsu.app/manga/"
private const val algoliaKeyUrl = "https://kitsu.io/api/edge/algolia-keys/media/" private const val algoliaKeyUrl = "https://kitsu.app/api/edge/algolia-keys/media/"
private const val algoliaUrl = private const val algoliaUrl =
"https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/" "https://AWQO5J657S-dsn.algolia.net/1/indexes/production_media/query/"
@@ -129,12 +129,7 @@ class MyAnimeListApi(
obj["status"]!!.jsonPrimitive.content.replace("_", " ") obj["status"]!!.jsonPrimitive.content.replace("_", " ")
publishing_type = publishing_type =
obj["media_type"]!!.jsonPrimitive.content.replace("_", " ") obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
start_date = try { start_date = obj["start_date"]?.jsonPrimitive?.content ?: ""
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
outputDf.format(obj["start_date"]!!)
} catch (e: Exception) {
""
}
} }
} }
} }
@@ -102,7 +102,7 @@ abstract class Installer(private val service: Service) {
} }
val nextEntry = queue.first() val nextEntry = queue.first()
if (waitingInstall.compareAndSet(null, nextEntry)) { if (waitingInstall.compareAndSet(null, nextEntry)) {
queue.removeFirst() queue.removeAt(0)
processEntry(nextEntry) processEntry(nextEntry)
} }
} }
@@ -133,7 +133,10 @@ internal class ExtensionInstaller(private val context: Context) {
emit(downloadStatus) emit(downloadStatus)
// Stop polling when the download fails or finishes // Stop polling when the download fails or finishes
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) { if (
downloadStatus == DownloadManager.STATUS_SUCCESSFUL ||
downloadStatus == DownloadManager.STATUS_FAILED
) {
return@flow return@flow
} }
@@ -53,6 +53,9 @@ class AndroidSourceManager(
private val sourceRepository: StubSourceRepository, private val sourceRepository: StubSourceRepository,
) : SourceManager { ) : SourceManager {
private val _isInitialized = MutableStateFlow(false)
override val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
private val scope = CoroutineScope(Job() + Dispatchers.IO) private val scope = CoroutineScope(Job() + Dispatchers.IO)
@@ -189,9 +192,6 @@ class AndroidSourceManager(
} }
// SY --> // SY -->
private val _isInitialized = MutableStateFlow(false)
override val isInitialized: StateFlow<Boolean> = _isInitialized.asStateFlow()
override fun getVisibleOnlineSources() = sourcesMapFlow.value.values override fun getVisibleOnlineSources() = sourcesMapFlow.value.values
.filterIsInstance<HttpSource>() .filterIsInstance<HttpSource>()
.filter { .filter {
@@ -31,6 +31,7 @@ import exh.md.handlers.FollowsHandler
import exh.md.handlers.MangaHandler import exh.md.handlers.MangaHandler
import exh.md.handlers.MangaHotHandler import exh.md.handlers.MangaHotHandler
import exh.md.handlers.MangaPlusHandler import exh.md.handlers.MangaPlusHandler
import exh.md.handlers.NamicomiHandler
import exh.md.handlers.PageHandler import exh.md.handlers.PageHandler
import exh.md.handlers.SimilarHandler import exh.md.handlers.SimilarHandler
import exh.md.network.MangaDexLoginHelper import exh.md.network.MangaDexLoginHelper
@@ -123,6 +124,9 @@ class MangaDex(delegate: HttpSource, val context: Context) :
private val mangaHotHandler by lazy { private val mangaHotHandler by lazy {
MangaHotHandler(network.client, network.defaultUserAgentProvider()) MangaHotHandler(network.client, network.defaultUserAgentProvider())
} }
private val namicomiHandler by lazy {
NamicomiHandler(network.client, network.defaultUserAgentProvider())
}
private val pageHandler by lazy { private val pageHandler by lazy {
PageHandler( PageHandler(
headers, headers,
@@ -132,6 +136,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
bilibiliHandler, bilibiliHandler,
azukHandler, azukHandler,
mangaHotHandler, mangaHotHandler,
namicomiHandler,
trackPreferences, trackPreferences,
mdList, mdList,
) )
@@ -74,6 +74,7 @@ class MigrationBottomSheetDialogState(private val onStartMigration: State<(extra
binding.skipStep.isChecked = preferences.skipPreMigration().get() binding.skipStep.isChecked = preferences.skipPreMigration().get()
binding.HideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get() binding.HideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get()
binding.OnlyShowUpdates.isChecked = preferences.showOnlyUpdatesMigration().get()
binding.skipStep.setOnCheckedChangeListener { _, isChecked -> binding.skipStep.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
binding.root.context.toast( binding.root.context.toast(
@@ -86,6 +87,7 @@ class MigrationBottomSheetDialogState(private val onStartMigration: State<(extra
binding.migrateBtn.setOnClickListener { binding.migrateBtn.setOnClickListener {
preferences.skipPreMigration().set(binding.skipStep.isChecked) preferences.skipPreMigration().set(binding.skipStep.isChecked)
preferences.hideNotFoundMigration().set(binding.HideNotFoundManga.isChecked) preferences.hideNotFoundMigration().set(binding.HideNotFoundManga.isChecked)
preferences.showOnlyUpdatesMigration().set(binding.OnlyShowUpdates.isChecked)
onStartMigration.value( onStartMigration.value(
if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) { if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) {
binding.extraSearchParamText.toString() binding.extraSearchParamText.toString()
@@ -94,6 +94,7 @@ class MigrationListScreenModel(
val manualMigrations = MutableStateFlow(0) val manualMigrations = MutableStateFlow(0)
val hideNotFound = preferences.hideNotFoundMigration().get() val hideNotFound = preferences.hideNotFoundMigration().get()
val showOnlyUpdates = preferences.showOnlyUpdatesMigration().get()
val navigateOut = MutableSharedFlow<Unit>() val navigateOut = MutableSharedFlow<Unit>()
@@ -313,6 +314,12 @@ class MigrationListScreenModel(
if (result == null && hideNotFound) { if (result == null && hideNotFound) {
removeManga(manga) removeManga(manga)
} }
if (result != null && showOnlyUpdates &&
(getChapterInfo(result.id).latestChapter ?: 0.0) <= (manga.chapterInfo.latestChapter ?: 0.0)
) {
removeManga(manga)
}
sourceFinished() sourceFinished()
} }
} }
@@ -56,9 +56,7 @@ fun SourceFilterDialog(
) { ) {
val updateFilters = { onUpdate(filters) } val updateFilters = { onUpdate(filters) }
AdaptiveSheet( AdaptiveSheet(onDismissRequest = onDismissRequest) {
onDismissRequest = onDismissRequest,
) {
LazyColumn { LazyColumn {
stickyHeader { stickyHeader {
Row( Row(
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.util.ioCoroutineScope import eu.kanade.presentation.util.ioCoroutineScope
@@ -24,6 +25,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import tachiyomi.core.common.preference.toggle
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@@ -39,6 +41,7 @@ abstract class SearchScreenModel(
private val extensionManager: ExtensionManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val preferences: SourcePreferences = Injekt.get(),
) : StateScreenModel<SearchScreenModel.State>(initialState) { ) : StateScreenModel<SearchScreenModel.State>(initialState) {
private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher() private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher()
@@ -61,6 +64,14 @@ abstract class SearchScreenModel(
) )
} }
init {
screenModelScope.launch {
preferences.globalSearchFilterState().changes().collectLatest { state ->
mutableState.update { it.copy(onlyShowHasResults = state) }
}
}
}
@Composable @Composable
fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> { fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> {
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
@@ -111,7 +122,7 @@ abstract class SearchScreenModel(
} }
fun toggleFilterResults() { fun toggleFilterResults() {
mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) } preferences.globalSearchFilterState().toggle()
} }
fun search() { fun search() {
@@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -177,18 +178,18 @@ class LibraryScreenModel(
::Pair, ::Pair,
), ),
// SY <-- // SY <--
) { searchQuery, library, tracks, (loggedInTrackers, _), (groupType, sort) -> ) { searchQuery, library, tracks, (trackingFiler, _), (groupType, sort) ->
library library
// SY --> // SY -->
.applyGrouping(groupType) .applyGrouping(groupType)
// SY <-- // SY <--
.applyFilters(tracks, loggedInTrackers) .applyFilters(tracks, trackingFiler)
.applySort(tracks, /* SY --> */sort.takeIf { groupType != LibraryGroup.BY_DEFAULT } /* SY <-- */) .applySort(tracks, trackingFiler.keys,/* SY --> */sort.takeIf { groupType != LibraryGroup.BY_DEFAULT } /* SY <-- */)
.mapValues { (_, value) -> .mapValues { (_, value) ->
if (searchQuery != null) { if (searchQuery != null) {
// Filter query // Filter query
// SY --> // SY -->
filterLibrary(value, searchQuery, loggedInTrackers) filterLibrary(value, searchQuery, trackingFiler)
// SY <-- // SY <--
} else { } else {
// Don't do anything // Don't do anything
@@ -277,9 +278,10 @@ class LibraryScreenModel(
/** /**
* Applies library filters to the given map of manga. * Applies library filters to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private suspend fun LibraryMap.applyFilters( private suspend fun LibraryMap.applyFilters(
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
loggedInTrackers: Map<Long, TriState>, trackingFiler: Map<Long, TriState>,
): LibraryMap { ): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first() val prefs = getLibraryItemPreferencesFlow().first()
val downloadedOnly = prefs.globalFilterDownloaded val downloadedOnly = prefs.globalFilterDownloaded
@@ -291,10 +293,10 @@ class LibraryScreenModel(
val filterCompleted = prefs.filterCompleted val filterCompleted = prefs.filterCompleted
val filterIntervalCustom = prefs.filterIntervalCustom val filterIntervalCustom = prefs.filterIntervalCustom
val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty() val isNotLoggedInAnyTrack = trackingFiler.isEmpty()
val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } val excludedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null }
val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } val includedTracks = trackingFiler.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null }
val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty()
// SY --> // SY -->
@@ -371,9 +373,11 @@ class LibraryScreenModel(
/** /**
* Applies library sorting to the given map of manga. * Applies library sorting to the given map of manga.
*/ */
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun LibraryMap.applySort( private fun LibraryMap.applySort(
// Map<MangaId, List<Track>> // Map<MangaId, List<Track>>
trackMap: Map<Long, List<Track>>, trackMap: Map<Long, List<Track>>,
loggedInTrackerIds: Set<Long>,
/* SY --> */ /* SY --> */
groupSort: LibrarySort? = null, /* SY <-- */ groupSort: LibrarySort? = null, /* SY <-- */
): LibraryMap { ): LibraryMap {
@@ -397,7 +401,7 @@ class LibraryScreenModel(
val defaultTrackerScoreSortValue = -1.0 val defaultTrackerScoreSortValue = -1.0
val trackerScores by lazy { val trackerScores by lazy {
val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id } val trackerMap = trackerManager.getAll(loggedInTrackerIds).associateBy { e -> e.id }
trackMap.mapValues { entry -> trackMap.mapValues { entry ->
when { when {
entry.value.isEmpty() -> null entry.value.isEmpty() -> null
@@ -596,18 +600,17 @@ class LibraryScreenModel(
* @return map of track id with the filter value * @return map of track id with the filter value
*/ */
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> { private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedInTrackers = trackerManager.loggedInTrackers() return trackerManager.loggedInTrackersFlow().flatMapLatest { loggedInTrackers ->
return if (loggedInTrackers.isNotEmpty()) { if (loggedInTrackers.isEmpty()) return@flatMapLatest flowOf(emptyMap())
val prefFlows = loggedInTrackers
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() } val prefFlows = loggedInTrackers.map { tracker ->
.toTypedArray() libraryPreferences.filterTracking(tracker.id.toInt()).changes()
combine(*prefFlows) { }
combine(prefFlows) {
loggedInTrackers loggedInTrackers
.mapIndexed { index, tracker -> tracker.id to it[index] } .mapIndexed { index, tracker -> tracker.id to it[index] }
.toMap() .toMap()
} }
} else {
flowOf(emptyMap())
} }
} }
@@ -6,6 +6,8 @@ import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.data.track.TrackerManager
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.TriState import tachiyomi.core.common.preference.TriState
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
@@ -18,17 +20,22 @@ import tachiyomi.domain.library.model.LibrarySort
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
class LibrarySettingsScreenModel( class LibrarySettingsScreenModel(
val preferences: BasePreferences = Injekt.get(), val preferences: BasePreferences = Injekt.get(),
val libraryPreferences: LibraryPreferences = Injekt.get(), val libraryPreferences: LibraryPreferences = Injekt.get(),
private val setDisplayMode: SetDisplayMode = Injekt.get(), private val setDisplayMode: SetDisplayMode = Injekt.get(),
private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(), private val setSortModeForCategory: SetSortModeForCategory = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(), trackerManager: TrackerManager = Injekt.get(),
) : ScreenModel { ) : ScreenModel {
val trackers val trackersFlow = trackerManager.loggedInTrackersFlow()
get() = trackerManager.trackers.filter { it.isLoggedIn } .stateIn(
scope = screenModelScope,
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds),
initialValue = trackerManager.loggedInTrackers()
)
// SY --> // SY -->
val grouping by libraryPreferences.groupLibraryBy().asState(screenModelScope) val grouping by libraryPreferences.groupLibraryBy().asState(screenModelScope)
@@ -5,6 +5,7 @@ import android.net.Uri
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import coil3.asDrawable
import coil3.imageLoader import coil3.imageLoader
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.size.Size import coil3.size.Size
@@ -186,7 +186,7 @@ class MangaScreen(
) )
}.takeIf { isHttpSource }, }.takeIf { isHttpSource },
onTrackingClicked = { onTrackingClicked = {
if (screenModel.loggedInTrackers.isEmpty()) { if (!successState.hasLoggedInTrackers) {
navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
} else { } else {
screenModel.showTrackDialog() screenModel.showTrackDialog()
@@ -127,6 +127,7 @@ import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.source.local.LocalSource import tachiyomi.source.local.LocalSource
@@ -187,8 +188,6 @@ class MangaScreenModel(
private val successState: State.Success? private val successState: State.Success?
get() = state.value as? State.Success get() = state.value as? State.Success
val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn } }
val manga: Manga? val manga: Manga?
get() = successState?.manga get() = successState?.manga
@@ -1490,45 +1489,56 @@ class MangaScreenModel(
val manga = state?.manga ?: return val manga = state?.manga ?: return
screenModelScope.launchIO { screenModelScope.launchIO {
getTracks.subscribe(manga.id) combine(
.catch { logcat(LogPriority.ERROR, it) } getTracks.subscribe(manga.id)
.map { tracks -> // SY -->
loggedInTrackers .map { trackItems ->
// Map to TrackItem if (manga.source in mangaDexSourceIds || state.mergedData?.manga?.values.orEmpty().any {
.map { service -> TrackItem(tracks.find { it.trackerId == service.id }, service) } it.source in mangaDexSourceIds
// Show only if the service supports this manga's source
.filter { (it.tracker as? EnhancedTracker)?.accept(source!!) ?: true }
}
// SY -->
.map { trackItems ->
if (manga.source in mangaDexSourceIds || state.mergedData?.manga?.values.orEmpty().any {
it.source in mangaDexSourceIds
}
) {
val mdTrack = trackItems.firstOrNull { it.tracker is MdList }
when {
mdTrack == null -> {
trackItems
} }
mdTrack.track == null -> { ) {
trackItems - mdTrack + createMdListTrack() val mdTrack = trackItems.firstOrNull { it.trackerId == TrackerManager.MDLIST }
when {
trackerManager.mdList.isLoggedIn && mdTrack == null -> {
trackItems + createMdListTrack()
}
else -> trackItems
} }
else -> trackItems } else {
trackItems
} }
} else {
trackItems
} }
// SY <--
.catch { logcat(LogPriority.ERROR, it) },
trackerManager.loggedInTrackersFlow(),
) { mangaTracks, loggedInTrackers ->
// Show only if the service supports this manga's source
val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedTracker)?.accept(source!!) ?: true }
val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet()
val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds }
// SY -->
val trackingCount = supportedTrackerTracks.count {
(it.trackerId == TrackerManager.MDLIST && it.status != FollowStatus.UNFOLLOWED.long) ||
it.trackerId != TrackerManager.MDLIST
} }
trackingCount to supportedTrackers.isNotEmpty()
// SY <-- // SY <--
}
.distinctUntilChanged() .distinctUntilChanged()
.collectLatest { trackItems -> .collectLatest { (trackingCount, hasLoggedInTrackers) ->
updateSuccessState { it.copy(trackItems = trackItems) } updateSuccessState {
it.copy(
trackingCount = trackingCount,
hasLoggedInTrackers = hasLoggedInTrackers,
)
}
} }
} }
} }
// SY --> // SY -->
private suspend fun createMdListTrack(): TrackItem { private suspend fun createMdListTrack(): Track {
val state = successState!! val state = successState!!
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds } val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds } ?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds }
@@ -1536,7 +1546,7 @@ class MangaScreenModel(
val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga) val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga)
.toDomainTrack(false)!! .toDomainTrack(false)!!
insertTrack.await(track) insertTrack.await(track)
return TrackItem(getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }, trackerManager.mdList) return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
} }
// SY <-- // SY <--
@@ -1633,7 +1643,8 @@ class MangaScreenModel(
val chapters: List<ChapterList.Item>, val chapters: List<ChapterList.Item>,
val availableScanlators: ImmutableSet<String>, val availableScanlators: ImmutableSet<String>,
val excludedScanlators: ImmutableSet<String>, val excludedScanlators: ImmutableSet<String>,
val trackItems: List<TrackItem> = emptyList(), val trackingCount: Int = 0,
val hasLoggedInTrackers: Boolean = false,
val isRefreshingData: Boolean = false, val isRefreshingData: Boolean = false,
val dialog: MangaScreenModel.Dialog? = null, val dialog: MangaScreenModel.Dialog? = null,
val hasPromptedToAddBefore: Boolean = false, val hasPromptedToAddBefore: Boolean = false,
@@ -1689,11 +1700,6 @@ class MangaScreenModel(
val filterActive: Boolean val filterActive: Boolean
get() = scanlatorFilterActive || manga.chaptersFiltered() get() = scanlatorFilterActive || manga.chaptersFiltered()
val trackingCount: Int
get() = trackItems.count {
it.track != null && ((it.tracker is MdList && it.track.status != FollowStatus.UNFOLLOWED.long) || it.tracker !is MdList)
}
/** /**
* Applies the view filters to the list of chapters obtained from the database. * Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted. * @return an observable of the list of chapters filtered and sorted.
@@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@@ -170,6 +171,7 @@ data class TrackInfoDialogHomeScreen(
), ),
) )
}, },
onCopyLink = { context.copyTrackerLink(it) },
) )
} }
@@ -183,6 +185,13 @@ data class TrackInfoDialogHomeScreen(
} }
} }
private fun Context.copyTrackerLink(trackItem: TrackItem) {
val url = trackItem.track?.remoteUrl ?: return
if (url.isNotBlank()) {
copyToClipboard(url, url)
}
}
private class Model( private class Model(
private val mangaId: Long, private val mangaId: Long,
private val sourceId: Long, private val sourceId: Long,
@@ -239,7 +248,7 @@ data class TrackInfoDialogHomeScreen(
} }
private fun List<Track>.mapToTrackItem(): List<TrackItem> { private fun List<Track>.mapToTrackItem(): List<TrackItem> {
val loggedInTrackers = Injekt.get<TrackerManager>().trackers.filter { it.isLoggedIn } val loggedInTrackers = Injekt.get<TrackerManager>().loggedInTrackers()
val source = Injekt.get<SourceManager>().getOrStub(sourceId) val source = Injekt.get<SourceManager>().getOrStub(sourceId)
return loggedInTrackers return loggedInTrackers
// Map to TrackItem // Map to TrackItem
@@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.ui.reader
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.assist.AssistContent import android.app.assist.AssistContent
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
@@ -35,6 +37,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.getSystemService
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.transition.doOnEnd import androidx.core.transition.doOnEnd
@@ -269,6 +272,9 @@ class ReaderActivity : BaseActivity() {
is ReaderViewModel.Event.ShareImage -> { is ReaderViewModel.Event.ShareImage -> {
onShareImageResult(event.uri, event.page /* SY --> */, event.secondPage /* SY <-- */) onShareImageResult(event.uri, event.page /* SY --> */, event.secondPage /* SY <-- */)
} }
is ReaderViewModel.Event.CopyImage -> {
onCopyImageResult(event.uri)
}
is ReaderViewModel.Event.SetCoverResult -> { is ReaderViewModel.Event.SetCoverResult -> {
onSetAsCoverResult(event.result) onSetAsCoverResult(event.result)
} }
@@ -1100,6 +1106,12 @@ class ReaderActivity : BaseActivity() {
startActivity(Intent.createChooser(intent, stringResource(MR.strings.action_share))) startActivity(Intent.createChooser(intent, stringResource(MR.strings.action_share)))
} }
private fun onCopyImageResult(uri: Uri) {
val clipboardManager = applicationContext.getSystemService<ClipboardManager>() ?: return
val clipData = ClipData.newUri(applicationContext.contentResolver, "", uri)
clipboardManager.setPrimaryClip(clipData)
}
/** /**
* Called from the presenter when a page is saved or fails. It shows a message or logs the * Called from the presenter when a page is saved or fails. It shows a message or logs the
* event depending on the [result]. * event depending on the [result].
@@ -707,7 +707,12 @@ class ReaderViewModel @JvmOverloads constructor(
it.chapterNumber.toFloat() == readerChapter.chapter.chapter_number it.chapterNumber.toFloat() == readerChapter.chapter.chapter_number
} }
.ifEmpty { null } .ifEmpty { null }
?.also { setReadStatus.await(true, *it.toTypedArray()) } ?.also {
setReadStatus.await(true, *it.toTypedArray())
it.forEach { chapter ->
deleteChapterIfNeeded(ReaderChapter(chapter))
}
}
} }
if (manga?.isEhBasedManga() == true) { if (manga?.isEhBasedManga() == true) {
viewModelScope.launchNonCancellable { viewModelScope.launchNonCancellable {
@@ -1156,7 +1161,7 @@ class ReaderViewModel @JvmOverloads constructor(
* get a path to the file and it has to be decompressed somewhere first. Only the last shared * get a path to the file and it has to be decompressed somewhere first. Only the last shared
* image will be kept so it won't be taking lots of internal disk space. * image will be kept so it won't be taking lots of internal disk space.
*/ */
fun shareImage(useExtraPage: Boolean) { fun shareImage(copyToClipboard: Boolean, useExtraPage: Boolean) {
// SY --> // SY -->
val page = if (useExtraPage) { val page = if (useExtraPage) {
(state.value.dialog as? Dialog.PageActions)?.extraPage (state.value.dialog as? Dialog.PageActions)?.extraPage
@@ -1182,7 +1187,7 @@ class ReaderViewModel @JvmOverloads constructor(
location = Location.Cache, location = Location.Cache,
), ),
) )
eventChannel.send(Event.ShareImage(uri, page)) eventChannel.send(if (copyToClipboard) Event.CopyImage(uri) else Event.ShareImage(uri, page))
} }
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
@@ -1190,7 +1195,7 @@ class ReaderViewModel @JvmOverloads constructor(
} }
// SY --> // SY -->
fun shareImages() { fun shareImages(copyToClipboard: Boolean) {
val (firstPage, secondPage) = (state.value.dialog as? Dialog.PageActions ?: return) val (firstPage, secondPage) = (state.value.dialog as? Dialog.PageActions ?: return)
val viewer = state.value.viewer as? PagerViewer ?: return val viewer = state.value.viewer as? PagerViewer ?: return
val isLTR = (viewer !is R2LPagerViewer) xor (viewer.config.invertDoublePages) val isLTR = (viewer !is R2LPagerViewer) xor (viewer.config.invertDoublePages)
@@ -1214,7 +1219,7 @@ class ReaderViewModel @JvmOverloads constructor(
location = Location.Cache, location = Location.Cache,
manga = manga, manga = manga,
) )
eventChannel.send(Event.ShareImage(uri, firstPage, secondPage)) eventChannel.send(if (copyToClipboard) Event.CopyImage(uri) else Event.ShareImage(uri, firstPage, secondPage))
} }
} catch (e: Throwable) { } catch (e: Throwable) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
@@ -1382,5 +1387,6 @@ class ReaderViewModel @JvmOverloads constructor(
val page: ReaderPage/* SY --> */, val page: ReaderPage/* SY --> */,
val secondPage: ReaderPage? = null, /* SY <-- */ val secondPage: ReaderPage? = null, /* SY <-- */
) : Event ) : Event
data class CopyImage(val uri: Uri) : Event
} }
} }

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