Compare commits

..

274 Commits

Author SHA1 Message Date
Jobobby04 f8f645772d Crashfix
Co-authored-by: name <arkon@users.noreply.github.com>
2026-01-01 12:26:13 -05:00
Jobobby04 b1e6fa65d6 Or 2025-12-26 23:31:31 -05:00
Jobobby04 01e8c6cc12 Use ComposeStars from RatingBar library. 2025-12-26 14:56:38 -05:00
Jobobby04 b4668c6829 Lint 2025-12-25 17:43:46 -05:00
Jobobby04 08d6c604bc Cleanup 2025-12-25 17:42:40 -05:00
Constantin Piber 02cec06535 Implement automatic removal of downloads on Suwayomi after reading, configurable via extension settings (#2673)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 1263df9d4111511e49a43463c9808060433ce76d)

# Conflicts:
#	CHANGELOG.md
2025-12-25 17:26:02 -05:00
Weblate (bot) ebdb3f7478 Translations update from Hosted Weblate (#2711)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ka/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hasanur Rahman Biplob <hrbiplob10@gmail.com>
Co-authored-by: Jakub Szafranek13 Fabijan <jakubfabijan@tuta.io>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Temuri Doghonadze <temuri.doghonadze@gmail.com>
Co-authored-by: ابْنُ السَدِيمِ <amarlubs2@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
(cherry picked from commit c96b6ae562cee1220b9fec74708d447413ab8c35)
2025-12-25 17:25:49 -05:00
Mend Renovate 3724d79825 Update dependency androidx.compose:compose-bom to v2025.12.01 (#2651)
(cherry picked from commit 23c427cf60701e46e470fee8b0a7564804ec599e)
2025-12-25 17:25:46 -05:00
Mend Renovate c3e2eb6672 Update markdown to v0.39.0 (#2804)
(cherry picked from commit e3260d56f713d4f5411ae00dfd3da2aba87f4cf2)
2025-12-25 17:25:39 -05:00
Mend Renovate fa91695add Update aboutlib.version to v13.2.1 (#2803)
(cherry picked from commit f37afbcec9f21823cd894036b6ca7f464eb34481)
2025-12-25 17:25:35 -05:00
MajorTanya e7786bd16f Fix pre-1970 upload date display in chapter list (#2779)
A user in #2777 was using the ComicInfo.xml Year/Month/Day fields to
indicate date of publication for some American comics, which often
predate the UNIX Epoch of 1970.

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

The `toRelativeString` extension function used in the other
`relativeDateText` function already accounts for very distant dates
(anything >7 days away turns into full date, not relative, regardless
of setting, though disabling the relative timestamp setting
circumvents this with the same result). Removing this line should not
cause any issues as it is purely a display difference and the use case
of backdating comics to pre-1970 is worth it in my opinion.

(cherry picked from commit 7a1c8a1b61e07d2e1a402b5daf0e7c04c232f655)

# Conflicts:
#	CHANGELOG.md
2025-12-25 17:25:31 -05:00
Mend Renovate 3d70476b9f Update dependency androidx.activity:activity-compose to v1.12.2 (#2797)
(cherry picked from commit 532b5cf290b448814ce5370bde461d9d5c8f086a)
2025-12-25 17:25:07 -05:00
Mend Renovate e74e0de8f5 Update kotlin monorepo to v2.3.0 (#2794)
(cherry picked from commit 3cb1b2e17a5f77d6133e6bf244304c2d6719aa04)
2025-12-25 17:24:59 -05:00
Luca Auer a2f552d6d2 Minimize memory usage by reducing in-memory cover cache size (#2266)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 4c9cfd8da5f9c67daa4b6401a910f979fd79179f)

# Conflicts:
#	CHANGELOG.md
2025-12-25 17:24:53 -05:00
AntsyLich a6bd0bbd2a Fix reader not saving read duration when changing chapter (#2784)
(cherry picked from commit 2e0786f699cc6d4863eb20331739c8325a451e63)

# Conflicts:
#	CHANGELOG.md
2025-12-25 17:24:36 -05:00
Mend Renovate fd42bba188 Update dependency com.google.firebase:firebase-bom to v34.7.0 (#2782)
(cherry picked from commit e7e4d9b6b35033a0568f40e32e94aaf336c96c39)
2025-12-25 17:24:06 -05:00
AntsyLich a0ec735066 Use AGP provided NDK and Build Tools version
(cherry picked from commit 5fe7dd9f0612412afabd7646f89ba38b230fb7e7)

# Conflicts:
#	buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt
2025-12-25 17:23:59 -05:00
Mend Renovate 89f5fce19d Update dependency com.android.tools.build:gradle to v8.13.2 (#2780)
(cherry picked from commit 4cb05cc738862be52c5f53e43c943f20712c6153)
2025-12-25 17:23:18 -05:00
Jobobby04 bf711a995c Fix build 2025-12-25 17:23:01 -05:00
AntsyLich d977614b7a Update tracker icons (#2773)
(cherry picked from commit 876c3f951b7e9782054d8f788ab39772ae6cf440)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt
2025-12-25 17:22:53 -05:00
Mend Renovate d282df6973 Update dependency androidx.activity:activity-compose to v1.12.1 (#2760)
(cherry picked from commit 08a61a42e9b01f5591615e298c2fecc9f59762ac)
2025-12-25 16:55:40 -05:00
Mend Renovate db5b3a69cc Update dependency io.mockk:mockk to v1.14.7 (#2771)
(cherry picked from commit cadd36ad9a5721d19ea51b68910b8b3276965b3d)
2025-12-25 16:55:35 -05:00
Mend Renovate c70c5dff25 Update dependency io.kotest:kotest-assertions-core to v6.0.7 (#2749)
(cherry picked from commit 556371e1c89bfb0961b63629e79d3fed6a8e7999)
2025-12-25 16:55:24 -05:00
AntsyLich 25ace80419 Cleanup BaseOAuthLoginActivity and TrackLoginActivity (#2748)
(cherry picked from commit c222a28bd14c989b6fa0d53d7497bf063887c9ec)
2025-12-25 16:55:13 -05:00
Jobobby04 b8b468cea7 Minor fixes 2025-12-25 16:49:01 -05:00
NGB-Was-Taken 0ffc798e9a Add preference to toggle chapter URL hash for downloads (#1533) 2025-12-25 16:47:56 -05:00
renovate[bot] ad5a76741a Update actions/upload-artifact action to v6 (#1530)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-25 16:46:49 -05:00
Weblate (bot) 003c5ad39a Translations update from Hosted Weblate (#1529)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/zh_Hant/
Translation: Mihon/TachiyomiSY

Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Hiroshi <borlonjhayron1119@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
2025-12-25 16:46:06 -05:00
NGB-Was-Taken 582d0ef121 Add handling for previously unhandled preferences (delegated MD) (#1524)
* Include romanized titles of the original language in description

* Implement handling for `finalChapterInDesc` preference.

* Handle `preferExtensionLangTitle` preference when fetching manga details.

* Address some warnings, clean up unused code and spotless apply.
2025-12-11 13:58:56 -05:00
NGB-Was-Taken 5566db160b fix deletion of duplicate downloaded chapters when automatically marked as read (#1500) 2025-12-11 13:56:57 -05:00
renovate[bot] 6fb6838656 Update actions/checkout action to v6 (#1522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:48:09 -05:00
renovate[bot] 1e5d490c22 Update actions/upload-artifact action to v5 (#1513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:48:02 -05:00
renovate[bot] 276aeb0f59 Update gradle/actions action to v5 (#1508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:47:56 -05:00
renovate[bot] c62d9d1446 Update actions/github-script action to v8 (#1497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:47:44 -05:00
renovate[bot] 4ff18364d9 Update actions/setup-java action to v5 (#1493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:47:36 -05:00
renovate[bot] 6c8e4e951a Update dependency net.zetetic:sqlcipher-android to v4.12.0 (#1485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:47:28 -05:00
renovate[bot] dc1fde628d Update koin to v4.1.1 (#1466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 13:47:09 -05:00
Weblate (bot) 241b70e5ce Translations update from Hosted Weblate (#1465)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/vi/
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/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/eo/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/hu/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ta/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/vi/
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: Acelith <joel.jon@moix.me>
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Champ0999 <champ0999@users.noreply.hosted.weblate.org>
Co-authored-by: Conrad Mateman <conradmateme001@gmail.com>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Deleted User <noreply+48943@weblate.org>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Dika <hikawaart2@gmail.com>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Jakub Fabijan <jakubfabijan@tuta.io>
Co-authored-by: João Sousa <joaopsousa99@gmail.com>
Co-authored-by: Karley <siegitsi@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: Manjul Tamrakar <manjultamrakar4@gmail.com>
Co-authored-by: Manuela Silva <mmsrs@sky.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mohamed kh <mohamedkhamekhami@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Omgeta <anooptiger@hotmail.com>
Co-authored-by: Rahim Kansous <rahimkansous18@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: WarriorDan <Danpgl@live.it>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Co-authored-by: dianisaac <muhandreop@gmail.com>
Co-authored-by: f_pluz <pedroh.lobo20@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Đào Ngọc Đang Khoa <daongocdangkhoa2510@gmail.com>
Co-authored-by: ابْنُ السَدِيمِ <amarlubs2@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
2025-12-11 13:46:18 -05:00
Jobobby04 64c755ddf3 Lint 2025-12-05 13:45:55 -05:00
Jobobby04 3ae6c0131b Lanraragi delegation 2025-12-05 13:44:58 -05:00
NGB-Was-Taken e3b43de298 Spotless apply 2025-11-26 18:41:48 +05:45
AntsyLich 02ff6b4e2f Fix mass migration not using the same search queries as individual migration (#2736)
(cherry picked from commit 7161bc2e825bdfd66a1829f7dce42bd0570b1008)
2025-11-26 15:06:06 +05:45
NGB-Was-Taken ee8379b12a Fix shizuku installer not updating installed extensions (#2697)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit c3d858a5613a133352f6b140916d05de7f752771)
2025-11-26 15:05:26 +05:45
Mend Renovate 20e1cc0a7d Update dependency com.pinterest.ktlint:ktlint-cli to v1.8.0 (#2708)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 13552c5ffa796aa9bf5db5e12c758a4b3763bf2b)
2025-11-26 15:02:17 +05:45
Mend Renovate 78b434e794 Update dependency androidx.activity:activity-compose to v1.12.0 (#2725)
(cherry picked from commit e6ca458e1f4cad3786d685ce8412ad40aed5155d)
2025-11-26 14:59:51 +05:45
Mend Renovate 52c8b260e0 Update moko to v0.25.2 (#2723)
(cherry picked from commit 05c7df2ed32b429f1ed3f1425e854c1867813a21)
2025-11-26 14:59:51 +05:45
Mend Renovate cccdc99977 Update sqlite to v2.6.2 (#2724)
(cherry picked from commit 4a3339a21f479bd57a7765cbeb092674aa2e8507)
2025-11-26 14:59:51 +05:45
Mend Renovate ca15d2ccc5 Update lifecycle.version to v2.10.0 (#2726)
(cherry picked from commit 6f497ed03181b15cb79899c689ffe70353656f0b)
2025-11-26 14:59:51 +05:45
Jobobby04 b7b0ffc885 Remove old SmartSearch 2025-11-20 13:20:53 -05:00
Mend Renovate f1a57749c4 Update dependency com.squareup.okio:okio to v3.16.4 (#2716)
(cherry picked from commit c17fc6792a3b8a5b6f31c107f165bfcda44617ae)
2025-11-20 12:53:57 -05:00
Mend Renovate 26d6c09d21 Update dependency io.kotest:kotest-assertions-core to v6.0.5 (#2717)
(cherry picked from commit 9e27ae3d2b63210714d6b16add687d2b37a34736)
2025-11-20 12:53:51 -05:00
Mend Renovate 088e3b6800 Update okhttp monorepo to v5.3.2 (#2720)
(cherry picked from commit 8b5dd39f1cbfd2eb8dd09cf42757206eeba631f7)
2025-11-20 12:53:45 -05:00
Mend Renovate c8ee2dbff4 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.1.0 (#2721)
(cherry picked from commit 8d81c94679e4ecc9e452b03feb7c08f524a4969d)
2025-11-20 12:53:39 -05:00
Mend Renovate 835a21b426 Update dependency com.google.firebase:firebase-bom to v34.6.0 (#2707)
(cherry picked from commit e5a693f224190a662f2192a06fd12ae2a03e7dc3)
2025-11-20 12:53:32 -05:00
Mend Renovate 8d67c87639 Update okhttp monorepo to v5.3.1 (#2712)
(cherry picked from commit 7947e1bd6a7bdd99fe6badde8b665da24f92ab7c)
2025-11-20 12:53:24 -05:00
Jobobby04 73cd25e8ba Remove unused migration preferences 2025-11-20 12:50:33 -05:00
Jobobby04 e9ed861f00 Minor cleanup 2025-11-20 12:49:42 -05:00
Jobobby04 e7c1d4deef Minor cleanup 2025-11-20 12:38:09 -05:00
Jobobby04 b8eb75fc68 Throttle E-Hentai requests 2025-11-20 12:37:37 -05:00
NGB-Was-Taken 3d34f3dd2f Exclude MergedSource from MigrationConfigScreen 2025-11-17 08:49:15 +05:45
NGB-Was-Taken 01dc277877 Replace topbar in ReaderAppBars with ReaderTopBar 2025-11-16 20:35:50 +05:45
NGB-Was-Taken b809ae5c6f Optimize imports 2025-11-16 18:48:51 +05:45
NGB-Was-Taken 4c563122f8 Spotless apply 2025-11-16 18:14:20 +05:45
NGB-Was-Taken 4c1124fdb0 Fix compile time errors and make it build 2025-11-16 18:07:44 +05:45
NGB-Was-Taken bdbaecd975 Fix queries and mappers to work with updated views 2025-11-16 18:07:44 +05:45
Mend Renovate 2b8641c1dd Update dependency com.squareup.okio:okio to v3.16.3 (#2709)
(cherry picked from commit cab729c9396d54c98b1e8c4716b93814016deab4)
2025-11-16 18:07:44 +05:45
Mend Renovate f1a90000b2 Update dependency app.cash.sqldelight:sqlite-3-38-dialect to v2.2.1 (#2703)
(cherry picked from commit f6a95a741667871df34288bdb6b67cafe2f79d54)
2025-11-16 18:07:44 +05:45
Weblate (bot) 3bd0f0b447 Translations update from Hosted Weblate (#2687)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/am/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Anderhale <anderhale@users.noreply.hosted.weblate.org>
Co-authored-by: Anderhale <safwanistaken@gmail.com>
Co-authored-by: Ardiansyahset <ardiantozep@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>
(cherry picked from commit 32db0a5f4c261062883a11f86b7091f654bff568)
2025-11-16 18:07:44 +05:45
NGB-Was-Taken cb5b618266 Stop tap zones from triggering when scrolling is stopped by tapping (#2680)
(cherry picked from commit 2ec67ac0c1831a68d8d73e2679f8c98a5a48acf5)
2025-11-16 18:07:44 +05:45
Mend Renovate c71c07690a Update sqldelight to v2.2.1 (#2704)
(cherry picked from commit b1b79a63f0ec570ba2eac41f8faa50ecf7301e5d)
2025-11-16 18:07:44 +05:45
Mend Renovate d85d77da01 Update dependency com.android.tools.build:gradle to v8.13.1 (#2685)
(cherry picked from commit ec99ab3aef734e8b9ce156fe7493f72e7a8cc03a)
2025-11-16 18:07:44 +05:45
Weblate (bot) 164d7bc70f Translations update from Hosted Weblate (#2676)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/am/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/da/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/eu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/gl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/he/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hu/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/kn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/lv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sa/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sah/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sq/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/th/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Gino Cicatiello <ginocic@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: MajorTanya <github@majortanya.eu>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: NGB-Was-Taken <76197326+NGB-Was-Taken@users.noreply.github.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>

(cherry picked from commit 84d620978bbc5a4698787f5ab81686e7e60767c9)
2025-11-16 18:07:44 +05:45
AntsyLich db51b09f80 Revert "Fix reader tap zones triggering after scrolling was stopped by the user" (#2670)
(cherry picked from commit 412815af067cdf343a6b5c7b5cd38eeb8190d543)
2025-11-16 18:07:44 +05:45
Weblate (bot) 5f8d03ba9b Translations update from Hosted Weblate (#2656)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/cs/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Doministo <doministo@seznam.cz>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>

(cherry picked from commit f7fb68692a7c41f9f09721dde8db574df1fde1ce)
2025-11-16 18:07:44 +05:45
AntsyLich 4cae7b27a6 Fix extra padding appearing in reader after user interactions (#2669)
(cherry picked from commit aa300cb53ea3a02b63c3b3f3fca60d5e7533a8f1)
2025-11-16 18:07:44 +05:45
Trevor Paley edcf939611 Improve WebView multi-window UX (#2662)
- Navigation history for lower windows is preserved when a popup is opened
- Back gesture will close a popup window rather than the entire WebView activity when there is no previous page
- The leftmost close button closes the entire activity as before
- When a popup window is shown, a new button appears to close just that window

(cherry picked from commit 855eea2ada8c09404dee99a965325913b246618f)
2025-11-16 18:07:44 +05:45
Mend Renovate 208d291b3c Update dependency androidx.core:core-splashscreen to v1.2.0 (#2661)
(cherry picked from commit f4703ed83a32afe1d39669f4988233c1fe7c3f32)
2025-11-16 18:07:44 +05:45
NGB-Was-Taken 19f049189a Fix flaky migration tests (#2663)
(cherry picked from commit 506d51a007e730594e4e5f05f00b4f94c4f24e05)
2025-11-16 18:07:44 +05:45
AntsyLich c855276555 Revert "Update dependency androidx.compose:compose-bom to v2025.10.01 (#2522)"
This reverts commit e8bdf58530cdfd6d530ea9a282785bd313e69be4.

(cherry picked from commit ace387f8bf8f451887b8055213be78312a5b4ea7)
2025-11-16 18:07:43 +05:45
Weblate (bot) 9e244e0889 Translations update from Hosted Weblate (#2646)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translation: Mihon/Mihon
Translation: Mihon/Mihon Plurals

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
(cherry picked from commit 5e428071c9a12bc637cbfcf235a33dc19cdef197)
2025-11-16 18:07:43 +05:45
AntsyLich bf7a067908 Fix long strip reader not scrolling on consecutive taps (#2650)
(cherry picked from commit 0acd80dd95094a837c8dc05f3fc29ef3e69bdc21)
2025-11-16 18:07:43 +05:45
bapeey 67c4b71b88 Fix WebView crash introduced in v0.19.2 (#2649)
(cherry picked from commit bdb0ce4779d565fc528e97bd38133a72ff3c1724)
2025-11-16 18:07:43 +05:45
Mend Renovate 1fa8a86cce Update dependency androidx.compose:compose-bom to v2025.10.01 (#2522)
(cherry picked from commit e8bdf58530cdfd6d530ea9a282785bd313e69be4)
2025-11-16 18:07:43 +05:45
Weblate (bot) 73eb98960f Translations update from Hosted Weblate (#2639)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/
Translation: Mihon/Mihon

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
(cherry picked from commit e36b4ce60b2b7141c5b6a4205e98853a0d07438b)
2025-11-16 18:07:43 +05:45
AntsyLich 59704221b7 Migrated to the Android specific about libraries gradle plugin
(cherry picked from commit 6d543024a32bba3136841a19942bed4ea8f0736b)
2025-11-16 18:07:43 +05:45
AntsyLich 19c23943ec Handle reader cutout setting with Insets to support Android 15+ (#2640)
(cherry picked from commit 0e0b6d92833f8e4f3aebdcc1f7c8c175084175d6)
2025-11-16 18:07:43 +05:45
Weblate (bot) d068559dee Translations update from Hosted Weblate (#2373)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ms/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
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/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/
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/pt/
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/sk/
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/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: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Efe Akın <efeakin1122@gmail.com>
Co-authored-by: Evan Jones (原文轩) <evanjones1883@gmail.com>
Co-authored-by: Farith <mail2@farithadnan.net>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <167056923+itsmechinmoy@users.noreply.github.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Jakub Fabijan <jakubfabijan@tuta.io>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Madddog1997 <madddog1997@gmail.com>
Co-authored-by: Manjul Tamrakar <manjultamrakar4@gmail.com>
Co-authored-by: Manuela Silva <mmsrs@sky.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mohamed kh <mohamedkhamekhami@gmail.com>
Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Co-authored-by: Omgeta <anooptiger@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Saft Octavian <saftoctavian@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: Throw Away <throwawayacc4gulshan@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: keegang 6705 <darunphobwi@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: 안세훈 <on9686@gmail.com>

(cherry picked from commit 860955389661ce35d37f499efdd27259b93d8e56)
2025-11-16 18:07:43 +05:45
AntsyLich 8e6b5b8bee Make reader edge-to-edge (#1908)
(cherry picked from commit 5f0c4606681cd59b38ae0855c7827e149fa5488c)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt
#	app/src/main/java/eu/kanade/presentation/reader/ReaderPageIndicator.kt
#	app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderAppBars.kt
#	app/src/main/java/eu/kanade/presentation/reader/appbars/ReaderBottomBar.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2025-11-16 18:07:43 +05:45
Naputt1 c99ddbe10f Fix reader tap zones triggering after scrolling was stopped by the user (#2518)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit ac28b6c80cee7605052299d6f5f43bc588f701d8)
2025-11-16 18:07:43 +05:45
Constantin Piber 23925c4ba6 Update Suwayomi tracker to use GraphQL API instead of REST API (#2585)
(cherry picked from commit cc2877673539db779af00fbefab3802ac52a7719)
2025-11-16 18:07:43 +05:45
Trevor Paley e71f0afd99 Added proper multi window support in WebView instead of treating everything as a redirect (#2584)
(cherry picked from commit 6ab87c793122165f98c36b4b7d9158069ea40f5a)
2025-11-16 18:07:43 +05:45
Kashish Aggarwal 014bf97248 Fix date picker not allowing the same start and finish date in negative time zones (#2617)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 8662f80fbf6b7c3aee4945bf656def8341cfdfd3)
2025-11-16 18:07:43 +05:45
anirudhn fc0d666366 Fix scrollbar not showing when animator duration scale animation is turned off (#2398)
(cherry picked from commit 09ec9fc8c54e126692ae68ff260058f3be46a5dd)
2025-11-16 18:07:43 +05:45
c2y5 eb7465e6f9 Fix extension download stuck at pending state in some cases (#2483)
Also auto update extension list whenever a repository is added or removed

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

(cherry picked from commit 87c6f34a558b009be4d316e396ec3eeb4cfbbbf8)
2025-11-16 18:07:43 +05:45
AntsyLich ff6ad20a77 Add option to customize concurrent downloads, increase page concurrency (#2637)
(cherry picked from commit 643762f91325a460c74398d472a555fb00ed9f63)
2025-11-16 18:07:43 +05:45
Mend Renovate 17c528a206 Update markdown to v0.38.1 (#2636)
(cherry picked from commit 7e880014b0f2c42bc430765a19068d93640ce603)
2025-11-16 18:07:43 +05:45
AntsyLich 63f4034a7f Add subtitle support to slider preference and general cleanup (#2635)
(cherry picked from commit f36c259c1faf2ee4a108fd98a5d27d93014ba34c)
2025-11-16 18:07:43 +05:45
AntsyLich 6c1bfc2177 Fix reader "Unable to edit key" error (#2634)
(cherry picked from commit aef3beb15fb2d4acbde9cb2ea4c5f639606d9fc8)
2025-11-16 18:07:43 +05:45
NGB-Was-Taken 45ff1f06ba Update shizuku.version to v13.1.5 (#2566)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit e9469451acf6c7ad39ff08e344ad5013ddc39337)
2025-11-16 18:07:43 +05:45
AntsyLich 3e287a593a Bump app version code and default user agent
(cherry picked from commit f9793d33233ceb7729fa9cfe5ae55b45a372c54c)
2025-11-16 18:07:43 +05:45
AntsyLich 01420154be Fix migration "Attempt to invoke virtual method" crash (#2632)
(cherry picked from commit 93ba6acea56334573fc506d593affd5bea2a86b2)
2025-11-16 18:07:43 +05:45
AntsyLich 1e4c596d0e Fix migration dialog migrating to wrong entry (#2631)
(cherry picked from commit 5e7fecc2c11b4a175fe1c3f698f7daeb58fe311f)
2025-11-16 18:07:43 +05:45
Mend Renovate 26cb2bbbd1 Update dependency org.junit.jupiter:junit-jupiter to v6.0.1 (#2630)
(cherry picked from commit 343074da5f7b5b6b2bb8116cc65348684d4bf615)
2025-11-16 18:07:43 +05:45
AntsyLich 588db79a64 Fix mass migration advanced search query building (#2629)
(cherry picked from commit 7c08b75555a5444ede4912dc5e32607fac2b9678)
2025-11-16 18:07:43 +05:45
Constantin Piber e5aaf3b31f Migrate Kitsu to use library_id and remote_id properly (#2609)
(cherry picked from commit cbf72f4c60cb85f29f8446ba1adb1cfd29d38a59)

# Conflicts:
#	CHANGELOG.md
#	data/src/main/sqldelight/tachiyomi/migrations/8.sqm
2025-11-16 18:07:43 +05:45
Mend Renovate 9c222b128b Update okhttp monorepo to v5.3.0 (#2628)
(cherry picked from commit 0b6de39f2f878f1188a4fc1ea989517bad38e6ca)
2025-11-16 18:07:43 +05:45
Mend Renovate c66e08d43e Update plugin google-services to v4.4.4 (#2573)
(cherry picked from commit 72c4d1fdee981ca0bc94cd32d0a43bb739f43a61)
2025-11-16 18:07:43 +05:45
Mend Renovate 493d8fc45f Update dependency androidx.work:work-runtime to v2.11.0 (#2626)
(cherry picked from commit 3ff25bc984717052b57484859283c96583d152b2)
2025-11-16 18:07:43 +05:45
Mend Renovate 47993cb55d Update dependency com.squareup.okio:okio to v3.16.2 (#2576)
(cherry picked from commit e9224bc2ba19a5a78dd16c30615f2cae5b02ba5d)
2025-11-16 18:07:43 +05:45
Mend Renovate 4701cbea23 Update dependency com.google.firebase:firebase-bom to v34.5.0 (#2575)
(cherry picked from commit 5ac58d01b82daae809139ccb4245802816d23ff8)
2025-11-16 18:07:43 +05:45
Mend Renovate 70efd6f2bf Update xml.serialization.version to v0.91.3 (#2625)
(cherry picked from commit eefaf028ce7bca41d25f77c73e44a1f36af8703a)
2025-11-16 18:07:43 +05:45
Mend Renovate b85b6a713c Update kotlin monorepo to v2.2.21 (#2624)
(cherry picked from commit 582ccca1ab327864e72fff0fe05da6757584c280)
2025-11-16 18:07:43 +05:45
Mend Renovate 6291529a10 Update dependency io.kotest:kotest-assertions-core to v6.0.4 (#2594)
(cherry picked from commit 8f972115a82cda8619577e22465a047c2a863a1a)
2025-11-16 18:07:43 +05:45
Mend Renovate 160907ab52 Update aboutlib.version to v13 (major) (#2580)
Update aboutlib.version to v13

(cherry picked from commit 6f6c0338110e0fdd16cfc997981a21b6ed4859bc)
2025-11-16 18:07:43 +05:45
Mend Renovate bccd30a80f Update okhttp monorepo to v5.2.1 (#2577)
(cherry picked from commit 57a0ab67112d1966c00d7ec433a2cff70064495e)
2025-11-16 18:07:43 +05:45
Radon Rosborough ef4d3e6c4d Improve handling of downloads for chapters with same metadata and optionally for OSes that don't support Unicode in filename (#2305)
Co-authored-by: jkim <jhskim@hotmail.com>
Co-authored-by: fatotak <111342761+fatotak@users.noreply.github.com>
Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com>
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 58b25d697f7987e9888344e815d5646ec010a663)
2025-11-16 18:07:43 +05:45
Mend Renovate 4fe7a1375a Update okhttp monorepo to v5.2.0 (#2564)
(cherry picked from commit 1a31c7c7ee31eee19030875d11ec1f31eab895a4)
2025-11-16 18:07:43 +05:45
NGB-Was-Taken 02bc195068 Fix disabling incognito mode from notification (#2512)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 96e5131358110079587d262d37ec346e10941758)
2025-11-16 18:07:43 +05:45
Mend Renovate 1e20913237 Update dependency com.google.firebase:firebase-bom to v34.3.0 (#2508)
(cherry picked from commit 1d5bc8d2c2e3cb6df56561782e2e2842af3eaa50)
2025-11-16 18:07:43 +05:45
Mend Renovate 48b488fa59 Update dependency org.junit.jupiter:junit-jupiter to v6 (#2553)
(cherry picked from commit 9a45d248b1545ec192b4d5cff9b0756d0a7a12dd)
2025-11-16 18:07:43 +05:45
Mend Renovate 893f3b2e34 Update moko to v0.25.1 (#2550)
(cherry picked from commit 04168ecec84a3e2b470468002c05bbe9e5e538b5)
2025-11-16 18:07:43 +05:45
Mend Renovate 25be12852f Update dependency io.mockk:mockk to v1.14.6 (#2549)
(cherry picked from commit 607f0ea9cd08c070323c4c1a46efec53dc4b1358)
2025-11-16 18:07:43 +05:45
Secozzi 4f0292b000 Update markdown to 0.37.0 (#2516)
(cherry picked from commit 27a4f6f45c0ba639450428f5f58b1c938fe3032e)
2025-11-16 18:07:43 +05:45
Mend Renovate f669fd9205 Update kotlin monorepo to v2.2.20 (#2498)
(cherry picked from commit 5236d003d27d2aaa8a4baaa8aa4dfc0c8b299fea)
2025-11-16 18:07:43 +05:45
Mend Renovate c3e0646b61 Update dependency androidx.work:work-runtime to v2.10.5 (#2523)
(cherry picked from commit d61a41e8192786c8bb958652098a704d79ad5f5d)
2025-11-16 18:07:43 +05:45
Mend Renovate 0d2cad8693 Update sqlite to v2.6.1 (#2525)
(cherry picked from commit 5637860dd2ed637223014433e1e6005885af10a1)
2025-11-16 18:07:43 +05:45
Mend Renovate 237916a37b Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8 (#2526)
(cherry picked from commit d4d18d0898d7fec07e6021e60481948a40b162ad)
2025-11-16 18:07:43 +05:45
Guzmazow 16653f9585 Improve spoofing of X-Requested-With header to support newer WebView versions (#2491)
(cherry picked from commit 065147472e8e683c47d11f3e00386aba2a7c1bac)
2025-11-16 18:07:43 +05:45
Constantin Piber 392b1009c9 Delegate Suwayomi tracker authentication to extension (#2476)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 6f635782c22cc0844caa4e4d88cf390005989b0b)
2025-11-16 18:07:43 +05:45
Mend Renovate 10c184e58a Update lifecycle.version to v2.9.4 (#2503)
Update dependency androidx.lifecycle:lifecycle-process to v2.9.4

(cherry picked from commit 86d85f74c097b8b52eb6a91887a055c92e1066f5)
2025-11-16 18:07:43 +05:45
Mend Renovate f6b8756dc0 Update sqlite to v2.6.0 (#2504)
(cherry picked from commit 29e6a2c4a6a32deae4c067c51b7e863b8f8128bb)
2025-11-16 18:07:43 +05:45
Mend Renovate f2654807a4 Update dependency androidx.core:core-ktx to v1.17.0 (#2402)
(cherry picked from commit 60c66bbd3a12e48bff52257c27d6b868e97da1d7)
2025-11-16 18:07:43 +05:45
Mend Renovate ab90b75d73 Update dependency androidx.activity:activity-compose to v1.11.0 (#2499)
(cherry picked from commit 060e5b2e2e8ad85886c4d3362ba1ae56fac52f79)
2025-11-16 18:07:43 +05:45
AntsyLich 0b58a081af Replace compose-stable-marker with compose-runtime-annotation
(cherry picked from commit 4ac9fcd4d3ff2eed59d9c8454d7463b7c9e6dda6)
2025-11-16 18:07:43 +05:45
AntsyLich ae98a5fe58 Bump compile and target sdk
(cherry picked from commit d3b7f7e55ff6609a2cddf1765205ba35c6c26b83)
2025-11-16 18:07:43 +05:45
Mend Renovate 74fa4e3503 Update dependency com.google.firebase:firebase-bom to v34.2.0 (#2376)
(cherry picked from commit 0d926626a1916783b67b3ae8cd2142aa90120e30)
2025-11-16 18:07:43 +05:45
Mend Renovate c20b0f67a8 Update dependency androidx.benchmark:benchmark-macro-junit4 to v1.4.1 (#2496)
(cherry picked from commit 6495a2ea430c82a8481b10ef345669d8ff915cac)
2025-11-16 18:07:43 +05:45
Mend Renovate 098c7196de Update dependency androidx.work:work-runtime to v2.10.4 (#2497)
(cherry picked from commit 94f711ba2a67d0da52ba26978f5c4a103abc7f69)
2025-11-16 18:07:43 +05:45
Mend Renovate 99a25560c1 Update dependency androidx.compose:compose-bom to v2025.09.00 (#2401)
(cherry picked from commit 9f5c4e03b23dab80051afa458361ce62133febbf)
2025-11-16 18:07:43 +05:45
Mend Renovate 79d19a2d8b Update lifecycle.version to v2.9.3 (#2447)
(cherry picked from commit 49562e1915108ac28fa3308d4eed319bb9ed61d5)
2025-11-16 18:07:43 +05:45
Mend Renovate 061d0809bd Update dependency org.jsoup:jsoup to v1.21.2 (#2438)
(cherry picked from commit 57c82b30bab20110832eecc4a825441708eaa02b)
2025-11-16 18:07:43 +05:45
Mend Renovate 0ce0c45cc7 Update dependency io.kotest:kotest-assertions-core to v6.0.3 (#2439)
(cherry picked from commit e573f72cfd5b6c66709ba948ce29c7246a9832e8)
2025-11-16 18:07:43 +05:45
Mend Renovate e910362b16 Update dependency com.android.tools.build:gradle to v8.13.0 (#2449)
(cherry picked from commit bd90307df9ca2f0d424483b29c0dd640eabc0f5f)
2025-11-16 18:07:43 +05:45
Secozzi 96c05bf113 Fix migration progress not updating and category flag mischeck (#2484)
- Fixed an issue where migration progress wasn't updated after a manual source search
- Fixed incorrect logic where the category migration flag was ignored due to checking the chapter flag instead

(cherry picked from commit 16b5317b90b3064d12aa38f687cc30110fd8cdb3)
2025-11-16 18:07:43 +05:45
Mend Renovate 6d2bda5c9d Update plugin firebase-crashlytics to v3.0.6 (#2374)
(cherry picked from commit 83f4b486296f6998efc8ef95fa9d58b75e5de130)
2025-11-16 18:07:43 +05:45
Mend Renovate 4a080fba3f Update dependency com.github.skydoves:compose-stable-marker to v1.0.7 (#2428)
(cherry picked from commit 85f5e5019e59a178f24c93c71644b1c4a08549f3)
2025-11-16 18:07:43 +05:45
AntsyLich ac5b3b164f Bump targetSdk to 35
(cherry picked from commit 4bc3b9f3b6fc87a9285ad3ca1aa8c81d8269d373)
2025-11-16 18:07:43 +05:45
Mend Renovate c43d7dbb31 Update dependency com.android.tools.build:gradle to v8.12.1 (#2417)
(cherry picked from commit feda4101525163c3978a2eb9ab934f9c4db1e873)
2025-11-16 18:07:43 +05:45
Mend Renovate d218fdfcf4 Update dependency sh.calvin.reorderable:reorderable to v3 (#2419)
(cherry picked from commit 200c2df5ba72a39c4116d14827d54a126b77cdc7)
2025-11-16 18:07:43 +05:45
Mend Renovate e59af2fd1f Update dependency io.kotest:kotest-assertions-core to v6 (#2416)
(cherry picked from commit be09cddde2f0799e7eff0003f9dacdb5a6e09947)
2025-11-16 18:07:43 +05:45
AntsyLich 3d761b5bf4 Switch to a fork of QuickJS Java
(cherry picked from commit 498317de52c119a08daaa8d38f3f7d859e7b4990)
2025-11-16 18:07:43 +05:45
Mend Renovate 1892101359 Update kotlin monorepo to v2.2.10 (#2404)
(cherry picked from commit 33b876edc6cd20c73682a165359bec1268aa1199)
2025-11-16 18:07:43 +05:45
Secozzi d76d25379e Don't hardcode app name in strings.xml (#2394)
(cherry picked from commit 3d3c36078a3b8cb741adffd46931aeab9cb38578)
2025-11-16 18:07:43 +05:45
Secozzi c96cf4b11a Fix height of description not being calculated correctly if images are present (#2382)
(cherry picked from commit c6a96b3970f0ba4643f438155653790973fa00f6)
2025-11-16 18:07:27 +05:45
krysanify 31601f523d Fix crash opening filter sheet with empty library and mark as read/unread for selected items (#2355)
(cherry picked from commit d88dbe6409b9c4dd7fbb37757e89c09af73f1fd3)
2025-11-16 17:48:12 +05:45
AntsyLich f9abe20b84 Remove gradle toolchains plugin
(cherry picked from commit d0bad9f0bd6b9d9dc641c81ee2428a0f859d01a8)
2025-11-16 17:48:12 +05:45
AntsyLich f7030ed800 Fix 'Default' category showing in library with no user-added categories (#2371)
(cherry picked from commit 7d717ee7fd9779b85fee29cadc946292cfdcee89)
2025-11-16 17:48:12 +05:45
AntsyLich f4173b3766 Fix title text color in light mode on mass migration list (#2370)
(cherry picked from commit a93f71b82be4489a7a2aefd8fa08bb114cae8db5)
2025-11-16 17:48:12 +05:45
AntsyLich 221a564644 Fix local source EPUB files not loading (#2369)
(cherry picked from commit 9bf3f15fff96b48e6847034c9fcd07f14675130b)
2025-11-16 17:48:12 +05:45
AntsyLich 7458eff2d8 Revert "Add full predictive back support (#2085)" (#2362)
This reverts commit c12bdbae8e7bc14da8966e45a3c450913e32129f.

(cherry picked from commit 1c3e96bf7f463949997c03e2119cfa55fd63dcd0)
2025-11-16 17:48:11 +05:45
Weblate (bot) 187245885a Translations update from Hosted Weblate (#1879)
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/as/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bg/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/el/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/eo/
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/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/hr/
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/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/uk/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bg/
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/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/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/ml/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/nl/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sc/
Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ta/
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/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: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Ahmed TOUCHANE <ahmedtouchane0@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Co-authored-by: Alex Maryson Jr <akamar87@gmail.com>
Co-authored-by: Aviv Ben Ami <avivbenami@gmail.com>
Co-authored-by: B4LiN7 <87660017+B4LiN7@users.noreply.github.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Conrad Mateman <conradmateme001@gmail.com>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Danilo Issida Goncalves <danissida@hotmail.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: Doministo <doministo@seznam.cz>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Guillaume Lecocq <guillaume.taylor@gmail.com>
Co-authored-by: Homura Akemi <amber_c001@protonmail.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Itsmechinmoy <itsmechinmoy@users.noreply.hosted.weblate.org>
Co-authored-by: Jakub Fabijan <jakubfabijan@tuta.io>
Co-authored-by: João Sousa <joaopsousa99@gmail.com>
Co-authored-by: Karley <siegitsi@gmail.com>
Co-authored-by: Lyfja <45209212+lyfja@users.noreply.github.com>
Co-authored-by: Mario Kevin D. A <programas013@gmail.com>
Co-authored-by: Mehedi Talha <meheditalha007@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mohamed kh <mohamedkhamekhami@gmail.com>
Co-authored-by: Nguyễn Trung Đức <vaicato16@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Prem Kumar <prem12321kumar@gmail.com>
Co-authored-by: Ryo Richie <ryorichie@gmail.com>
Co-authored-by: Saft Octavian <saftoctavian@gmail.com>
Co-authored-by: Siebrenvde <siebren@siebrenvde.dev>
Co-authored-by: Sky children of the Light <tu25261@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Tahsin Gökalp <tahsinsaan@gmail.com>
Co-authored-by: TheKingTermux <50316075+TheKingTermux@users.noreply.github.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: abc0922001 <abc0922001@hotmail.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: altinat <al@altqx.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>
Co-authored-by: edgole <test.backache009@aleeas.com>
Co-authored-by: f_pluz <pedroh.lobo20@gmail.com>
Co-authored-by: fl0k1 <michele.carnova@gmail.com>
Co-authored-by: gekka <1778962971@qq.com>
Co-authored-by: gulse02 <gnoeoxgulse@gmail.com>
Co-authored-by: kevans <albapazpi@gmail.com>
Co-authored-by: naikhon <naikhon5@gmail.com>
Co-authored-by: pancake <ppzh0@users.noreply.hosted.weblate.org>
Co-authored-by: scb261 <65343233+scb261@users.noreply.github.com>
Co-authored-by: scb261 <scb261261@gmail.com>
Co-authored-by: Đào Ngọc Đang Khoa <daongocdangkhoa2510@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: Артур Давлетов <ar.davletov2013@gmail.com>
Co-authored-by: Георгій Обушенков <heorhii.obushenkov@gmail.com>
Co-authored-by: Димитър Георгиев <dimitar13226@gmail.com>
Co-authored-by: ابومسلم <linuxmint1978@gmail.com>
Co-authored-by: ابْنُ السَدِيمِ <amarlubs2@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>

(cherry picked from commit 45c1a314887732f691e349a9e7684952830341f0)
2025-11-16 17:48:11 +05:45
Radon Rosborough e5d8c2edbc Use ComicInfo.xml for chapter metadata in localSource (#2332)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 32257e438e2f0bea7e0bfe84dc72135795d620ad)
2025-11-16 17:48:11 +05:45
anirudhn be4aa39c8a Fixed scrollbar sometimes not showing during scroll or not reaching the bottom with few items (#2304)
(cherry picked from commit 095ef8e74b1c208be25b616ce01d5b198749ee2e)
2025-11-16 17:48:11 +05:45
MajorTanya 014c697773 Add label to privately installed extensions (#2349)
Just adds the same word as the install option ("Private" in English)
next to the extension version and 18+ label.

(cherry picked from commit 549d74a2c9aef0eb41ea18378cd29d4ab9eee2b4)
2025-11-16 17:48:11 +05:45
AntsyLich ea733de80e Fix same manga check logic in mass migration
(cherry picked from commit f1193866f4306384a2a466c6353446bfed2bd9aa)
2025-11-16 17:48:11 +05:45
AntsyLich 3ac5dcd66e Potentially fix library IndexOutOfBound crash (#2341)
(cherry picked from commit c4407eda0eed5a7faed47d4470d79e6b1512b5c2)
2025-11-16 17:48:11 +05:45
AntsyLich 054de1cc6f Remove checksum from release notes and improve download tip
(cherry picked from commit b93337cb3d53278e191cf1dceeaf6c4effdb141d)
2025-11-16 17:48:11 +05:45
AntsyLich b525c0988a Support mass migration in 'Browse -> Migrate' (#2338)
(cherry picked from commit 22f851173b1eca242645f328a46e6038c035d5ec)
2025-11-16 17:48:11 +05:45
AntsyLich 691efe0831 Support mass migration for selected library items (#2336)
(cherry picked from commit 982ebcf777215c90584ad28fae79e9ca8a22a951)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt
2025-11-16 17:48:11 +05:45
AntsyLich 8317a30d6e Optimize and cleanup library code (#2329)
(cherry picked from commit e62cd0e816402303fdf12513816894624f77e208)

# Conflicts:
#	.editorconfig
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/presentation/library/components/LibraryContent.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt
#	data/src/main/sqldelight/tachiyomi/migrations/6.sqm
#	data/src/main/sqldelight/tachiyomi/view/libraryView.sq
2025-11-16 17:48:11 +05:45
Mend Renovate 633937b0bc Update dependency com.android.tools.build:gradle to v8.12.0 (#2331)
(cherry picked from commit 1365b28106c6fb01c920e997ad63c993e6703eef)
2025-11-16 17:48:11 +05:45
Mend Renovate 3152d2803a Update dependency androidx.test.ext:junit-ktx to v1.3.0 (#2327)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 7ec28eb3bd2d2ce409249e09b90189af21c2c2e6)
2025-11-16 17:48:11 +05:45
Mend Renovate 883c90adc8 Update dependency androidx.test.espresso:espresso-core to v3.7.0 (#2326)
(cherry picked from commit ff9dfe45ed4b9d4fa07cbc1b7bffbff86f596e5c)
2025-11-16 17:48:11 +05:45
Mend Renovate 8e658be7d7 Update dependency androidx.benchmark:benchmark-macro-junit4 to v1.4.0 (#2325)
(cherry picked from commit 967750ba5804f7ddf4ba230d8f3bb942096025b3)
2025-11-16 17:48:11 +05:45
Mend Renovate c27a4e2bf5 Update dependency androidx.work:work-runtime to v2.10.3 (#2324)
(cherry picked from commit 269af7fe2b5e1b444c1806bbeddfa89ec070ceab)
2025-11-16 17:48:11 +05:45
AwkwardPeak7 257f544a89 Include Manga initialized status in backup (#2285)
(cherry picked from commit 62eec15fe61be88e0ebc8be89a1e445dde55ba7e)

# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt
2025-11-16 17:48:11 +05:45
Secozzi f8cb08ce52 Add option for rendering images in description (#2076)
# Conflicts:
#	CHANGELOG.md
#	app/src/main/java/eu/kanade/domain/ui/UiPreferences.kt
2025-11-16 17:48:03 +05:45
Mend Renovate d970eea7cf Update dependency androidx.compose:compose-bom to v2025.07.00 (#2284)
(cherry picked from commit 4f1faf49f3c1fee757b6a9ec58b15c2a010085c3)
2025-11-16 12:37:36 +05:45
Mend Renovate a45fad81c8 Update okhttp monorepo to v5.1.0 (#2257)
(cherry picked from commit 6d717ea88b7545b3c7edba2dd051ce01f8f8aa7f)
2025-11-16 12:37:36 +05:45
Mend Renovate 76d6cf129a Update kotlin monorepo to v2.2.0 (#2235)
(cherry picked from commit fbb5e6b92f48b5f11fa4413f3a48e475cd431c11)
2025-11-16 12:37:36 +05:45
Mend Renovate 879427446f Update dependency org.jsoup:jsoup to v1.21.1 (#2233)
(cherry picked from commit 8f5f29e7376f0ecd275ed2fb95d13b1b10e8c04c)
2025-11-16 12:37:36 +05:45
Mend Renovate 9edd7b0c04 Update dependency com.squareup.okio:okio to v3.16.0 (#2320)
(cherry picked from commit a4b9c704b65027cd2066802960aeb248dbdf1c1f)
2025-11-16 12:37:36 +05:45
Mend Renovate d97b83fe93 Update plugin firebase-crashlytics to v3.0.5 (#2307)
(cherry picked from commit 9352201b03fad147b16423cf33584ecd58548700)
2025-11-16 12:37:36 +05:45
Mend Renovate 027e6bbb05 Update dependency com.google.firebase:firebase-bom to v34 (#2310)
(cherry picked from commit c715e981bffe2ad896559369515c491d2a76946f)
2025-11-16 12:37:36 +05:45
AntsyLich 4cdead8006 Make local source default chapter sorting match file explorer behavior
Closes #2225

(cherry picked from commit 7f56555d632508379037eb0fba4411079c27ad5b)
2025-11-16 12:37:36 +05:45
Mend Renovate aaff472317 Update dependency com.squareup.logcat:logcat to v0.4 (#2319)
(cherry picked from commit 8636b7a68526e033ced6767666f730e6c50ac1b3)
2025-11-16 12:37:36 +05:45
Mend Renovate 9b2febcd6d Update xml.serialization.version to v0.91.2 (#2317)
(cherry picked from commit a49670bf0dc1bfa3250356be2883ebc2f0b603c3)
2025-11-16 12:37:36 +05:45
Mend Renovate 5de355238d Update dependency com.squareup.logcat:logcat to v0.3 (#2309)
(cherry picked from commit 61cee5c5e04550a9c1f16a40600d9e60e28f072c)
2025-11-16 12:37:36 +05:45
Mend Renovate 644c8ec491 Update dependency com.pinterest.ktlint:ktlint-cli to v1.7.1 (#2281)
(cherry picked from commit d805f0cd2a93720726cc2284a384de3fb1f6abbd)
2025-11-16 12:37:36 +05:45
Mend Renovate 4b516ae4c5 Update dependency io.coil-kt.coil3:coil-bom to v3.3.0 (#2308)
(cherry picked from commit ce07259e8e8fb602d1028013aa0c27ee0ce705c9)
2025-11-16 12:37:36 +05:45
Mend Renovate 223251f868 Update lifecycle.version to v2.9.2 (#2283)
Update dependency androidx.lifecycle:lifecycle-process to v2.9.2

(cherry picked from commit 2f10e7beaa11bdddcb409a2fed9d5f0b97b8ade8)
2025-11-16 12:37:36 +05:45
Mend Renovate 9b7d6bace1 Update dependency io.mockk:mockk to v1.14.5 (#2282)
(cherry picked from commit 4ef8fb958824c500f1a2155951d12490fb55c709)
2025-11-16 12:37:36 +05:45
Mend Renovate c15b8b65e5 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.2.1 (#2293)
(cherry picked from commit 084e626669793cf637830827b5b03ae898c3814a)
2025-11-16 12:37:36 +05:45
Mend Renovate 4e9eaa5e81 Update dependency org.junit.jupiter:junit-jupiter to v5.13.4 (#2296)
(cherry picked from commit f93ccaaaa42fdf8091c89260803d9d518d06b9ac)
2025-11-16 12:37:36 +05:45
Mend Renovate 066e10246f Update dependency com.android.tools.build:gradle to v8.11.1 (#2277)
(cherry picked from commit 5585388e2d22879ac62e7fceaf26ae7f40cb8bde)
2025-11-16 12:37:36 +05:45
Matthias Ahouansou 3294dd6ca8 Use median to determine smart update interval (#2251)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit d60241690b373e42215761b4108e52a7655daaeb)
2025-11-16 12:37:36 +05:45
Mend Renovate 2315295985 Update dependency com.squareup.okio:okio to v3.15.0 (#2256)
(cherry picked from commit 84aa07b7f0b15bbe9f400596007f6714005118bb)
2025-11-16 12:37:36 +05:45
Mend Renovate 56ddf21107 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.1.0 (#2274)
(cherry picked from commit 0cc1224094069a321634cc3c1d5924b246ec7959)
2025-11-16 12:37:36 +05:45
Mend Renovate 3f47df21b8 Update serialization.version to v1.9.0 (#2252)
(cherry picked from commit a5a0d8330248174dc86d8f9a238a1c8489c291a6)
2025-11-16 12:37:36 +05:45
Mend Renovate 712407f524 Update dependency org.junit.jupiter:junit-jupiter to v5.13.3 (#2263)
(cherry picked from commit 1fde0275e313412af601e2ffdd79c7cf800678fb)
2025-11-16 12:37:36 +05:45
Mend Renovate 3a49a0a21a Update dependency gradle to v8.14.3 (#2264)
(cherry picked from commit a992f2d46758b741125c416c58a861a6f9ae9bc7)
2025-11-16 12:37:36 +05:45
Mend Renovate 04b3e13ba1 Update aboutlib.version to v12.2.4 (#2261)
(cherry picked from commit d8dd170d1b416cae793db10f9ee0568bfbdfadf9)
2025-11-16 12:37:36 +05:45
Mend Renovate 39bc2b49c0 Update moko to v0.25.0 (#2258)
(cherry picked from commit 6953090dabe3e90132c7c89a55e359a40257751e)
2025-11-16 12:37:36 +05:45
Mend Renovate 9765282640 Update plugin google-services to v4.4.3 (#2250)
(cherry picked from commit ab452a9945c6d34463e100ab63f614b1cf10b76c)
2025-11-16 12:37:36 +05:45
Mend Renovate 320a620afa Update dependency com.google.firebase:firebase-bom to v33.16.0 (#2248)
(cherry picked from commit 7dd595f16e2f484bd46f925c03b77675918208fc)
2025-11-16 12:37:36 +05:45
Mend Renovate ec2a720617 Update dependency com.android.tools.build:gradle to v8.11.0 (#2241)
(cherry picked from commit 6eb2a022f1e0b4a4da5714d69efe69bef584e179)
2025-11-16 12:37:36 +05:45
Mend Renovate 83d2ca7a54 Update dependency org.junit.jupiter:junit-jupiter to v5.13.2 (#2240)
(cherry picked from commit d61c66c286cf2c6d269809e2fbb5d601ed2d59e0)
2025-11-16 12:37:36 +05:45
Mend Renovate 891503c793 Update dependency io.mockk:mockk to v1.14.4 (#2232)
(cherry picked from commit 1e4ee1460893f7969a17f05b569f3673a42b77f0)
2025-11-16 12:37:36 +05:45
AntsyLich 43abc6c797 Fix background crash in mass migration screen
(cherry picked from commit 63943debc2fd4efa1a0418bbfefaea93a24b49fd)
2025-11-16 12:37:36 +05:45
Danny Wu f916e94a4c Add option to hide missing chapter count (#2108)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit d2c1ff6adf5543e5a593949704d41f2358252593)
2025-11-16 12:37:36 +05:45
AntsyLich fc9b2b6e1e Update manga without chapters even if restricted by source (#2224)
(cherry picked from commit b9e02e92bedf66aa2c4f7ed42b2c6899ed7fd013)
2025-11-16 12:37:36 +05:45
Mend Renovate 66bdd70485 Update dependency com.mohamedrejeb.richeditor:richeditor-compose to v1.0.0-rc13 (#2213)
(cherry picked from commit 103218681a6d9eda2f264e8f09724814dc8d5a96)
2025-11-16 12:37:36 +05:45
Mend Renovate 29024ea03a Update dependency androidx.compose:compose-bom to v2025.06.01 (#2220)
(cherry picked from commit 07136d3969daee988ddc129d091a6923d50f6a5a)
2025-11-16 12:37:36 +05:45
Mend Renovate 6af33905be Update dependency androidx.work:work-runtime to v2.10.2 (#2221)
(cherry picked from commit 4962deeb0cb5766d22ec0d3fb6e88ac50be327de)
2025-11-16 12:37:36 +05:45
Mend Renovate f97d918728 Update dependency com.squareup.okio:okio to v3.13.0 (#2201)
(cherry picked from commit cecf4596f9580806cf3208a518e5acac30becb50)
2025-11-16 12:37:36 +05:45
Mend Renovate 6a38e501d9 Update aboutlib.version to v12.2.3 (#2205)
(cherry picked from commit 0c77afbe038c2ad3db3f09d221f2a03fc2cec237)
2025-11-16 12:37:36 +05:45
Mend Renovate 8310733c4c Update sqlite to v2.5.2 (#2210)
(cherry picked from commit d126b84f954d6a35dd3cc1a29849fb0822714552)
2025-11-16 12:37:36 +05:45
AwkwardPeak7 28f0946877 Ensure app waits for Cloudflare challenge to complete before continuing (#2200)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 2df3382148c76d287943fc36e2f55bc16df1cd83)
2025-11-16 12:37:36 +05:45
jobobby04 e135a0dc71 Mass migration implementation (#2110)
There is no way to trigger mass migration at the moment. The functionality will be added in a follow up PR.

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit ee19050cc00b0a787af65b641624d07bb194e155)
2025-11-16 12:37:36 +05:45
AntsyLich 05a65773f0 Further tweak migration config screen sheet
(cherry picked from commit 019fc08da2392725c36e064ffadc4e645edafb63)
2025-11-16 12:37:36 +05:45
AntsyLich 8d794560a0 Add more migration config options and remove skipping option (#2193)
(cherry picked from commit 288f577a45a6835c34ad41caab95794f164b7a0b)
2025-11-16 12:37:36 +05:45
Mend Renovate 4341a98413 Update aboutlib.version to v12.2.2 (#2190)
(cherry picked from commit a47d4ebbdd04ce439c25a564afdb813dc80615d4)
2025-11-16 12:37:36 +05:45
Mend Renovate 24f5a36350 Update dependency sh.calvin.reorderable:reorderable to v2.5.1 (#2183)
(cherry picked from commit 89954e68e3265936718b5dd0664102f9f37b554a)
2025-11-16 12:37:36 +05:45
Mend Renovate f125db6973 Update dependency sh.calvin.reorderable:reorderable to v2.5.0 (#2178)
(cherry picked from commit c3b590cd3dfe70a5736411c6533913f2dadb17bc)
2025-11-16 12:37:36 +05:45
Mend Renovate 4ec969657e Update dependency com.google.firebase:firebase-bom to v33.15.0 (#2177)
(cherry picked from commit cb3c5e9c9c8465d3b1228264964b19ecda8bfc98)
2025-11-16 12:37:36 +05:45
Mend Renovate cb7e790086 Update plugin firebase-crashlytics to v3.0.4 (#2174)
(cherry picked from commit 7fa2834009013b01b06e2d95a80a4b4e09f37d17)
2025-11-16 12:37:36 +05:45
Mend Renovate 9012dafed4 Update dependency gradle to v8.14.2 (#2168)
(cherry picked from commit 95e3c22429adc98fb3ad7b941b5a8aa45322bfdc)
2025-11-16 12:37:36 +05:45
Mend Renovate 8b24d7eded Update okhttp monorepo to v5.0.0-alpha.16 (#2149)
(cherry picked from commit 8bd70342fcd1355c53fef62e597e2aa20229e123)
2025-11-16 12:37:36 +05:45
Mend Renovate dcc537accb Update sqldelight to v2.1.0 (#2119)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
(cherry picked from commit 92ec6b17a315c12838bf94bd214d216435e0f8f9)
2025-11-16 12:37:36 +05:45
Mend Renovate ab67775f13 Update dependency org.junit.jupiter:junit-jupiter to v5.13.1 (#1754)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>

(cherry picked from commit 591e9c13560f28d71598eb32648387d099ebc881)
2025-11-16 12:37:36 +05:45
Mend Renovate e105828e07 Update dependency androidx.compose:compose-bom to v2025.06.00 (#2175)
(cherry picked from commit 7fed9c2ccfed0179e50b4b8960fcd3eb6d229f53)
2025-11-16 12:37:36 +05:45
Mend Renovate 3c83edaad2 Update dependency com.squareup.logcat:logcat to v0.2.3 (#2126)
(cherry picked from commit ccb554c877e552433cc4fff9164207b02f0a17cf)
2025-11-16 12:37:36 +05:45
Mend Renovate b4f278e5fa Update dependency androidx.appcompat:appcompat to v1.7.1 (#2167)
(cherry picked from commit 5235713d83ab3643d30f2a7f087126d60dbb21f4)
2025-11-16 12:37:36 +05:45
Mend Renovate 29ca687a26 Update lifecycle.version to v2.9.1 (#2173)
(cherry picked from commit 4692010400d4fad2cc51901a355a0c15b865860c)
2025-11-16 12:37:36 +05:45
Mend Renovate ed975cae63 Update aboutlib.version to v12.2.1 (#2170)
(cherry picked from commit be528ba12b82d357170262d825b38033fbd83e91)
2025-11-16 12:37:36 +05:45
Mend Renovate 4fce0944b4 Update dependency me.zhanghai.android.libarchive:library to v1.1.6 (#2171)
(cherry picked from commit 405e536cbf4182e141d221795331c374236c9708)
2025-11-16 12:37:36 +05:45
AntsyLich 4a52898f08 Add option to skip migration config
(cherry picked from commit 8714653a2f3b1a1f536494bcefbe66e7a7bbb4f7)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt
2025-11-16 12:37:36 +05:45
AntsyLich 92b48319ed Cleanup migrate manga dialog and related code (#2156)
(cherry picked from commit 2b126f1ff56b63e470b48a04149e28c609f01148)

# Conflicts:
#	app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenDialogScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
2025-11-16 12:37:36 +05:45
AntsyLich 9e113d80f7 Fix no sources while migrating alongside UI and code cleanup (#2155)
(cherry picked from commit 5919f34fc96f254724bd3042ac2b91ac65912930)
2025-11-16 12:37:36 +05:45
Mend Renovate f960554cf8 Update dependency com.github.requery:sqlite-android to v3.49.0 (#2150)
(cherry picked from commit a4df33caf937a79ab1f70e33ad56e5b3899917e2)
2025-11-16 12:37:36 +05:45
Mend Renovate 0bdea705a5 Update aboutlib.version to v12.2.0 (#2152)
(cherry picked from commit 3580d2da6cc7f09eb3b6f1345c958a0ab0574856)
2025-11-16 12:37:36 +05:45
claymorwan 33361ea7f6 Add Catppuccin theme (#2117)
Mocha for dark and Latte for light, mauve accent

(cherry picked from commit 77eb55874278b7740d43b9144d9253a075cc593c)
2025-11-16 12:37:36 +05:45
Mend Renovate 717240f53c Update dependency com.android.tools.build:gradle to v8.10.1 (#2148)
(cherry picked from commit e1f6d143933ebf445adec5e1b87086551c2dca84)
2025-11-15 18:19:53 +05:45
AntsyLich 5156248a96 Add migration config screen to select and prioritize target sources (#2144)
(cherry picked from commit 2e180005a01f633ad7fafc5cfb3079f0bc858448)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrateMangaScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreenModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt
2025-11-15 18:19:53 +05:45
Mend Renovate e074df469e Update markdown to v0.35.0 (#2143)
(cherry picked from commit 0f59fc1dd42f770a0e7a9359a3eb8baebc826246)
2025-11-15 18:19:53 +05:45
Mend Renovate d1277ecb02 Update dependency com.diffplug.spotless:spotless-plugin-gradle to v7.0.4 (#2146)
(cherry picked from commit d1055475e23a3e20ef41b228056611be82e62515)
2025-11-15 18:19:53 +05:45
Mend Renovate 8bcc235490 Update dependency com.squareup.okio:okio to v3.12.0 (#2147)
(cherry picked from commit 32470657ddcb6976bb4c8f852ec19fe1d4dfb542)
2025-11-15 18:19:53 +05:45
Mend Renovate aaa7171c10 Update dependency me.zhanghai.android.libarchive:library to v1.1.5 (#2142)
(cherry picked from commit 158896cfa9314b89848d4d45b3042b80cefb127e)
2025-11-15 18:19:53 +05:45
Jobobby04 887311b440 Fix notification settings under api 26 2025-06-16 14:44:30 -04:00
Jobobby04 1c90aac059 Add id to staff-edges 2025-06-16 11:28:31 -04:00
Jobobby04 3ad9765dcf Remove +1 in Page Layout reader settings 2025-06-10 14:40:28 -04:00
Jobobby04 cc934607c8 SpotlessApply 2025-05-24 21:07:29 -04:00
Weblate (bot) 5074e68b9c Translations update from Hosted Weblate (#1442)
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/my/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy-plurals/vi/
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/de/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/es/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fil/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/fr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/id/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/it/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ja/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ne/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/ru/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/tr/
Translate-URL: https://hosted.weblate.org/projects/mihon/tachiyomisy/uk/
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: Alex Maryson Jr <akamar87@gmail.com>
Co-authored-by: B4LiN7 <87660017+B4LiN7@users.noreply.github.com>
Co-authored-by: Dexroneum <Rozhenkov69@gmail.com>
Co-authored-by: FateXBlood <fatexblood@gmail.com>
Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Co-authored-by: Hualiang <642615676@qq.com>
Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Co-authored-by: Kosťantin Horovij <lg096066587039@gmail.com>
Co-authored-by: Lapis (Bas77) <sebastianramli77@gmail.com>
Co-authored-by: LordTenebrous <danielmorenoperez836@gmail.com>
Co-authored-by: Mohamed kh <mohamedkhamekhami@gmail.com>
Co-authored-by: Sky children of the Light <tu25261@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: ZerOriSama <godarms2010@live.com>
Co-authored-by: akir45 <akkn0708@gmail.com>
Co-authored-by: edgole <test.backache009@aleeas.com>
Co-authored-by: fl0k1 <michele.carnova@gmail.com>
Co-authored-by: naikhon <naikhon5@gmail.com>
Co-authored-by: qaugji <asteeky9@gmail.com>
Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: Георгій Обушенков <heorhii.obushenkov@gmail.com>
Co-authored-by: ابومسلم <linuxmint1978@gmail.com>
2025-05-24 20:47:35 -04:00
多能豆 ade41f113d Fix E-Hentai Jump/Seek match for detailed date (#1450) 2025-05-24 20:44:55 -04:00
Jobobby04 95dc82594f More guards against edited data 2025-05-24 20:44:27 -04:00
NGB-Was-Taken 80e585fa91 Change log file extension to .txt (#1449) 2025-05-24 20:19:17 -04:00
Jobobby04 9f110f9db8 Bump version so migation actually runs 2025-05-24 20:18:14 -04:00
NGB-Was-Taken 71470b9e02 Remove the unused mark duplicate as read preference. (#1448)
* Remove the unused mark duplicate as read preference.

* Migrate the old preference to new preference
2025-05-24 20:16:44 -04:00
renovate[bot] 4fd24accac Update dependency net.zetetic:sqlcipher-android to v4.9.0 (#1447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-24 20:15:17 -04:00
NGB-Was-Taken 31312fecac Fixes screen staying on in library tab. (#1451) 2025-05-24 20:13:43 -04:00
Mend Renovate b80d057922 Update dependency androidx.compose:compose-bom to v2025.05.01 (#2133)
(cherry picked from commit 92b376d9af93da988b695c36c4775d5e6947c048)
2025-05-24 20:12:57 -04:00
Mend Renovate f01d8bc835 Update dependency gradle to v8.14.1 (#2138)
(cherry picked from commit 1a2f09a622017dc5b201eadc6acc667487cf3d4d)
2025-05-24 20:12:48 -04:00
Jobobby04 ddffe71a22 SpotlessApply 2025-05-24 20:12:38 -04:00
AntsyLich 649a19ec57 Fix content cut off in home screen
Closes #2141

(cherry picked from commit 209e982fe4f1da5d1d49cfbfdd178625ee3c70f4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/home/HomeScreen.kt
2025-05-24 20:10:18 -04:00
Mend Renovate e82fd99a09 Update plugin org.gradle.toolchains.foojay-resolver-convention to v1 (#2130)
(cherry picked from commit 0109102901f942502807e2a7a9f1a58f951c763f)
2025-05-24 20:09:57 -04:00
Mend Renovate 67a9b8e2a0 Update dependency com.pinterest.ktlint:ktlint-cli to v1.6.0 (#2129)
(cherry picked from commit 4117a5167470ae36b4172721b4e4d40373f2a3c6)
2025-05-24 20:09:51 -04:00
Mend Renovate 2000f947c3 Update xml.serialization.version to v0.91.1 (#2112)
(cherry picked from commit 4090a61d08964a8e82f943ac4eb81a52c110005f)
2025-05-24 20:09:45 -04:00
Jobobby04 f992ada0a8 SpotlessApply 2025-05-16 21:16:09 -04:00
Jobobby04 f876cdb037 Use MediaServer in Json for NHentai
Co-authored-by: 4521 <18432684+az4521@users.noreply.github.com>
2025-05-16 20:34:03 -04:00
Jobobby04 f919d42370 Hello Discord 2025-05-16 12:02:37 -04:00
Mend Renovate ab5ff00c39 Update kotlin monorepo to v2.1.21 (#2102)
(cherry picked from commit 625c85cbd68086d605553b2d5ab9cc9cc9460688)
2025-05-15 13:50:17 -04:00
Mend Renovate 422738af56 Update dependency org.jetbrains.kotlinx:kotlinx-collections-immutable to v0.4.0 (#2104)
(cherry picked from commit 737ceeea576074cffcd2e96933aceffdbcc0a03a)
2025-05-15 13:50:08 -04:00
Mend Renovate 81751fc9ce Update dependency io.coil-kt.coil3:coil-bom to v3.2.0 (#2101)
(cherry picked from commit 7933c9eeb7b28ecc2ae31fa337654a80f2371b85)
2025-05-15 13:49:58 -04:00
Jobobby04 9b6c5effc9 Minor refactors 2025-05-15 13:38:03 -04:00
425 changed files with 13184 additions and 6230 deletions
+6 -3
View File
@@ -7,7 +7,7 @@ indent_style = space
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.{xml,sq,sqm}] [*.{xml,sq,sqm,aidl}]
indent_size = 4 indent_size = 4
# noinspection EditorConfigKeyCorrectness # noinspection EditorConfigKeyCorrectness
@@ -23,9 +23,14 @@ ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ktlint_code_style = intellij_idea ktlint_code_style = intellij_idea
ktlint_function_naming_ignore_when_annotated_with = Composable ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_class-signature = disabled ktlint_standard_class-signature = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_discouraged-comment-location = disabled ktlint_standard_discouraged-comment-location = disabled
ktlint_standard_function-expression-body = disabled ktlint_standard_function-expression-body = disabled
ktlint_standard_function-signature = disabled ktlint_standard_function-signature = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_type-parameter-comment = disabled
ktlint_standard_blank-line-between-when-conditions = disabled
# SY # SY
ktlint_standard_filename = disabled ktlint_standard_filename = disabled
ktlint_standard_argument-list-wrapping = disabled ktlint_standard_argument-list-wrapping = disabled
@@ -33,8 +38,6 @@ ktlint_standard_function-naming = disabled
ktlint_standard_property-naming = disabled ktlint_standard_property-naming = disabled
ktlint_standard_multiline-expression-wrapping = disabled ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_string-template-indent = disabled ktlint_standard_string-template-indent = disabled
ktlint_standard_comment-wrapping = disabled
ktlint_standard_max-line-length = disabled ktlint_standard_max-line-length = disabled
ktlint_standard_type-argument-comment = disabled
ktlint_standard_value-argument-comment = disabled ktlint_standard_value-argument-comment = disabled
ktlint_standard_value-parameter-comment = disabled ktlint_standard_value-parameter-comment = disabled
+4 -4
View File
@@ -12,22 +12,22 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
java-version: 17 java-version: 17
distribution: temurin distribution: temurin
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v5
- name: Build app - name: Build app
run: ./gradlew spotlessCheck assembleDevDebug run: ./gradlew spotlessCheck assembleDevDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v6
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
+7 -30
View File
@@ -15,20 +15,20 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Android SDK - name: Setup Android SDK
run: | run: |
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3" ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
java-version: 17 java-version: 17
distribution: temurin distribution: temurin
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v5
# SY --> # SY -->
- name: Write google-services.json - name: Write google-services.json
@@ -71,24 +71,10 @@ jobs:
set -e set -e
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk TachiyomiSY.apk mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk TachiyomiSY.apk
sha=`sha256sum TachiyomiSY.apk | awk '{ print $1 }'`
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk mv app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk TachiyomiSY-arm64-v8a.apk
sha=`sha256sum TachiyomiSY-arm64-v8a.apk | awk '{ print $1 }'`
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk mv app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk TachiyomiSY-armeabi-v7a.apk
sha=`sha256sum TachiyomiSY-armeabi-v7a.apk | awk '{ print $1 }'`
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk mv app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk TachiyomiSY-x86.apk
sha=`sha256sum TachiyomiSY-x86.apk | awk '{ print $1 }'`
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk mv app/build/outputs/apk/standard/release/app-standard-x86_64-release-unsigned-signed.apk TachiyomiSY-x86_64.apk
sha=`sha256sum TachiyomiSY-x86_64.apk | awk '{ print $1 }'`
echo "APK_X86_64_SHA=$sha" >> $GITHUB_ENV
- name: Create release - name: Create release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
@@ -96,19 +82,10 @@ jobs:
tag_name: ${{ github.run_number }} tag_name: ${{ github.run_number }}
name: TachiyomiSY name: TachiyomiSY
body: | body: |
--- <!-->
> [!TIP]
### Checksums >
> ### If you are unsure which version to download then go with `TachiyomiSY.apk`
| Variant | SHA-256 |
| ------- | ------- |
| Universal | ${{ env.APK_UNIVERSAL_SHA }} |
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }} |
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }} |
| x86 | ${{ env.APK_X86_SHA }} |
| x86_64 | ${{ env.APK_X86_64_SHA }} |
## If you are unsure which version to choose then go with TachiyomiSY.apk
files: | files: |
TachiyomiSY.apk TachiyomiSY.apk
TachiyomiSY-arm64-v8a.apk TachiyomiSY-arm64-v8a.apk
+3 -3
View File
@@ -12,16 +12,16 @@ jobs:
steps: steps:
- name: Clone repo - name: Clone repo
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v4 uses: actions/setup-java@v5
with: with:
java-version: 17 java-version: 17
distribution: temurin distribution: temurin
- name: Set up gradle - name: Set up gradle
uses: gradle/actions/setup-gradle@v4 uses: gradle/actions/setup-gradle@v5
- name: Create Tag - name: Create Tag
run: | run: |
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
steps: steps:
- name: Check PR and Add Label - name: Check PR and Add Label
uses: actions/github-script@v7 uses: actions/github-script@v8
with: with:
script: | script: |
const prAuthor = context.payload.pull_request.user.login; const prAuthor = context.payload.pull_request.user.login;
+1 -1
View File
@@ -60,7 +60,7 @@ Additional features for some extensions, features include custom description, op
* Mangadex * Mangadex
* NHentai * NHentai
* Puruin * Puruin
* Tsumino * LANraragi
## Download ## Download
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest). Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
+6 -6
View File
@@ -31,7 +31,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi.sy" applicationId = "eu.kanade.tachiyomi.sy"
versionCode = 74 versionCode = 75
versionName = "1.12.0" versionName = "1.12.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
@@ -129,9 +129,9 @@ android {
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
buildConfig = true buildConfig = true
aidl = true
// Disable some unused things // Disable some unused things
aidl = false
renderScript = false renderScript = false
shaders = false shaders = false
} }
@@ -256,7 +256,6 @@ dependencies {
implementation(libs.directionalviewpager) { implementation(libs.directionalviewpager) {
exclude(group = "androidx.viewpager", module = "viewpager") exclude(group = "androidx.viewpager", module = "viewpager")
} }
implementation(libs.insetter)
implementation(libs.richeditor.compose) implementation(libs.richeditor.compose)
implementation(libs.aboutLibraries.compose) implementation(libs.aboutLibraries.compose)
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
@@ -278,8 +277,12 @@ dependencies {
// Shizuku // Shizuku
implementation(libs.bundles.shizuku) implementation(libs.bundles.shizuku)
// String similarity
implementation(libs.stringSimilarity)
// Tests // Tests
testImplementation(libs.bundles.test) testImplementation(libs.bundles.test)
testRuntimeOnly(libs.junit.platform.launcher)
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation(libs.leakcanary.android) // debugImplementation(libs.leakcanary.android)
@@ -288,9 +291,6 @@ dependencies {
testImplementation(kotlinx.coroutines.test) testImplementation(kotlinx.coroutines.test)
// SY --> // SY -->
// Text distance (EH)
implementation(sylibs.simularity)
// Firebase (EH) // Firebase (EH)
implementation(platform(libs.firebase.bom)) implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics) implementation(libs.firebase.analytics)
@@ -0,0 +1,7 @@
package mihon.app.shizuku;
interface IShellInterface {
void install(in AssetFileDescriptor apk) = 1;
void destroy() = 16777114;
}
@@ -38,6 +38,7 @@ import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import mihon.domain.extensionrepo.service.ExtensionRepoService import mihon.domain.extensionrepo.service.ExtensionRepoService
import mihon.domain.migration.usecases.MigrateMangaUseCase
import mihon.domain.upcoming.interactor.GetUpcomingManga import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl import tachiyomi.data.chapter.ChapterRepositoryImpl
@@ -133,6 +134,11 @@ class DomainModule : InjektModule {
addFactory { SetMangaCategories(get()) } addFactory { SetMangaCategories(get()) }
addFactory { GetExcludedScanlators(get()) } addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) } addFactory { SetExcludedScanlators(get()) }
addFactory {
MigrateMangaUseCase(
get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(),
)
}
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) } addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get(), get()) } addFactory { GetApplicationRelease(get(), get()) }
@@ -28,7 +28,6 @@ import tachiyomi.data.source.SavedSearchRepositoryImpl
import tachiyomi.domain.chapter.interactor.DeleteChapters import tachiyomi.domain.chapter.interactor.DeleteChapters
import tachiyomi.domain.chapter.interactor.GetChapterByUrl import tachiyomi.domain.chapter.interactor.GetChapterByUrl
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
import tachiyomi.domain.history.interactor.GetHistoryByMangaId
import tachiyomi.domain.manga.interactor.DeleteByMergeId import tachiyomi.domain.manga.interactor.DeleteByMergeId
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
import tachiyomi.domain.manga.interactor.DeleteMangaById import tachiyomi.domain.manga.interactor.DeleteMangaById
@@ -88,7 +87,6 @@ class SYDomainModule : InjektModule {
addFactory { DeleteChapters(get()) } addFactory { DeleteChapters(get()) }
addFactory { DeleteMangaById(get()) } addFactory { DeleteMangaById(get()) }
addFactory { FilterSerializer() } addFactory { FilterSerializer() }
addFactory { GetHistoryByMangaId(get()) }
addFactory { GetChapterByUrl(get()) } addFactory { GetChapterByUrl(get()) }
addFactory { GetSourceCategories(get()) } addFactory { GetSourceCategories(get()) }
addFactory { CreateSourceCategory(get()) } addFactory { CreateSourceCategory(get()) }
@@ -119,6 +119,7 @@ class SyncChaptersWithSource(
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(
dbChapter.name, dbChapter.name,
dbChapter.scanlator, dbChapter.scanlator,
dbChapter.url,
/* SY --> */ manga.ogTitle /* SY <-- */, /* SY --> */ manga.ogTitle /* SY <-- */,
manga.source, manga.source,
) )
@@ -126,12 +127,14 @@ class SyncChaptersWithSource(
if (shouldRenameChapter) { if (shouldRenameChapter) {
downloadManager.renameChapter(source, manga, dbChapter, chapter) downloadManager.renameChapter(source, manga, dbChapter, chapter)
} }
var toChangeChapter = dbChapter.copy( var toChangeChapter = dbChapter.copy(
name = chapter.name, name = chapter.name,
chapterNumber = chapter.chapterNumber, chapterNumber = chapter.chapterNumber,
scanlator = chapter.scanlator, scanlator = chapter.scanlator,
sourceOrder = chapter.sourceOrder, sourceOrder = chapter.sourceOrder,
) )
if (chapter.dateUpload != 0L) { if (chapter.dateUpload != 0L) {
toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload) toChangeChapter = toChangeChapter.copy(dateUpload = chapter.dateUpload)
} }
@@ -34,6 +34,7 @@ fun List<Chapter>.applyFilters(
val downloaded = downloadManager.isChapterDownloaded( val downloaded = downloadManager.isChapterDownloaded(
chapter.name, chapter.name,
chapter.scanlator, chapter.scanlator,
chapter.url,
/* SY --> */ manga.ogTitle /* SY <-- */, /* SY --> */ manga.ogTitle /* SY <-- */,
manga.source, manga.source,
) )
@@ -23,7 +23,7 @@ class GetPagePreviews(
return try { return try {
val pagePreviews = try { val pagePreviews = try {
pagePreviewCache.getPageListFromCache(manga, chapterIds, page) pagePreviewCache.getPageListFromCache(manga, chapterIds, page)
} catch (e: Exception) { } catch (_: Exception) {
source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also { source.getPagePreviewList(manga.toSManga(), chapters.map { it.toSChapter() }, page).also {
pagePreviewCache.putPageListToCache(manga, chapterIds, it) pagePreviewCache.putPageListToCache(manga, chapterIds, it)
} }
@@ -38,12 +38,14 @@ fun Manga.chaptersFiltered(): Boolean {
fun Manga.toSManga(): SManga = SManga.create().also { fun Manga.toSManga(): SManga = SManga.create().also {
it.url = url it.url = url
it.title = title // SY -->
it.artist = artist it.title = ogTitle
it.author = author it.artist = ogArtist
it.description = description it.author = ogAuthor
it.genre = genre.orEmpty().joinToString() it.description = ogDescription
it.status = status.toInt() it.genre = ogGenre.orEmpty().joinToString()
it.status = ogStatus.toInt()
// SY <--
it.thumbnail_url = thumbnailUrl it.thumbnail_url = thumbnailUrl
it.initialized = initialized it.initialized = initialized
} }
@@ -7,7 +7,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
@@ -2,16 +2,18 @@ package eu.kanade.domain.source.service
import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import mihon.domain.migration.models.MigrationFlag
import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getEnum import tachiyomi.core.common.preference.getEnum
import tachiyomi.core.common.preference.getLongArray
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
class SourcePreferences( class SourcePreferences(
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
) { ) {
fun sourceDisplayMode() = preferenceStore.getObject( fun sourceDisplayMode() = preferenceStore.getObjectFromString(
"pref_display_mode_catalogue", "pref_display_mode_catalogue",
LibraryDisplayMode.default, LibraryDisplayMode.default,
LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::serialize,
@@ -88,5 +90,33 @@ class SourcePreferences(
BANDWIDTH_HERO, BANDWIDTH_HERO,
WSRV_NL, WSRV_NL,
} }
fun allowLocalSourceHiddenFolders() = preferenceStore.getBoolean("allow_local_source_hidden_folders", false)
fun preferredMangaDexId() = preferenceStore.getString("preferred_mangaDex_id", "0")
fun mangadexSyncToLibraryIndexes() = preferenceStore.getStringSet(
"pref_mangadex_sync_to_library_indexes",
emptySet(),
)
fun recommendationSearchFlags() = preferenceStore.getInt("rec_search_flags", Int.MAX_VALUE)
// SY <-- // SY <--
fun migrationSources() = preferenceStore.getLongArray("migration_sources", emptyList())
fun migrationFlags() = preferenceStore.getObjectFromInt(
key = "migration_flags",
defaultValue = MigrationFlag.entries.toSet(),
serializer = { MigrationFlag.toBit(it) },
deserializer = { value: Int -> MigrationFlag.fromBit(value) },
)
fun migrationDeepSearchMode() = preferenceStore.getBoolean("migration_deep_search", false)
fun migrationPrioritizeByChapters() = preferenceStore.getBoolean("migration_prioritize_by_chapters", false)
fun migrationHideUnmatched() = preferenceStore.getBoolean("migration_hide_unmatched", false)
fun migrationHideWithoutUpdates() = preferenceStore.getBoolean("migration_hide_without_updates", false)
} }
@@ -42,6 +42,8 @@ class UiPreferences(
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC) fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
fun imagesInDescription() = preferenceStore.getBoolean("pref_render_images_description", true)
// SY --> // SY -->
fun expandFilters() = preferenceStore.getBoolean("eh_expand_filters", false) fun expandFilters() = preferenceStore.getBoolean("eh_expand_filters", false)
@@ -6,6 +6,7 @@ import tachiyomi.i18n.MR
enum class AppTheme(val titleRes: StringResource?) { enum class AppTheme(val titleRes: StringResource?) {
DEFAULT(MR.strings.label_default), DEFAULT(MR.strings.label_default),
MONET(MR.strings.theme_monet), MONET(MR.strings.theme_monet),
CATPPUCCIN(MR.strings.theme_catppuccin),
GREEN_APPLE(MR.strings.theme_greenapple), GREEN_APPLE(MR.strings.theme_greenapple),
LAVENDER(MR.strings.theme_lavender), LAVENDER(MR.strings.theme_lavender),
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk), MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
@@ -351,13 +351,17 @@ private fun ExtensionItemContent(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
) { ) {
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) { ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
var hasAlreadyShownAnElement by remember { mutableStateOf(false) }
if (extension is Extension.Installed && extension.lang.isNotEmpty()) { if (extension is Extension.Installed && extension.lang.isNotEmpty()) {
hasAlreadyShownAnElement = true
Text( Text(
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current), text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
) )
} }
if (extension.versionName.isNotEmpty()) { if (extension.versionName.isNotEmpty()) {
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
hasAlreadyShownAnElement = true
Text( Text(
text = extension.versionName, text = extension.versionName,
) )
@@ -373,6 +377,8 @@ private fun ExtensionItemContent(
else -> null else -> null
} }
if (warning != null) { if (warning != null) {
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
hasAlreadyShownAnElement = true
Text( Text(
text = stringResource(warning).uppercase(), text = stringResource(warning).uppercase(),
color = MaterialTheme.colorScheme.error, color = MaterialTheme.colorScheme.error,
@@ -380,6 +386,12 @@ private fun ExtensionItemContent(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} }
if (extension is Extension.Installed && !extension.isShared) {
if (hasAlreadyShownAnElement) DotSeparatorNoSpaceText()
Text(
text = stringResource(MR.strings.ext_installer_private),
)
}
if (!installStep.isCompleted()) { if (!installStep.isCompleted()) {
DotSeparatorNoSpaceText() DotSeparatorNoSpaceText()
@@ -41,6 +41,7 @@ fun GlobalSearchScreen(
navigateUp = navigateUp, navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery, onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch, onSearch = onSearch,
hideSourceFilter = false,
sourceFilter = state.sourceFilter, sourceFilter = state.sourceFilter,
onChangeSearchFilter = onChangeSearchFilter, onChangeSearchFilter = onChangeSearchFilter,
onlyShowHasResults = state.onlyShowHasResults, onlyShowHasResults = state.onlyShowHasResults,
@@ -1,84 +0,0 @@
package eu.kanade.presentation.browse
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.manga.components.BaseMangaListItem
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreenModel
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.FastScrollLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen
@Composable
fun MigrateMangaScreen(
navigateUp: () -> Unit,
title: String?,
state: MigrateMangaScreenModel.State,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = title,
navigateUp = navigateUp,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
if (state.isEmpty) {
EmptyScreen(
stringRes = MR.strings.empty_screen,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
}
MigrateMangaContent(
contentPadding = contentPadding,
state = state,
onClickItem = onClickItem,
onClickCover = onClickCover,
)
}
}
@Composable
private fun MigrateMangaContent(
contentPadding: PaddingValues,
state: MigrateMangaScreenModel.State,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
) {
FastScrollLazyColumn(
contentPadding = contentPadding,
) {
items(state.titles) { manga ->
MigrateMangaItem(
manga = manga,
onClickItem = onClickItem,
onClickCover = onClickCover,
)
}
}
}
@Composable
private fun MigrateMangaItem(
manga: Manga,
onClickItem: (Manga) -> Unit,
onClickCover: (Manga) -> Unit,
modifier: Modifier = Modifier,
) {
BaseMangaListItem(
modifier = modifier,
manga = manga,
onClickItem = { onClickItem(manga) },
onClickCover = { onClickCover(manga) },
)
}
@@ -32,6 +32,7 @@ fun MigrateSearchScreen(
navigateUp = navigateUp, navigateUp = navigateUp,
onChangeSearchQuery = onChangeSearchQuery, onChangeSearchQuery = onChangeSearchQuery,
onSearch = onSearch, onSearch = onSearch,
hideSourceFilter = true,
sourceFilter = state.sourceFilter, sourceFilter = state.sourceFilter,
onChangeSearchFilter = onChangeSearchFilter, onChangeSearchFilter = onChangeSearchFilter,
onlyShowHasResults = state.onlyShowHasResults, onlyShowHasResults = state.onlyShowHasResults,
@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowForward import androidx.compose.material.icons.automirrored.outlined.ArrowForward
import androidx.compose.material.icons.outlined.ArrowForward
import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.ContentCopy
import androidx.compose.material.icons.outlined.CopyAll import androidx.compose.material.icons.outlined.CopyAll
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
@@ -30,8 +30,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import com.gowtham.ratingbar.RatingBar import com.gowtham.ratingbar.ComposeStars
import com.gowtham.ratingbar.RatingBarConfig import com.gowtham.ratingbar.RatingBarStyle
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import exh.metadata.MetadataUtil import exh.metadata.MetadataUtil
@@ -222,17 +222,18 @@ fun BrowseSourceEHentaiListItem(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
horizontalAlignment = Alignment.Start, horizontalAlignment = Alignment.Start,
) { ) {
RatingBar( ComposeStars(
value = rating, value = rating,
onValueChange = {}, numOfStars = 5,
onRatingChanged = {}, size = 18.dp,
config = RatingBarConfig().apply { spaceBetween = 2.dp,
isIndicator(true) hideInactiveStars = false,
numStars(5) style = RatingBarStyle.Fill(
size(18.dp) activeColor = Color(0xFF005ED7),
activeColor(Color(0xFF005ED7)) inActiveColor = Color(0xE1E2ECFF),
inactiveColor(Color(0xE1E2ECFF)) ),
}, painterEmpty = null,
painterFilled = null,
) )
val color = genre?.first?.color val color = genre?.first?.color
val res = genre?.second val res = genre?.second
@@ -4,7 +4,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ViewList import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.automirrored.outlined.Help import androidx.compose.material.icons.automirrored.outlined.Help
import androidx.compose.material.icons.filled.ViewModule import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Help
import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Public
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
@@ -40,6 +40,7 @@ fun GlobalSearchToolbar(
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
hideSourceFilter: Boolean,
sourceFilter: SourceFilter, sourceFilter: SourceFilter,
onChangeSearchFilter: (SourceFilter) -> Unit, onChangeSearchFilter: (SourceFilter) -> Unit,
onlyShowHasResults: Boolean, onlyShowHasResults: Boolean,
@@ -73,38 +74,40 @@ fun GlobalSearchToolbar(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
// TODO: make this UX better; it only applies when triggering a new search // TODO: make this UX better; it only applies when triggering a new search
FilterChip( if (!hideSourceFilter) {
selected = sourceFilter == SourceFilter.PinnedOnly, FilterChip(
onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) }, selected = sourceFilter == SourceFilter.PinnedOnly,
leadingIcon = { onClick = { onChangeSearchFilter(SourceFilter.PinnedOnly) },
Icon( leadingIcon = {
imageVector = Icons.Outlined.PushPin, Icon(
contentDescription = null, imageVector = Icons.Outlined.PushPin,
modifier = Modifier contentDescription = null,
.size(FilterChipDefaults.IconSize), modifier = Modifier
) .size(FilterChipDefaults.IconSize),
}, )
label = { },
Text(text = stringResource(MR.strings.pinned_sources)) label = {
}, Text(text = stringResource(MR.strings.pinned_sources))
) },
FilterChip( )
selected = sourceFilter == SourceFilter.All, FilterChip(
onClick = { onChangeSearchFilter(SourceFilter.All) }, selected = sourceFilter == SourceFilter.All,
leadingIcon = { onClick = { onChangeSearchFilter(SourceFilter.All) },
Icon( leadingIcon = {
imageVector = Icons.Outlined.DoneAll, Icon(
contentDescription = null, imageVector = Icons.Outlined.DoneAll,
modifier = Modifier contentDescription = null,
.size(FilterChipDefaults.IconSize), modifier = Modifier
) .size(FilterChipDefaults.IconSize),
}, )
label = { },
Text(text = stringResource(MR.strings.all)) label = {
}, Text(text = stringResource(MR.strings.all))
) },
)
VerticalDivider() VerticalDivider()
}
FilterChip( FilterChip(
selected = onlyShowHasResults, selected = onlyShowHasResults,
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Label
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -9,7 +9,6 @@ import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.ArrowDropUp import androidx.compose.material.icons.outlined.ArrowDropUp
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Label
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -9,7 +9,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Label import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Label
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@@ -1,9 +1,9 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
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.window.Dialog import androidx.compose.ui.window.Dialog
@@ -32,9 +32,10 @@ fun NavigatorAdaptiveSheet(
) { ) {
ScreenTransition( ScreenTransition(
navigator = sheetNavigator, navigator = sheetNavigator,
enterTransition = { fadeIn(animationSpec = tween(220, delayMillis = 90)) }, transition = {
exitTransition = { fadeOut(animationSpec = tween(90)) }, fadeIn(animationSpec = tween(220, delayMillis = 90)) togetherWith
sizeTransform = { SizeTransform() }, fadeOut(animationSpec = tween(90))
},
) )
} }
@@ -22,7 +22,7 @@ fun relativeDateText(
Instant.ofEpochMilli(dateEpochMillis), Instant.ofEpochMilli(dateEpochMillis),
ZoneId.systemDefault(), ZoneId.systemDefault(),
) )
.takeIf { dateEpochMillis > 0L }, .takeIf { dateEpochMillis != 0L },
) )
} }
@@ -1,9 +1,11 @@
package eu.kanade.presentation.components package eu.kanade.presentation.components
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpOffset
import eu.kanade.presentation.manga.DownloadAction import eu.kanade.presentation.manga.DownloadAction
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -15,7 +17,41 @@ fun DownloadDropdownMenu(
expanded: Boolean, expanded: Boolean,
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit, onDownloadClicked: (DownloadAction) -> Unit,
offset: DpOffset? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) {
if (offset != null) {
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
offset = offset,
content = {
DownloadDropdownMenuItems(
onDismissRequest = onDismissRequest,
onDownloadClicked = onDownloadClicked,
)
},
)
} else {
DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
content = {
DownloadDropdownMenuItems(
onDismissRequest = onDismissRequest,
onDownloadClicked = onDownloadClicked,
)
},
)
}
}
@Composable
private fun ColumnScope.DownloadDropdownMenuItems(
onDismissRequest: () -> Unit,
onDownloadClicked: (DownloadAction) -> Unit,
) { ) {
val options = persistentListOf( val options = persistentListOf(
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1), DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
@@ -25,19 +61,13 @@ fun DownloadDropdownMenu(
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread), DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
) )
DropdownMenu( options.map { (downloadAction, string) ->
expanded = expanded, DropdownMenuItem(
onDismissRequest = onDismissRequest, text = { Text(text = string) },
modifier = modifier, onClick = {
) { onDownloadClicked(downloadAction)
options.map { (downloadAction, string) -> onDismissRequest()
DropdownMenuItem( },
text = { Text(text = string) }, )
onClick = {
onDownloadClicked(downloadAction)
onDismissRequest()
},
)
}
} }
} }
@@ -313,7 +313,7 @@ private fun ColumnScope.DisplayPage(
value = columns, value = columns,
valueRange = 0..10, valueRange = 0..10,
label = stringResource(MR.strings.pref_library_columns), label = stringResource(MR.strings.pref_library_columns),
valueText = if (columns > 0) { valueString = if (columns > 0) {
columns.toString() columns.toString()
} else { } else {
stringResource(MR.strings.label_auto) stringResource(MR.strings.label_auto)
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
@@ -15,7 +14,7 @@ internal fun LibraryComfortableGrid(
items: List<LibraryItem>, items: List<LibraryItem>,
columns: Int, columns: Int,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: Set<Long>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?, onClickContinueReading: ((LibraryManga) -> Unit)?,
@@ -35,7 +34,7 @@ internal fun LibraryComfortableGrid(
) { libraryItem -> ) { libraryItem ->
val manga = libraryItem.libraryManga.manga val manga = libraryItem.libraryManga.manga
MangaComfortableGridItem( MangaComfortableGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id }, isSelected = manga.id in selection,
title = manga.title, title = manga.title,
coverData = MangaCover( coverData = MangaCover(
mangaId = manga.id, mangaId = manga.id,
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
@@ -16,7 +15,7 @@ internal fun LibraryCompactGrid(
showTitle: Boolean, showTitle: Boolean,
columns: Int, columns: Int,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: Set<Long>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?, onClickContinueReading: ((LibraryManga) -> Unit)?,
@@ -36,7 +35,7 @@ internal fun LibraryCompactGrid(
) { libraryItem -> ) { libraryItem ->
val manga = libraryItem.libraryManga.manga val manga = libraryItem.libraryManga.manga
MangaCompactGridItem( MangaCompactGridItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id }, isSelected = manga.id in selection,
title = manga.title.takeIf { showTitle }, title = manga.title.takeIf { showTitle },
coverData = MangaCover( coverData = MangaCover(
mangaId = manga.id, mangaId = manga.id,
@@ -29,22 +29,22 @@ import kotlin.time.Duration.Companion.seconds
fun LibraryContent( fun LibraryContent(
categories: List<Category>, categories: List<Category>,
searchQuery: String?, searchQuery: String?,
selection: List<LibraryManga>, selection: Set<Long>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
currentPage: () -> Int, currentPage: Int,
hasActiveFilters: Boolean, hasActiveFilters: Boolean,
showPageTabs: Boolean, showPageTabs: Boolean,
onChangeCurrentPage: (Int) -> Unit, onChangeCurrentPage: (Int) -> Unit,
onMangaClicked: (Long) -> Unit, onClickManga: (Long) -> Unit,
onContinueReadingClicked: ((LibraryManga) -> Unit)?, onContinueReadingClicked: ((LibraryManga) -> Unit)?,
onToggleSelection: (LibraryManga) -> Unit, onToggleSelection: (Category, LibraryManga) -> Unit,
onToggleRangeSelection: (LibraryManga) -> Unit, onToggleRangeSelection: (Category, LibraryManga) -> Unit,
onRefresh: (Category?) -> Boolean, onRefresh: () -> Boolean,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
getNumberOfMangaForCategory: (Category) -> Int?, getItemCountForCategory: (Category) -> Int?,
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>, getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>, getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: (Int) -> List<LibraryItem>, getItemsForCategory: (Category) -> List<LibraryItem>,
) { ) {
Column( Column(
modifier = Modifier.padding( modifier = Modifier.padding(
@@ -53,15 +53,14 @@ fun LibraryContent(
end = contentPadding.calculateEndPadding(LocalLayoutDirection.current), end = contentPadding.calculateEndPadding(LocalLayoutDirection.current),
), ),
) { ) {
// SY --> val coercedCurrentPage = remember(categories, currentPage) { currentPage.coerceIn(0, categories.lastIndex) }
val coercedCurrentPage = remember(categories) { currentPage().coerceIn(0, categories.lastIndex) }
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
// SY <-- // SY <--
val pagerState = rememberPagerState(coercedCurrentPage) { categories.size }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) } var isRefreshing by remember(pagerState.currentPage) { mutableStateOf(false) }
if (showPageTabs && categories.size > 1) { if (showPageTabs && categories.isNotEmpty() && (categories.size > 1 || !categories.first().isSystemCategory)) {
LaunchedEffect(categories) { LaunchedEffect(categories) {
if (categories.size <= pagerState.currentPage) { if (categories.size <= pagerState.currentPage) {
pagerState.scrollToPage(categories.size - 1) pagerState.scrollToPage(categories.size - 1)
@@ -70,23 +69,20 @@ fun LibraryContent(
LibraryTabs( LibraryTabs(
categories = categories, categories = categories,
pagerState = pagerState, pagerState = pagerState,
getNumberOfMangaForCategory = getNumberOfMangaForCategory, getItemCountForCategory = getItemCountForCategory,
) { scope.launch { pagerState.animateScrollToPage(it) } } onTabItemClick = {
} scope.launch {
pagerState.animateScrollToPage(it)
val notSelectionMode = selection.isEmpty() }
val onClickManga = { manga: LibraryManga -> },
if (notSelectionMode) { )
onMangaClicked(manga.manga.id)
} else {
onToggleSelection(manga)
}
} }
PullRefresh( PullRefresh(
refreshing = isRefreshing, refreshing = isRefreshing,
enabled = selection.isEmpty(),
onRefresh = { onRefresh = {
val started = onRefresh(categories.getOrNull(currentPage()) ?: return@PullRefresh) val started = onRefresh()
if (!started) return@PullRefresh if (!started) return@PullRefresh
scope.launch { scope.launch {
// Fake refresh status but hide it after a second as it's a long running task // Fake refresh status but hide it after a second as it's a long running task
@@ -95,19 +91,25 @@ fun LibraryContent(
isRefreshing = false isRefreshing = false
} }
}, },
enabled = notSelectionMode,
) { ) {
LibraryPager( LibraryPager(
state = pagerState, state = pagerState,
contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()), contentPadding = PaddingValues(bottom = contentPadding.calculateBottomPadding()),
hasActiveFilters = hasActiveFilters, hasActiveFilters = hasActiveFilters,
selectedManga = selection, selection = selection,
searchQuery = searchQuery, searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,
getCategoryForPage = { page -> categories[page] },
getDisplayMode = getDisplayMode, getDisplayMode = getDisplayMode,
getColumnsForOrientation = getColumnsForOrientation, getColumnsForOrientation = getColumnsForOrientation,
getLibraryForPage = getLibraryForPage, getItemsForCategory = getItemsForCategory,
onClickManga = onClickManga, onClickManga = { category, manga ->
if (selection.isNotEmpty()) {
onToggleSelection(category, manga)
} else {
onClickManga(manga.manga.id)
}
},
onLongClickManga = onToggleRangeSelection, onLongClickManga = onToggleRangeSelection,
onClickContinueReading = onContinueReadingClicked, onClickContinueReading = onContinueReadingClicked,
) )
@@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.items
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.util.fastAny
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.MangaCover import tachiyomi.domain.manga.model.MangaCover
@@ -18,7 +17,7 @@ import tachiyomi.presentation.core.util.plus
internal fun LibraryList( internal fun LibraryList(
items: List<LibraryItem>, items: List<LibraryItem>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: Set<Long>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?, onClickContinueReading: ((LibraryManga) -> Unit)?,
@@ -45,7 +44,7 @@ internal fun LibraryList(
) { libraryItem -> ) { libraryItem ->
val manga = libraryItem.libraryManga.manga val manga = libraryItem.libraryManga.manga
MangaListItem( MangaListItem(
isSelected = selection.fastAny { it.id == libraryItem.libraryManga.id }, isSelected = manga.id in selection,
title = manga.title, title = manga.title,
coverData = MangaCover( coverData = MangaCover(
mangaId = manga.id, mangaId = manga.id,
@@ -20,6 +20,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.core.preference.PreferenceMutableState import eu.kanade.core.preference.PreferenceMutableState
import eu.kanade.tachiyomi.ui.library.LibraryItem import eu.kanade.tachiyomi.ui.library.LibraryItem
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.model.LibraryDisplayMode
import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -31,14 +32,15 @@ fun LibraryPager(
state: PagerState, state: PagerState,
contentPadding: PaddingValues, contentPadding: PaddingValues,
hasActiveFilters: Boolean, hasActiveFilters: Boolean,
selectedManga: List<LibraryManga>, selection: Set<Long>,
searchQuery: String?, searchQuery: String?,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
getCategoryForPage: (Int) -> Category,
getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>, getDisplayMode: (Int) -> PreferenceMutableState<LibraryDisplayMode>,
getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>, getColumnsForOrientation: (Boolean) -> PreferenceMutableState<Int>,
getLibraryForPage: (Int) -> List<LibraryItem>, getItemsForCategory: (Category) -> List<LibraryItem>,
onClickManga: (LibraryManga) -> Unit, onClickManga: (Category, LibraryManga) -> Unit,
onLongClickManga: (LibraryManga) -> Unit, onLongClickManga: (Category, LibraryManga) -> Unit,
onClickContinueReading: ((LibraryManga) -> Unit)?, onClickContinueReading: ((LibraryManga) -> Unit)?,
) { ) {
HorizontalPager( HorizontalPager(
@@ -50,9 +52,10 @@ fun LibraryPager(
// To make sure only one offscreen page is being composed // To make sure only one offscreen page is being composed
return@HorizontalPager return@HorizontalPager
} }
val library = getLibraryForPage(page) val category = getCategoryForPage(page)
val items = getItemsForCategory(category)
if (library.isEmpty()) { if (items.isEmpty()) {
LibraryPagerEmptyScreen( LibraryPagerEmptyScreen(
searchQuery = searchQuery, searchQuery = searchQuery,
hasActiveFilters = hasActiveFilters, hasActiveFilters = hasActiveFilters,
@@ -72,12 +75,15 @@ fun LibraryPager(
remember { mutableIntStateOf(0) } remember { mutableIntStateOf(0) }
} }
val onClickManga: (LibraryManga) -> Unit = { onClickManga(category, it) }
val onLongClickManga: (LibraryManga) -> Unit = { onLongClickManga(category, it) }
when (displayMode) { when (displayMode) {
LibraryDisplayMode.List -> { LibraryDisplayMode.List -> {
LibraryList( LibraryList(
items = library, items = items,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selection,
onClick = onClickManga, onClick = onClickManga,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading, onClickContinueReading = onClickContinueReading,
@@ -87,11 +93,11 @@ fun LibraryPager(
} }
LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> { LibraryDisplayMode.CompactGrid, LibraryDisplayMode.CoverOnlyGrid -> {
LibraryCompactGrid( LibraryCompactGrid(
items = library, items = items,
showTitle = displayMode is LibraryDisplayMode.CompactGrid, showTitle = displayMode is LibraryDisplayMode.CompactGrid,
columns = columns, columns = columns,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selection,
onClick = onClickManga, onClick = onClickManga,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading, onClickContinueReading = onClickContinueReading,
@@ -101,10 +107,10 @@ fun LibraryPager(
} }
LibraryDisplayMode.ComfortableGrid -> { LibraryDisplayMode.ComfortableGrid -> {
LibraryComfortableGrid( LibraryComfortableGrid(
items = library, items = items,
columns = columns, columns = columns,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selection,
onClick = onClickManga, onClick = onClickManga,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading, onClickContinueReading = onClickContinueReading,
@@ -18,13 +18,11 @@ import tachiyomi.presentation.core.components.material.TabText
internal fun LibraryTabs( internal fun LibraryTabs(
categories: List<Category>, categories: List<Category>,
pagerState: PagerState, pagerState: PagerState,
getNumberOfMangaForCategory: (Category) -> Int?, getItemCountForCategory: (Category) -> Int?,
onTabItemClick: (Int) -> Unit, onTabItemClick: (Int) -> Unit,
) { ) {
val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex) val currentPageIndex = pagerState.currentPage.coerceAtMost(categories.lastIndex)
Column( Column(modifier = Modifier.zIndex(2f)) {
modifier = Modifier.zIndex(1f),
) {
PrimaryScrollableTabRow( PrimaryScrollableTabRow(
selectedTabIndex = currentPageIndex, selectedTabIndex = currentPageIndex,
edgePadding = 0.dp, edgePadding = 0.dp,
@@ -39,7 +37,7 @@ internal fun LibraryTabs(
text = { text = {
TabText( TabText(
text = category.visualName, text = category.visualName,
badgeCount = getNumberOfMangaForCategory(category), badgeCount = getItemCountForCategory(category),
) )
}, },
unselectedContentColor = MaterialTheme.colorScheme.onSurface, unselectedContentColor = MaterialTheme.colorScheme.onSurface,
@@ -69,6 +69,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.Lanraragi
import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.source.online.english.EightMuses import eu.kanade.tachiyomi.source.online.english.EightMuses
@@ -87,6 +88,7 @@ import exh.source.isEhBasedManga
import exh.ui.metadata.adapters.EHentaiDescription import exh.ui.metadata.adapters.EHentaiDescription
import exh.ui.metadata.adapters.EightMusesDescription import exh.ui.metadata.adapters.EightMusesDescription
import exh.ui.metadata.adapters.HBrowseDescription import exh.ui.metadata.adapters.HBrowseDescription
import exh.ui.metadata.adapters.LanraragiDescription
import exh.ui.metadata.adapters.MangaDexDescription import exh.ui.metadata.adapters.MangaDexDescription
import exh.ui.metadata.adapters.NHentaiDescription import exh.ui.metadata.adapters.NHentaiDescription
import exh.ui.metadata.adapters.PururinDescription import exh.ui.metadata.adapters.PururinDescription
@@ -527,7 +529,7 @@ private fun MangaScreenSmallImpl(
// SY --> // SY -->
doSearch = onSearch, doSearch = onSearch,
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) { searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
SearchMetadataChips(state.meta, state.source, state.manga.genre) SearchMetadataChips(state.meta, state.source.id, state.manga.genre)
}, },
// SY <-- // SY <--
) )
@@ -822,7 +824,7 @@ fun MangaScreenLargeImpl(
// SY --> // SY -->
doSearch = onSearch, doSearch = onSearch,
searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) { searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) {
SearchMetadataChips(state.meta, state.source, state.manga.genre) SearchMetadataChips(state.meta, state.source.id, state.manga.genre)
}, },
// SY <-- // SY <--
) )
@@ -1089,6 +1091,9 @@ fun metadataDescription(source: Source): MetadataDescriptionComposable? {
is Tsumino -> { state, openMetadataViewer, _ -> is Tsumino -> { state, openMetadataViewer, _ ->
TsuminoDescription(state, openMetadataViewer) TsuminoDescription(state, openMetadataViewer)
} }
is Lanraragi -> { state, openMetadataViewer, _ ->
LanraragiDescription(state, openMetadataViewer)
}
else -> null else -> null
} }
} }
@@ -9,6 +9,7 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
@@ -30,7 +31,6 @@ import androidx.compose.material.icons.outlined.DoneAll
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.icons.outlined.SwapCalls
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -52,6 +52,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.DownloadDropdownMenu import eu.kanade.presentation.components.DownloadDropdownMenu
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
@@ -192,7 +193,7 @@ private fun RowScope.Button(
targetValue = if (toConfirm) 2f else 1f, targetValue = if (toConfirm) 2f else 1f,
label = "weight", label = "weight",
) )
Column( Box(
modifier = Modifier modifier = Modifier
.size(48.dp) .size(48.dp)
.weight(animatedWeight) .weight(animatedWeight)
@@ -202,24 +203,28 @@ private fun RowScope.Button(
onLongClick = onLongClick, onLongClick = onLongClick,
onClick = onClick, onClick = onClick,
), ),
verticalArrangement = Arrangement.Center, contentAlignment = Alignment.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Icon( Column(
imageVector = icon, verticalArrangement = Arrangement.Center,
contentDescription = title, horizontalAlignment = Alignment.CenterHorizontally,
)
AnimatedVisibility(
visible = toConfirm,
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
) { ) {
Text( Icon(
text = title, imageVector = icon,
overflow = TextOverflow.Visible, contentDescription = title,
maxLines = 1,
style = MaterialTheme.typography.labelSmall,
) )
AnimatedVisibility(
visible = toConfirm,
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
) {
Text(
text = title,
overflow = TextOverflow.Visible,
maxLines = 1,
style = MaterialTheme.typography.labelSmall,
)
}
} }
content?.invoke() content?.invoke()
} }
@@ -233,9 +238,9 @@ fun LibraryBottomActionMenu(
onMarkAsUnreadClicked: () -> Unit, onMarkAsUnreadClicked: () -> Unit,
onDownloadClicked: ((DownloadAction) -> Unit)?, onDownloadClicked: ((DownloadAction) -> Unit)?,
onDeleteClicked: () -> Unit, onDeleteClicked: () -> Unit,
onMigrateClicked: (() -> Unit)?,
// SY --> // SY -->
onClickCleanTitles: (() -> Unit)?, onClickCleanTitles: (() -> Unit)?,
onClickMigrate: (() -> Unit)?,
onClickCollectRecommendations: (() -> Unit)?, onClickCollectRecommendations: (() -> Unit)?,
onClickAddToMangaDex: (() -> Unit)?, onClickAddToMangaDex: (() -> Unit)?,
onClickResetInfo: (() -> Unit)?, onClickResetInfo: (() -> Unit)?,
@@ -254,12 +259,11 @@ fun LibraryBottomActionMenu(
color = MaterialTheme.colorScheme.surfaceContainerHigh, color = MaterialTheme.colorScheme.surfaceContainerHigh,
) { ) {
val haptic = LocalHapticFeedback.current val haptic = LocalHapticFeedback.current
val confirm = val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
remember { mutableStateListOf(false, false, false, false, false /* SY --> */, false /* SY <-- */) }
var resetJob: Job? = remember { null } var resetJob: Job? = remember { null }
val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex } (0..5).forEach { i -> confirm[i] = i == toConfirmIndex }
resetJob?.cancel() resetJob?.cancel()
resetJob = scope.launch { resetJob = scope.launch {
delay(1.seconds) delay(1.seconds)
@@ -270,7 +274,8 @@ fun LibraryBottomActionMenu(
val showOverflow = onClickCleanTitles != null || val showOverflow = onClickCleanTitles != null ||
onClickAddToMangaDex != null || onClickAddToMangaDex != null ||
onClickResetInfo != null || onClickResetInfo != null ||
onClickCollectRecommendations != null onClickCollectRecommendations != null ||
onMigrateClicked != null
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val moveMarkPrev = remember { !configuration.isTabletUi() } val moveMarkPrev = remember { !configuration.isTabletUi() }
var overFlowOpen by remember { mutableStateOf(false) } var overFlowOpen by remember { mutableStateOf(false) }
@@ -299,11 +304,11 @@ fun LibraryBottomActionMenu(
onLongClick = { onLongClickItem(3) }, onLongClick = { onLongClickItem(3) },
onClick = { downloadExpanded = !downloadExpanded }, onClick = { downloadExpanded = !downloadExpanded },
) { ) {
val onDismissRequest = { downloadExpanded = false }
DownloadDropdownMenu( DownloadDropdownMenu(
expanded = downloadExpanded, expanded = downloadExpanded,
onDismissRequest = onDismissRequest, onDismissRequest = { downloadExpanded = false },
onDownloadClicked = onDownloadClicked, onDownloadClicked = onDownloadClicked,
offset = BottomBarMenuDpOffset,
) )
} }
} }
@@ -355,10 +360,10 @@ fun LibraryBottomActionMenu(
onClick = onClickCleanTitles, onClick = onClickCleanTitles,
) )
} }
if (onClickMigrate != null) { if (onMigrateClicked != null) {
DropdownMenuItem( DropdownMenuItem(
text = { Text(stringResource(MR.strings.migrate)) }, text = { Text(stringResource(MR.strings.migrate)) },
onClick = onClickMigrate, onClick = onMigrateClicked,
) )
} }
if (onClickCollectRecommendations != null) { if (onClickCollectRecommendations != null) {
@@ -388,18 +393,11 @@ fun LibraryBottomActionMenu(
onLongClick = { onLongClickItem(2) }, onLongClick = { onLongClickItem(2) },
onClick = onMarkAsUnreadClicked, onClick = onMarkAsUnreadClicked,
) )
if (onClickMigrate != null) {
Button(
title = stringResource(MR.strings.migrate),
icon = Icons.Outlined.SwapCalls,
toConfirm = confirm[5],
onLongClick = { onLongClickItem(5) },
onClick = onClickMigrate,
)
}
} }
// SY <-- // SY <--
} }
} }
} }
} }
private val BottomBarMenuDpOffset = DpOffset(0.dp, 0.dp)
@@ -3,9 +3,6 @@ package eu.kanade.presentation.manga.components
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.os.Build import android.os.Build
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@@ -28,18 +25,15 @@ import androidx.compose.material3.SnackbarHostState
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 androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import androidx.compose.ui.viewinterop.AndroidView 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
@@ -56,14 +50,11 @@ import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.manga.EditCoverAction import eu.kanade.presentation.manga.EditCoverAction
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import soup.compose.material.motion.MotionConstants
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.PredictiveBack
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import kotlin.coroutines.cancellation.CancellationException
@Composable @Composable
fun MangaCoverDialog( fun MangaCoverDialog(
@@ -162,32 +153,10 @@ fun MangaCoverDialog(
val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() } val statusBarPaddingPx = with(LocalDensity.current) { contentPadding.calculateTopPadding().roundToPx() }
val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() } val bottomPaddingPx = with(LocalDensity.current) { contentPadding.calculateBottomPadding().roundToPx() }
var scale by remember { mutableFloatStateOf(1f) }
PredictiveBackHandler { progress ->
try {
progress.collect { backEvent ->
scale = lerp(1f, 0.8f, PredictiveBack.transform(backEvent.progress))
}
onDismissRequest()
} catch (e: CancellationException) {
animate(
initialValue = scale,
targetValue = 1f,
animationSpec = tween(durationMillis = MotionConstants.DefaultMotionDuration),
) { value, _ ->
scale = value
}
}
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.clickableNoIndication(onClick = onDismissRequest) .clickableNoIndication(onClick = onDismissRequest),
.graphicsLayer {
scaleX = scale
scaleY = scale
},
) { ) {
AndroidView( AndroidView(
factory = { factory = {
@@ -23,6 +23,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.CallMerge import androidx.compose.material.icons.automirrored.outlined.CallMerge
@@ -33,7 +34,6 @@ import androidx.compose.material.icons.filled.PersonOutline
import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.AttachMoney import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.outlined.Block import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CallMerge
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.DoneAll import androidx.compose.material.icons.outlined.DoneAll
@@ -52,6 +52,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
@@ -67,9 +68,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -79,10 +84,15 @@ import coil3.request.ImageRequest
import coil3.request.crossfade import coil3.request.crossfade
import com.mikepenz.markdown.model.markdownAnnotator import com.mikepenz.markdown.model.markdownAnnotator
import com.mikepenz.markdown.model.markdownAnnotatorConfig import com.mikepenz.markdown.model.markdownAnnotatorConfig
import com.mikepenz.markdown.utils.getUnescapedTextInNode
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.findChildOfType
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
@@ -93,6 +103,8 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -593,8 +605,33 @@ private fun ColumnScope.MangaContentInfo(
} }
} }
private val descriptionAnnotator = markdownAnnotator( private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = markdownAnnotator(
annotate = { content, child -> annotate = { content, child ->
if (!loadImages && child.type == MarkdownElementTypes.IMAGE) {
val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK)
val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION)
?.getUnescapedTextInNode(content)
?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK)
?.findChildOfType(MarkdownTokenTypes.AUTOLINK)
?.getUnescapedTextInNode(content)
?: return@markdownAnnotator false
val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE)
?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT)
val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT)
?.getUnescapedTextInNode(content).orEmpty()
withLink(LinkAnnotation.Url(url = url)) {
pushStyle(linkStyle)
appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG)
append(altText)
pop()
}
return@markdownAnnotator true
}
if (child.type in DISALLOWED_MARKDOWN_TYPES) { if (child.type in DISALLOWED_MARKDOWN_TYPES) {
append(content.substring(child.startOffset, child.endOffset)) append(content.substring(child.startOffset, child.endOffset))
return@markdownAnnotator true return@markdownAnnotator true
@@ -615,10 +652,13 @@ private fun MangaSummary(
onEditNotesClicked: () -> Unit, onEditNotesClicked: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val preferences = remember { Injekt.get<UiPreferences>() }
val loadImages = remember { preferences.imagesInDescription().get() }
val animProgress by animateFloatAsState( val animProgress by animateFloatAsState(
targetValue = if (expanded) 1f else 0f, targetValue = if (expanded) 1f else 0f,
label = "summary", label = "summary",
) )
var infoHeight by remember { mutableIntStateOf(0) }
Layout( Layout(
modifier = modifier.clipToBounds(), modifier = modifier.clipToBounds(),
contents = listOf( contents = listOf(
@@ -631,21 +671,11 @@ private fun MangaSummary(
) )
}, },
{ {
Column { Column(
MangaNotesSection( modifier = Modifier.onSizeChanged { size ->
content = notes, infoHeight = size.height
expanded = true, },
onEditNotes = onEditNotesClicked, ) {
)
MarkdownRender(
content = description,
modifier = Modifier.secondaryItemAlpha(),
annotator = descriptionAnnotator,
)
}
},
{
Column {
MangaNotesSection( MangaNotesSection(
content = notes, content = notes,
expanded = expanded, expanded = expanded,
@@ -655,7 +685,11 @@ private fun MangaSummary(
MarkdownRender( MarkdownRender(
content = description, content = description,
modifier = Modifier.secondaryItemAlpha(), modifier = Modifier.secondaryItemAlpha(),
annotator = descriptionAnnotator, annotator = descriptionAnnotator(
loadImages = loadImages,
linkStyle = getMarkdownLinkStyle().toSpanStyle(),
),
loadImages = loadImages,
) )
} }
} }
@@ -678,14 +712,11 @@ private fun MangaSummary(
} }
}, },
), ),
) { (shrunk, expanded, actual, scrim), constraints -> ) { (shrunk, actual, scrim), constraints ->
val shrunkHeight = shrunk.single() val shrunkHeight = shrunk.single()
.measure(constraints) .measure(constraints)
.height .height
val expandedHeight = expanded.single() val heightDelta = infoHeight - shrunkHeight
.measure(constraints)
.height
val heightDelta = expandedHeight - shrunkHeight
val scrimHeight = 24.dp.roundToPx() val scrimHeight = 24.dp.roundToPx()
val actualPlaceable = actual.single() val actualPlaceable = actual.single()
@@ -4,14 +4,19 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Image
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.FirstBaseline import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
@@ -32,11 +37,13 @@ import com.mikepenz.markdown.compose.elements.MarkdownTableRow
import com.mikepenz.markdown.compose.elements.MarkdownText import com.mikepenz.markdown.compose.elements.MarkdownText
import com.mikepenz.markdown.compose.elements.listDepth import com.mikepenz.markdown.compose.elements.listDepth
import com.mikepenz.markdown.model.DefaultMarkdownColors import com.mikepenz.markdown.model.DefaultMarkdownColors
import com.mikepenz.markdown.model.DefaultMarkdownInlineContent
import com.mikepenz.markdown.model.DefaultMarkdownTypography import com.mikepenz.markdown.model.DefaultMarkdownTypography
import com.mikepenz.markdown.model.MarkdownAnnotator import com.mikepenz.markdown.model.MarkdownAnnotator
import com.mikepenz.markdown.model.MarkdownColors import com.mikepenz.markdown.model.MarkdownColors
import com.mikepenz.markdown.model.MarkdownPadding import com.mikepenz.markdown.model.MarkdownPadding
import com.mikepenz.markdown.model.MarkdownTypography import com.mikepenz.markdown.model.MarkdownTypography
import com.mikepenz.markdown.model.NoOpImageTransformerImpl
import com.mikepenz.markdown.model.markdownAnnotator import com.mikepenz.markdown.model.markdownAnnotator
import com.mikepenz.markdown.model.rememberMarkdownState import com.mikepenz.markdown.model.rememberMarkdownState
import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG import org.intellij.markdown.MarkdownTokenTypes.Companion.HTML_TAG
@@ -59,12 +66,15 @@ import org.intellij.markdown.parser.markerblocks.providers.ListMarkerProvider
import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider import org.intellij.markdown.parser.markerblocks.providers.SetextHeaderProvider
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
const val MARKDOWN_INLINE_IMAGE_TAG = "MARKDOWN_INLINE_IMAGE"
@Composable @Composable
fun MarkdownRender( fun MarkdownRender(
content: String, content: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor, flavour: MarkdownFlavourDescriptor = SimpleMarkdownFlavourDescriptor,
annotator: MarkdownAnnotator = remember { markdownAnnotator() }, annotator: MarkdownAnnotator = remember { markdownAnnotator() },
loadImages: Boolean = true,
) { ) {
Markdown( Markdown(
markdownState = rememberMarkdownState( markdownState = rememberMarkdownState(
@@ -77,7 +87,10 @@ fun MarkdownRender(
typography = getMarkdownTypography(), typography = getMarkdownTypography(),
padding = markdownPadding, padding = markdownPadding,
components = markdownComponents, components = markdownComponents,
imageTransformer = Coil3ImageTransformerImpl, imageTransformer = remember(loadImages) {
if (loadImages) Coil3ImageTransformerImpl else NoOpImageTransformerImpl()
},
inlineContent = getMarkdownInlineContent(),
modifier = modifier, modifier = modifier,
) )
} }
@@ -88,24 +101,24 @@ private fun getMarkdownColors(): MarkdownColors {
val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f) val codeBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
return DefaultMarkdownColors( return DefaultMarkdownColors(
text = MaterialTheme.colorScheme.onSurface, text = MaterialTheme.colorScheme.onSurface,
codeText = Color.Unspecified,
inlineCodeText = Color.Unspecified,
linkText = Color.Unspecified,
codeBackground = codeBackground, codeBackground = codeBackground,
inlineCodeBackground = codeBackground, inlineCodeBackground = codeBackground,
dividerColor = MaterialTheme.colorScheme.outlineVariant, dividerColor = MaterialTheme.colorScheme.outlineVariant,
tableText = Color.Unspecified,
tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f), tableBackground = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.05f),
) )
} }
@Composable
@ReadOnlyComposable
fun getMarkdownLinkStyle() = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
)
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
private fun getMarkdownTypography(): MarkdownTypography { private fun getMarkdownTypography(): MarkdownTypography {
val link = MaterialTheme.typography.bodyMedium.copy( val link = getMarkdownLinkStyle()
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold,
)
return DefaultMarkdownTypography( return DefaultMarkdownTypography(
h1 = MaterialTheme.typography.headlineMedium, h1 = MaterialTheme.typography.headlineMedium,
h2 = MaterialTheme.typography.headlineSmall, h2 = MaterialTheme.typography.headlineSmall,
@@ -121,7 +134,6 @@ private fun getMarkdownTypography(): MarkdownTypography {
ordered = MaterialTheme.typography.bodyMedium, ordered = MaterialTheme.typography.bodyMedium,
bullet = MaterialTheme.typography.bodyMedium, bullet = MaterialTheme.typography.bodyMedium,
list = MaterialTheme.typography.bodyMedium, list = MaterialTheme.typography.bodyMedium,
link = link,
textLink = TextLinkStyles(style = link.toSpanStyle()), textLink = TextLinkStyles(style = link.toSpanStyle()),
table = MaterialTheme.typography.bodyMedium, table = MaterialTheme.typography.bodyMedium,
) )
@@ -216,6 +228,27 @@ private val markdownComponents = markdownComponents(
}, },
) )
@Composable
@ReadOnlyComposable
private fun getMarkdownInlineContent() = DefaultMarkdownInlineContent(
inlineContent = mapOf(
MARKDOWN_INLINE_IMAGE_TAG to InlineTextContent(
placeholder = Placeholder(
width = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
height = MaterialTheme.typography.bodyMedium.fontSize * 1.25,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
),
children = {
Icon(
imageVector = Icons.Outlined.Image,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
},
),
),
)
private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() { private object SimpleMarkdownFlavourDescriptor : CommonMarkFlavourDescriptor() {
override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory override val markerProcessorFactory: MarkerProcessorFactory = SimpleMarkdownProcessFactory
} }
@@ -17,7 +17,6 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -25,8 +24,6 @@ import eu.kanade.presentation.components.ChipBorder
import eu.kanade.presentation.components.SuggestionChip import eu.kanade.presentation.components.SuggestionChip
import eu.kanade.presentation.components.SuggestionChipDefaults import eu.kanade.presentation.components.SuggestionChipDefaults
import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.theme.TachiyomiPreviewTheme
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.all.EHentai
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag import exh.metadata.metadata.base.RaisedTag
@@ -49,7 +46,7 @@ value class SearchMetadataChips(
val tags: Map<String, List<DisplayTag>>, val tags: Map<String, List<DisplayTag>>,
) { ) {
companion object { companion object {
operator fun invoke(meta: RaisedSearchMetadata?, source: Source, tags: List<String>?): SearchMetadataChips? { operator fun invoke(meta: RaisedSearchMetadata?, sourceId: Long, tags: List<String>?): SearchMetadataChips? {
return if (meta != null) { return if (meta != null) {
SearchMetadataChips( SearchMetadataChips(
meta.tags meta.tags
@@ -59,11 +56,11 @@ value class SearchMetadataChips(
namespace = it.namespace, namespace = it.namespace,
text = it.name, text = it.name,
search = if (!it.namespace.isNullOrEmpty()) { search = if (!it.namespace.isNullOrEmpty()) {
SourceTagsUtil.getWrappedTag(source.id, namespace = it.namespace, tag = it.name) SourceTagsUtil.getWrappedTag(sourceId, namespace = it.namespace, tag = it.name)
} else { } else {
SourceTagsUtil.getWrappedTag(source.id, fullTag = it.name) SourceTagsUtil.getWrappedTag(sourceId, fullTag = it.name)
} ?: it.name, } ?: it.name,
border = if (source.id == EXH_SOURCE_ID || source.id == EH_SOURCE_ID) { border = if (sourceId == EXH_SOURCE_ID || sourceId == EH_SOURCE_ID) {
when (it.type) { when (it.type) {
EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2 EHentaiSearchMetadata.TAG_TYPE_NORMAL -> 2
EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1 EHentaiSearchMetadata.TAG_TYPE_LIGHT -> 1
@@ -178,7 +175,6 @@ fun TagsChip(
fun NamespaceTagsPreview() { fun NamespaceTagsPreview() {
TachiyomiPreviewTheme { TachiyomiPreviewTheme {
Surface { Surface {
val context = LocalContext.current
NamespaceTags( NamespaceTags(
tags = remember { tags = remember {
EHentaiSearchMetadata().apply { EHentaiSearchMetadata().apply {
@@ -216,7 +212,7 @@ fun NamespaceTagsPreview() {
), ),
), ),
) )
}.let { SearchMetadataChips(it, EHentai(EXH_SOURCE_ID, true, context), emptyList()) }!! }.let { SearchMetadataChips(it, EXH_SOURCE_ID, emptyList()) }!!
}, },
onClick = {}, onClick = {},
) )
@@ -7,12 +7,9 @@ import androidx.compose.material.icons.automirrored.outlined.Label
import androidx.compose.material.icons.automirrored.outlined.PlaylistAdd import androidx.compose.material.icons.automirrored.outlined.PlaylistAdd
import androidx.compose.material.icons.outlined.CloudOff import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.GetApp import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.History import androidx.compose.material.icons.outlined.History
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Label
import androidx.compose.material.icons.outlined.NewReleases import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.outlined.PlaylistAdd
import androidx.compose.material.icons.outlined.QueryStats import androidx.compose.material.icons.outlined.QueryStats
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Storage import androidx.compose.material.icons.outlined.Storage
@@ -15,13 +15,13 @@ sealed class Preference {
abstract val title: String abstract val title: String
abstract val enabled: Boolean abstract val enabled: Boolean
sealed class PreferenceItem<T> : Preference() { sealed class PreferenceItem<T, R> : Preference() {
// SY --> // SY -->
abstract val subtitle: CharSequence? abstract val subtitle: CharSequence?
// SY <-- // SY <--
abstract val icon: ImageVector? abstract val icon: ImageVector?
abstract val onValueChanged: suspend (value: T) -> Boolean abstract val onValueChanged: suspend (value: T) -> R
/** /**
* A basic [PreferenceItem] that only displays texts. * A basic [PreferenceItem] that only displays texts.
@@ -31,9 +31,9 @@ sealed class Preference {
override val subtitle: CharSequence? = null, override val subtitle: CharSequence? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
val onClick: (() -> Unit)? = null, val onClick: (() -> Unit)? = null,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
/** /**
@@ -45,7 +45,7 @@ sealed class Preference {
override val subtitle: CharSequence? = null, override val subtitle: CharSequence? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Boolean) -> Boolean = { true }, override val onValueChanged: suspend (value: Boolean) -> Boolean = { true },
) : PreferenceItem<Boolean>() { ) : PreferenceItem<Boolean, Boolean>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -55,12 +55,13 @@ sealed class Preference {
data class SliderPreference( data class SliderPreference(
val value: Int, val value: Int,
override val title: String, override val title: String,
override val subtitle: String? = null,
val valueString: String? = null,
val valueRange: IntProgression = 0..1, val valueRange: IntProgression = 0..1,
@IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 }, @IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 },
override val subtitle: String? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Int) -> Boolean = { true }, override val onValueChanged: suspend (value: Int) -> Unit = {},
) : PreferenceItem<Int>() { ) : PreferenceItem<Int, Unit>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -78,7 +79,7 @@ sealed class Preference {
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: T) -> Boolean = { true }, override val onValueChanged: suspend (value: T) -> Boolean = { true },
) : PreferenceItem<T>() { ) : PreferenceItem<T, Boolean>() {
internal fun internalSet(value: Any) = preference.set(value as T) internal fun internalSet(value: Any) = preference.set(value as T)
internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T) internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T)
@@ -99,8 +100,8 @@ sealed class Preference {
{ v, e -> subtitle?.format(e[v]) }, { v, e -> subtitle?.format(e[v]) },
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: String) -> Boolean = { true }, override val onValueChanged: suspend (value: String) -> Unit = {},
) : PreferenceItem<String>() ) : PreferenceItem<String, Unit>()
/** /**
* A [PreferenceItem] that displays a list of entries as a dialog. * A [PreferenceItem] that displays a list of entries as a dialog.
@@ -124,7 +125,7 @@ sealed class Preference {
override val icon: ImageVector? = null, override val icon: ImageVector? = null,
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true }, override val onValueChanged: suspend (value: Set<String>) -> Boolean = { true },
) : PreferenceItem<Set<String>>() ) : PreferenceItem<Set<String>, Boolean>()
/** /**
* A [PreferenceItem] that shows a EditText in the dialog. * A [PreferenceItem] that shows a EditText in the dialog.
@@ -135,7 +136,7 @@ sealed class Preference {
override val subtitle: String? = "%s", override val subtitle: String? = "%s",
override val enabled: Boolean = true, override val enabled: Boolean = true,
override val onValueChanged: suspend (value: String) -> Boolean = { true }, override val onValueChanged: suspend (value: String) -> Boolean = { true },
) : PreferenceItem<String>() { ) : PreferenceItem<String, Boolean>() {
override val icon: ImageVector? = null override val icon: ImageVector? = null
} }
@@ -146,31 +147,31 @@ sealed class Preference {
val tracker: Tracker, val tracker: Tracker,
val login: () -> Unit, val login: () -> Unit,
val logout: () -> Unit, val logout: () -> Unit,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val title: String = "" override val title: String = ""
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
data class InfoPreference( data class InfoPreference(
override val title: String, override val title: String,
) : PreferenceItem<String>() { ) : PreferenceItem<String, Unit>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: String) -> Boolean = { true } override val onValueChanged: suspend (value: String) -> Unit = {}
} }
data class CustomPreference( data class CustomPreference(
override val title: String, override val title: String,
val content: @Composable () -> Unit, val content: @Composable () -> Unit,
) : PreferenceItem<Unit>() { ) : PreferenceItem<Unit, Unit>() {
override val enabled: Boolean = true override val enabled: Boolean = true
override val subtitle: String? = null override val subtitle: String? = null
override val icon: ImageVector? = null override val icon: ImageVector? = null
override val onValueChanged: suspend (value: Unit) -> Boolean = { true } override val onValueChanged: suspend (value: Unit) -> Unit = {}
} }
} }
@@ -178,6 +179,6 @@ sealed class Preference {
override val title: String, override val title: String,
override val enabled: Boolean = true, override val enabled: Boolean = true,
val preferenceItems: ImmutableList<PreferenceItem<out Any>>, val preferenceItems: ImmutableList<PreferenceItem<out Any, out Any>>,
) : Preference() ) : Preference()
} }
@@ -35,7 +35,7 @@ val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) {
@Composable @Composable
fun StatusWrapper( fun StatusWrapper(
item: Preference.PreferenceItem<*>, item: Preference.PreferenceItem<*, *>,
highlightKey: String?, highlightKey: String?,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@@ -56,7 +56,7 @@ fun StatusWrapper(
@Composable @Composable
internal fun PreferenceItem( internal fun PreferenceItem(
item: Preference.PreferenceItem<*>, item: Preference.PreferenceItem<*, *>,
highlightKey: String?, highlightKey: String?,
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -83,17 +83,18 @@ internal fun PreferenceItem(
} }
is Preference.PreferenceItem.SliderPreference -> { is Preference.PreferenceItem.SliderPreference -> {
BaseSliderItem( BaseSliderItem(
label = item.title,
value = item.value, value = item.value,
valueRange = item.valueRange, valueRange = item.valueRange,
valueText = item.subtitle.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
steps = item.steps, steps = item.steps,
labelStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize), title = item.title,
subtitle = item.subtitle,
valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(),
onChange = { onChange = {
scope.launch { scope.launch {
item.onValueChanged(it) item.onValueChanged(it)
} }
}, },
titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize),
modifier = Modifier.padding( modifier = Modifier.padding(
horizontal = PrefsHorizontalPadding, horizontal = PrefsHorizontalPadding,
vertical = PrefsVerticalPadding, vertical = PrefsVerticalPadding,
@@ -71,7 +71,7 @@ fun PreferenceScreen(
} }
// Create Preference Item // Create Preference Item
is Preference.PreferenceItem<*> -> item { is Preference.PreferenceItem<*, *> -> item {
PreferenceItem( PreferenceItem(
item = preference, item = preference,
highlightKey = highlightKey, highlightKey = highlightKey,
@@ -13,13 +13,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.log.xLogE import exh.log.xLogE
import exh.source.ExhPreferences
import exh.uconfig.EHConfigurator import exh.uconfig.EHConfigurator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import tachiyomi.core.common.util.lang.launchUI import tachiyomi.core.common.util.lang.launchUI
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@@ -29,8 +29,8 @@ import kotlin.time.Duration.Companion.seconds
@Composable @Composable
fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) { fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
val unsortedPreferences = remember { val exhPreferences = remember {
Injekt.get<UnsortedPreferences>() Injekt.get<ExhPreferences>()
} }
var warnDialogOpen by remember { mutableStateOf(false) } var warnDialogOpen by remember { mutableStateOf(false) }
var configureDialogOpen by remember { mutableStateOf(false) } var configureDialogOpen by remember { mutableStateOf(false) }
@@ -38,7 +38,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
LaunchedEffect(run) { LaunchedEffect(run) {
if (run) { if (run) {
if (unsortedPreferences.exhShowSettingsUploadWarning().get()) { if (exhPreferences.exhShowSettingsUploadWarning().get()) {
warnDialogOpen = true warnDialogOpen = true
} else { } else {
configureDialogOpen = true configureDialogOpen = true
@@ -57,7 +57,7 @@ fun ConfigureExhDialog(run: Boolean, onRunning: () -> Unit) {
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
unsortedPreferences.exhShowSettingsUploadWarning().set(false) exhPreferences.exhShowSettingsUploadWarning().set(false)
configureDialogOpen = true configureDialogOpen = true
warnDialogOpen = false warnDialogOpen = false
}, },
@@ -3,6 +3,7 @@ package eu.kanade.presentation.more.settings.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Build
import android.provider.Settings import android.provider.Settings
import android.webkit.WebStorage import android.webkit.WebStorage
import android.webkit.WebView import android.webkit.WebView
@@ -72,6 +73,7 @@ import exh.pref.DelegateSourcePreferences
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
import exh.source.ExhPreferences
import exh.util.toAnnotatedString import exh.util.toAnnotatedString
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
@@ -86,8 +88,8 @@ import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetAllManga import tachiyomi.domain.manga.interactor.GetAllManga
import tachiyomi.domain.manga.interactor.ResetViewerFlags import tachiyomi.domain.manga.interactor.ResetViewerFlags
@@ -116,6 +118,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val basePreferences = remember { Injekt.get<BasePreferences>() } val basePreferences = remember { Injekt.get<BasePreferences>() }
val networkPreferences = remember { Injekt.get<NetworkPreferences>() } val networkPreferences = remember { Injekt.get<NetworkPreferences>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() } val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
return listOf( return listOf(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
@@ -147,9 +150,18 @@ object SettingsAdvancedScreen : SearchableSettings {
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_manage_notifications), title = stringResource(MR.strings.pref_manage_notifications),
onClick = { onClick = {
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { // SY -->
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName) val intent = Intent().apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
} else {
setAction("android.settings.APP_NOTIFICATION_SETTINGS")
putExtra("app_package", context.packageName)
putExtra("app_uid", context.applicationInfo.uid)
}
} }
// SY <--
context.startActivity(intent) context.startActivity(intent)
}, },
), ),
@@ -157,6 +169,7 @@ object SettingsAdvancedScreen : SearchableSettings {
getDataGroup(), getDataGroup(),
getNetworkGroup(networkPreferences = networkPreferences), getNetworkGroup(networkPreferences = networkPreferences),
getLibraryGroup(libraryPreferences = libraryPreferences), getLibraryGroup(libraryPreferences = libraryPreferences),
getDownloadsGroup(downloadPreferences = downloadPreferences),
getReaderGroup(basePreferences = basePreferences), getReaderGroup(basePreferences = basePreferences),
getExtensionsGroup(basePreferences = basePreferences), getExtensionsGroup(basePreferences = basePreferences),
// SY --> // SY -->
@@ -359,10 +372,33 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(MR.strings.pref_update_library_manga_titles), title = stringResource(MR.strings.pref_update_library_manga_titles),
subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary), subtitle = stringResource(MR.strings.pref_update_library_manga_titles_summary),
), ),
Preference.PreferenceItem.SwitchPreference(
preference = libraryPreferences.disallowNonAsciiFilenames(),
title = stringResource(MR.strings.pref_disallow_non_ascii_filenames),
subtitle = stringResource(MR.strings.pref_disallow_non_ascii_filenames_details),
),
), ),
) )
} }
// SY ->
@Composable
private fun getDownloadsGroup(
downloadPreferences: DownloadPreferences,
): Preference.PreferenceGroup {
return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_downloads),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.includeChapterUrlHash(),
title = stringResource(SYMR.strings.pref_include_chapter_url_hash),
subtitle = stringResource(SYMR.strings.pref_include_chapter_url_hash_desc),
),
),
)
}
// <- SY
@Composable @Composable
private fun getReaderGroup( private fun getReaderGroup(
basePreferences: BasePreferences, basePreferences: BasePreferences,
@@ -701,14 +737,14 @@ object SettingsAdvancedScreen : SearchableSettings {
val context = LocalContext.current val context = LocalContext.current
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val sourcePreferences = remember { Injekt.get<SourcePreferences>() } val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() } val exhPreferences = remember { Injekt.get<ExhPreferences>() }
val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() } val delegateSourcePreferences = remember { Injekt.get<DelegateSourcePreferences>() }
val securityPreferences = remember { Injekt.get<SecurityPreferences>() } val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(SYMR.strings.developer_tools), title = stringResource(SYMR.strings.developer_tools),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.isHentaiEnabled(), preference = exhPreferences.isHentaiEnabled(),
title = stringResource(SYMR.strings.toggle_hentai_features), title = stringResource(SYMR.strings.toggle_hentai_features),
subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary), subtitle = stringResource(SYMR.strings.toggle_hentai_features_summary),
onValueChanged = { onValueChanged = {
@@ -733,7 +769,7 @@ object SettingsAdvancedScreen : SearchableSettings {
), ),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = unsortedPreferences.logLevel(), preference = exhPreferences.logLevel(),
title = stringResource(SYMR.strings.log_level), title = stringResource(SYMR.strings.log_level),
subtitle = stringResource(SYMR.strings.log_level_summary), subtitle = stringResource(SYMR.strings.log_level_summary),
entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel -> entries = EHLogLevel.entries.mapIndexed { index, ehLogLevel ->
@@ -151,6 +151,10 @@ object SettingsAppearanceScreen : SearchableSettings {
formattedNow, formattedNow,
), ),
), ),
Preference.PreferenceItem.SwitchPreference(
preference = uiPreferences.imagesInDescription(),
title = stringResource(MR.strings.pref_display_images_description),
),
), ),
) )
} }
@@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.UnsortedPreferences
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.pluralStringResource
@@ -49,7 +48,6 @@ object SettingsBrowseScreen : SearchableSettings {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab().asState(scope) } val hideFeedTab by remember { Injekt.get<UiPreferences>().hideFeedTab().asState(scope) }
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
// SY <-- // SY <--
return listOf( return listOf(
// SY --> // SY -->
@@ -77,7 +75,7 @@ object SettingsBrowseScreen : SearchableSettings {
subtitle = stringResource(SYMR.strings.pref_source_navigation_summery), subtitle = stringResource(SYMR.strings.pref_source_navigation_summery),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.allowLocalSourceHiddenFolders(), preference = sourcePreferences.allowLocalSourceHiddenFolders(),
title = stringResource(SYMR.strings.pref_local_source_hidden_folders), title = stringResource(SYMR.strings.pref_local_source_hidden_folders),
subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery), subtitle = stringResource(SYMR.strings.pref_local_source_hidden_folders_summery),
), ),
@@ -37,6 +37,8 @@ object SettingsDownloadScreen : SearchableSettings {
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList()) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() } val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState()
return listOf( return listOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.downloadOnlyOverWifi(), preference = downloadPreferences.downloadOnlyOverWifi(),
@@ -51,6 +53,19 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.split_tall_images), title = stringResource(MR.strings.split_tall_images),
subtitle = stringResource(MR.strings.split_tall_images_summary), subtitle = stringResource(MR.strings.split_tall_images_summary),
), ),
Preference.PreferenceItem.SliderPreference(
value = parallelSourceLimit,
valueRange = 1..10,
title = stringResource(MR.strings.pref_download_concurrent_sources),
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) },
),
Preference.PreferenceItem.SliderPreference(
value = parallelPageLimit,
valueRange = 1..15,
title = stringResource(MR.strings.pref_download_concurrent_pages),
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) },
),
getDeleteChaptersGroup( getDeleteChaptersGroup(
downloadPreferences = downloadPreferences, downloadPreferences = downloadPreferences,
categories = allCategories, categories = allCategories,
@@ -51,6 +51,7 @@ import exh.eh.EHentaiUpdateWorker
import exh.eh.EHentaiUpdateWorkerConstants import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.EHentaiUpdaterStats import exh.eh.EHentaiUpdaterStats
import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.EHentaiSearchMetadata
import exh.source.ExhPreferences
import exh.ui.login.EhLoginActivity import exh.ui.login.EhLoginActivity
import exh.util.nullIfBlank import exh.util.nullIfBlank
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -63,7 +64,6 @@ import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_ONLY_ON_WIFI
import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries import tachiyomi.domain.manga.interactor.DeleteFavoriteEntries
@@ -88,22 +88,22 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
override fun getTitleRes() = SYMR.strings.pref_category_eh override fun getTitleRes() = SYMR.strings.pref_category_eh
override fun isEnabled(): Boolean = Injekt.get<UnsortedPreferences>().isHentaiEnabled().get() override fun isEnabled(): Boolean = Injekt.get<ExhPreferences>().isHentaiEnabled().get()
@Composable @Composable
fun Reconfigure( fun Reconfigure(
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
openWarnConfigureDialogController: () -> Unit, openWarnConfigureDialogController: () -> Unit,
) { ) {
var initialLoadGuard by remember { mutableStateOf(false) } var initialLoadGuard by remember { mutableStateOf(false) }
val useHentaiAtHome by unsortedPreferences.useHentaiAtHome().collectAsState() val useHentaiAtHome by exhPreferences.useHentaiAtHome().collectAsState()
val useJapaneseTitle by unsortedPreferences.useJapaneseTitle().collectAsState() val useJapaneseTitle by exhPreferences.useJapaneseTitle().collectAsState()
val useOriginalImages by unsortedPreferences.exhUseOriginalImages().collectAsState() val useOriginalImages by exhPreferences.exhUseOriginalImages().collectAsState()
val ehTagFilterValue by unsortedPreferences.ehTagFilterValue().collectAsState() val ehTagFilterValue by exhPreferences.ehTagFilterValue().collectAsState()
val ehTagWatchingValue by unsortedPreferences.ehTagWatchingValue().collectAsState() val ehTagWatchingValue by exhPreferences.ehTagWatchingValue().collectAsState()
val settingsLanguages by unsortedPreferences.exhSettingsLanguages().collectAsState() val settingsLanguages by exhPreferences.exhSettingsLanguages().collectAsState()
val enabledCategories by unsortedPreferences.exhEnabledCategories().collectAsState() val enabledCategories by exhPreferences.exhEnabledCategories().collectAsState()
val imageQuality by unsortedPreferences.imageQuality().collectAsState() val imageQuality by exhPreferences.imageQuality().collectAsState()
DisposableEffect( DisposableEffect(
useHentaiAtHome, useHentaiAtHome,
useJapaneseTitle, useJapaneseTitle,
@@ -124,15 +124,15 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() } val exhPreferences: ExhPreferences = remember { Injekt.get() }
val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() } val getFlatMetadataById: GetFlatMetadataById = remember { Injekt.get() }
val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() } val deleteFavoriteEntries: DeleteFavoriteEntries = remember { Injekt.get() }
val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() } val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata = remember { Injekt.get() }
val exhentaiEnabled by unsortedPreferences.enableExhentai().collectAsState() val exhentaiEnabled by exhPreferences.enableExhentai().collectAsState()
var runConfigureDialog by remember { mutableStateOf(false) } var runConfigureDialog by remember { mutableStateOf(false) }
val openWarnConfigureDialogController = { runConfigureDialog = true } val openWarnConfigureDialogController = { runConfigureDialog = true }
Reconfigure(unsortedPreferences, openWarnConfigureDialogController) Reconfigure(exhPreferences, openWarnConfigureDialogController)
ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false }) ConfigureExhDialog(run = runConfigureDialog, onRunning = { runConfigureDialog = false })
@@ -140,36 +140,36 @@ object SettingsEhScreen : SearchableSettings {
Preference.PreferenceGroup( Preference.PreferenceGroup(
stringResource(SYMR.strings.ehentai_prefs_account_settings), stringResource(SYMR.strings.ehentai_prefs_account_settings),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
getLoginPreference(unsortedPreferences, openWarnConfigureDialogController), getLoginPreference(exhPreferences, openWarnConfigureDialogController),
useHentaiAtHome(exhentaiEnabled, unsortedPreferences), useHentaiAtHome(exhentaiEnabled, exhPreferences),
useJapaneseTitle(exhentaiEnabled, unsortedPreferences), useJapaneseTitle(exhentaiEnabled, exhPreferences),
useOriginalImages(exhentaiEnabled, unsortedPreferences), useOriginalImages(exhentaiEnabled, exhPreferences),
watchedTags(exhentaiEnabled), watchedTags(exhentaiEnabled),
tagFilterThreshold(exhentaiEnabled, unsortedPreferences), tagFilterThreshold(exhentaiEnabled, exhPreferences),
tagWatchingThreshold(exhentaiEnabled, unsortedPreferences), tagWatchingThreshold(exhentaiEnabled, exhPreferences),
settingsLanguages(exhentaiEnabled, unsortedPreferences), settingsLanguages(exhentaiEnabled, exhPreferences),
enabledCategories(exhentaiEnabled, unsortedPreferences), enabledCategories(exhentaiEnabled, exhPreferences),
watchedListDefaultState(exhentaiEnabled, unsortedPreferences), watchedListDefaultState(exhentaiEnabled, exhPreferences),
imageQuality(exhentaiEnabled, unsortedPreferences), imageQuality(exhentaiEnabled, exhPreferences),
enhancedEhentaiView(unsortedPreferences), enhancedEhentaiView(exhPreferences),
), ),
), ),
Preference.PreferenceGroup( Preference.PreferenceGroup(
stringResource(SYMR.strings.favorites_sync), stringResource(SYMR.strings.favorites_sync),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
readOnlySync(unsortedPreferences), readOnlySync(exhPreferences),
syncFavoriteNotes(), syncFavoriteNotes(),
lenientSync(unsortedPreferences), lenientSync(exhPreferences),
forceSyncReset(deleteFavoriteEntries), forceSyncReset(deleteFavoriteEntries),
), ),
), ),
Preference.PreferenceGroup( Preference.PreferenceGroup(
stringResource(SYMR.strings.gallery_update_checker), stringResource(SYMR.strings.gallery_update_checker),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
updateCheckerFrequency(unsortedPreferences), updateCheckerFrequency(exhPreferences),
autoUpdateRequirements(unsortedPreferences), autoUpdateRequirements(exhPreferences),
updaterStatistics( updaterStatistics(
unsortedPreferences, exhPreferences,
getExhFavoriteMangaWithMetadata, getExhFavoriteMangaWithMetadata,
getFlatMetadataById, getFlatMetadataById,
), ),
@@ -180,7 +180,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun getLoginPreference( fun getLoginPreference(
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
openWarnConfigureDialogController: () -> Unit, openWarnConfigureDialogController: () -> Unit,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
val activityResultContract = val activityResultContract =
@@ -191,9 +191,9 @@ object SettingsEhScreen : SearchableSettings {
} }
} }
val context = LocalContext.current val context = LocalContext.current
val value by unsortedPreferences.enableExhentai().collectAsState() val value by exhPreferences.enableExhentai().collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.enableExhentai(), preference = exhPreferences.enableExhentai(),
title = stringResource(SYMR.strings.enable_exhentai), title = stringResource(SYMR.strings.enable_exhentai),
subtitle = if (!value) { subtitle = if (!value) {
stringResource(SYMR.strings.requires_login) stringResource(SYMR.strings.requires_login)
@@ -202,7 +202,7 @@ object SettingsEhScreen : SearchableSettings {
}, },
onValueChanged = { newVal -> onValueChanged = { newVal ->
if (!newVal) { if (!newVal) {
unsortedPreferences.enableExhentai().set(false) exhPreferences.enableExhentai().set(false)
true true
} else { } else {
activityResultContract.launch(EhLoginActivity.newIntent(context)) activityResultContract.launch(EhLoginActivity.newIntent(context))
@@ -215,10 +215,10 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun useHentaiAtHome( fun useHentaiAtHome(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<Int> { ): Preference.PreferenceItem.ListPreference<Int> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = unsortedPreferences.useHentaiAtHome(), preference = exhPreferences.useHentaiAtHome(),
title = stringResource(SYMR.strings.use_hentai_at_home), title = stringResource(SYMR.strings.use_hentai_at_home),
subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary), subtitle = stringResource(SYMR.strings.use_hentai_at_home_summary),
entries = persistentMapOf( entries = persistentMapOf(
@@ -232,11 +232,11 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun useJapaneseTitle( fun useJapaneseTitle(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
val value by unsortedPreferences.useJapaneseTitle().collectAsState() val value by exhPreferences.useJapaneseTitle().collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.useJapaneseTitle(), preference = exhPreferences.useJapaneseTitle(),
title = stringResource(SYMR.strings.show_japanese_titles), title = stringResource(SYMR.strings.show_japanese_titles),
subtitle = if (value) { subtitle = if (value) {
stringResource(SYMR.strings.show_japanese_titles_option_1) stringResource(SYMR.strings.show_japanese_titles_option_1)
@@ -250,11 +250,11 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun useOriginalImages( fun useOriginalImages(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
val value by unsortedPreferences.exhUseOriginalImages().collectAsState() val value by exhPreferences.exhUseOriginalImages().collectAsState()
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.exhUseOriginalImages(), preference = exhPreferences.exhUseOriginalImages(),
title = stringResource(SYMR.strings.use_original_images), title = stringResource(SYMR.strings.use_original_images),
subtitle = if (value) { subtitle = if (value) {
stringResource(SYMR.strings.use_original_images_on) stringResource(SYMR.strings.use_original_images_on)
@@ -351,9 +351,9 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun tagFilterThreshold( fun tagFilterThreshold(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by unsortedPreferences.ehTagFilterValue().collectAsState() val value by exhPreferences.ehTagFilterValue().collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
TagThresholdDialog( TagThresholdDialog(
@@ -364,7 +364,7 @@ object SettingsEhScreen : SearchableSettings {
outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error), outsideRangeError = stringResource(SYMR.strings.tag_filtering_threshhold_error),
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
unsortedPreferences.ehTagFilterValue().set(it) exhPreferences.ehTagFilterValue().set(it)
}, },
) )
} }
@@ -381,9 +381,9 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun tagWatchingThreshold( fun tagWatchingThreshold(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by unsortedPreferences.ehTagWatchingValue().collectAsState() val value by exhPreferences.ehTagWatchingValue().collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
TagThresholdDialog( TagThresholdDialog(
@@ -394,7 +394,7 @@ object SettingsEhScreen : SearchableSettings {
outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error), outsideRangeError = stringResource(SYMR.strings.tag_watching_threshhold_error),
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
unsortedPreferences.ehTagWatchingValue().set(it) exhPreferences.ehTagWatchingValue().set(it)
}, },
) )
} }
@@ -604,9 +604,9 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun settingsLanguages( fun settingsLanguages(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by unsortedPreferences.exhSettingsLanguages().collectAsState() val value by exhPreferences.exhSettingsLanguages().collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
LanguagesDialog( LanguagesDialog(
@@ -614,7 +614,7 @@ object SettingsEhScreen : SearchableSettings {
initialValue = value, initialValue = value,
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
unsortedPreferences.exhSettingsLanguages().set(it) exhPreferences.exhSettingsLanguages().set(it)
}, },
) )
} }
@@ -770,9 +770,9 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun enabledCategories( fun enabledCategories(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
val value by unsortedPreferences.exhEnabledCategories().collectAsState() val value by exhPreferences.exhEnabledCategories().collectAsState()
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
FrontPageCategoriesDialog( FrontPageCategoriesDialog(
@@ -780,7 +780,7 @@ object SettingsEhScreen : SearchableSettings {
initialValue = value, initialValue = value,
onValueChange = { onValueChange = {
dialogOpen = false dialogOpen = false
unsortedPreferences.exhEnabledCategories().set(it) exhPreferences.exhEnabledCategories().set(it)
}, },
) )
} }
@@ -797,10 +797,10 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun watchedListDefaultState( fun watchedListDefaultState(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.SwitchPreference { ): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.exhWatchedListDefaultState(), preference = exhPreferences.exhWatchedListDefaultState(),
title = stringResource(SYMR.strings.watched_list_default), title = stringResource(SYMR.strings.watched_list_default),
subtitle = stringResource(SYMR.strings.watched_list_state_summary), subtitle = stringResource(SYMR.strings.watched_list_state_summary),
enabled = exhentaiEnabled, enabled = exhentaiEnabled,
@@ -810,10 +810,10 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun imageQuality( fun imageQuality(
exhentaiEnabled: Boolean, exhentaiEnabled: Boolean,
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<String> { ): Preference.PreferenceItem.ListPreference<String> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = unsortedPreferences.imageQuality(), preference = exhPreferences.imageQuality(),
title = stringResource(SYMR.strings.eh_image_quality_summary), title = stringResource(SYMR.strings.eh_image_quality_summary),
subtitle = stringResource(SYMR.strings.eh_image_quality), subtitle = stringResource(SYMR.strings.eh_image_quality),
entries = persistentMapOf( entries = persistentMapOf(
@@ -829,18 +829,18 @@ object SettingsEhScreen : SearchableSettings {
} }
@Composable @Composable
fun enhancedEhentaiView(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference { fun enhancedEhentaiView(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.enhancedEHentaiView(), preference = exhPreferences.enhancedEHentaiView(),
title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view), title = stringResource(SYMR.strings.pref_enhanced_e_hentai_view),
subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary), subtitle = stringResource(SYMR.strings.pref_enhanced_e_hentai_view_summary),
) )
} }
@Composable @Composable
fun readOnlySync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference { fun readOnlySync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.exhReadOnlySync(), preference = exhPreferences.exhReadOnlySync(),
title = stringResource(SYMR.strings.disable_favorites_uploading), title = stringResource(SYMR.strings.disable_favorites_uploading),
subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary), subtitle = stringResource(SYMR.strings.disable_favorites_uploading_summary),
) )
@@ -863,9 +863,9 @@ object SettingsEhScreen : SearchableSettings {
} }
@Composable @Composable
fun lenientSync(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.SwitchPreference { fun lenientSync(exhPreferences: ExhPreferences): Preference.PreferenceItem.SwitchPreference {
return Preference.PreferenceItem.SwitchPreference( return Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.exhLenientSync(), preference = exhPreferences.exhLenientSync(),
title = stringResource(SYMR.strings.ignore_sync_errors), title = stringResource(SYMR.strings.ignore_sync_errors),
subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary), subtitle = stringResource(SYMR.strings.ignore_sync_errors_summary),
) )
@@ -935,12 +935,12 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun updateCheckerFrequency( fun updateCheckerFrequency(
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.ListPreference<Int> { ): Preference.PreferenceItem.ListPreference<Int> {
val value by unsortedPreferences.exhAutoUpdateFrequency().collectAsState() val value by exhPreferences.exhAutoUpdateFrequency().collectAsState()
val context = LocalContext.current val context = LocalContext.current
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = unsortedPreferences.exhAutoUpdateFrequency(), preference = exhPreferences.exhAutoUpdateFrequency(),
title = stringResource(SYMR.strings.time_between_batches), title = stringResource(SYMR.strings.time_between_batches),
subtitle = if (value == 0) { subtitle = if (value == 0) {
stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name)) stringResource(SYMR.strings.time_between_batches_summary_1, stringResource(MR.strings.app_name))
@@ -971,12 +971,12 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun autoUpdateRequirements( fun autoUpdateRequirements(
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
): Preference.PreferenceItem.MultiSelectListPreference { ): Preference.PreferenceItem.MultiSelectListPreference {
val value by unsortedPreferences.exhAutoUpdateRequirements().collectAsState() val value by exhPreferences.exhAutoUpdateRequirements().collectAsState()
val context = LocalContext.current val context = LocalContext.current
return Preference.PreferenceItem.MultiSelectListPreference( return Preference.PreferenceItem.MultiSelectListPreference(
preference = unsortedPreferences.exhAutoUpdateRequirements(), preference = exhPreferences.exhAutoUpdateRequirements(),
title = stringResource(SYMR.strings.auto_update_restrictions), title = stringResource(SYMR.strings.auto_update_restrictions),
subtitle = remember(value) { subtitle = remember(value) {
context.stringResource( context.stringResource(
@@ -1139,7 +1139,7 @@ object SettingsEhScreen : SearchableSettings {
@Composable @Composable
fun updaterStatistics( fun updaterStatistics(
unsortedPreferences: UnsortedPreferences, exhPreferences: ExhPreferences,
getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata, getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata,
getFlatMetadataById: GetFlatMetadataById, getFlatMetadataById: GetFlatMetadataById,
): Preference.PreferenceItem.TextPreference { ): Preference.PreferenceItem.TextPreference {
@@ -1150,7 +1150,7 @@ object SettingsEhScreen : SearchableSettings {
value = withIOContext { value = withIOContext {
try { try {
val stats = val stats =
unsortedPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let { exhPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let {
Json.decodeFromString<EHentaiUpdaterStats>(it) Json.decodeFromString<EHentaiUpdaterStats>(it)
} }
@@ -25,7 +25,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.ResetCategoryFlags import tachiyomi.domain.category.interactor.ResetCategoryFlags
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
@@ -59,9 +58,6 @@ object SettingsLibraryScreen : SearchableSettings {
val getCategories = remember { Injekt.get<GetCategories>() } val getCategories = remember { Injekt.get<GetCategories>() }
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() } val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList()) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
// SY -->
val unsortedPreferences = remember { Injekt.get<UnsortedPreferences>() }
// SY <--
return listOf( return listOf(
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences), getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
@@ -69,7 +65,6 @@ object SettingsLibraryScreen : SearchableSettings {
getBehaviorGroup(libraryPreferences), getBehaviorGroup(libraryPreferences),
// SY --> // SY -->
getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences), getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences),
getMigrationCategory(unsortedPreferences),
// SY <-- // SY <--
) )
} }
@@ -300,22 +295,5 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
) )
} }
@Composable
fun getMigrationCategory(unsortedPreferences: UnsortedPreferences): Preference.PreferenceGroup {
val skipPreMigration by unsortedPreferences.skipPreMigration().collectAsState()
val migrationSources by unsortedPreferences.migrationSources().collectAsState()
return Preference.PreferenceGroup(
stringResource(SYMR.strings.migration),
enabled = skipPreMigration || migrationSources.isNotEmpty(),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
preference = unsortedPreferences.skipPreMigration(),
title = stringResource(SYMR.strings.skip_pre_migration),
subtitle = stringResource(SYMR.strings.pref_skip_pre_migration_summary),
),
),
)
}
// SY <-- // SY <--
} }
@@ -9,7 +9,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode import androidx.compose.material.icons.automirrored.outlined.ChromeReaderMode
import androidx.compose.material.icons.outlined.ChromeReaderMode
import androidx.compose.material.icons.outlined.Code import androidx.compose.material.icons.outlined.Code
import androidx.compose.material.icons.outlined.CollectionsBookmark import androidx.compose.material.icons.outlined.CollectionsBookmark
import androidx.compose.material.icons.outlined.Explore import androidx.compose.material.icons.outlined.Explore
@@ -44,7 +44,6 @@ import logcat.LogPriority
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
@@ -65,14 +64,13 @@ object SettingsMangadexScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val sourcePreferences: SourcePreferences = remember { Injekt.get() } val sourcePreferences: SourcePreferences = remember { Injekt.get() }
val unsortedPreferences: UnsortedPreferences = remember { Injekt.get() }
val trackPreferences: TrackPreferences = remember { Injekt.get() } val trackPreferences: TrackPreferences = remember { Injekt.get() }
val mdex = remember { MdUtil.getEnabledMangaDex(unsortedPreferences, sourcePreferences) } ?: return emptyList() val mdex = remember { MdUtil.getEnabledMangaDex(sourcePreferences) } ?: return emptyList()
return listOf( return listOf(
loginPreference(mdex, trackPreferences), loginPreference(mdex, trackPreferences),
preferredMangaDexId(unsortedPreferences, sourcePreferences), preferredMangaDexId(sourcePreferences),
syncMangaDexIntoThis(unsortedPreferences), syncMangaDexIntoThis(sourcePreferences),
syncLibraryToMangaDex(), syncLibraryToMangaDex(),
) )
} }
@@ -174,11 +172,10 @@ object SettingsMangadexScreen : SearchableSettings {
@Composable @Composable
fun preferredMangaDexId( fun preferredMangaDexId(
unsortedPreferences: UnsortedPreferences,
sourcePreferences: SourcePreferences, sourcePreferences: SourcePreferences,
): Preference.PreferenceItem.ListPreference<String> { ): Preference.PreferenceItem.ListPreference<String> {
return Preference.PreferenceItem.ListPreference( return Preference.PreferenceItem.ListPreference(
preference = unsortedPreferences.preferredMangaDexId(), preference = sourcePreferences.preferredMangaDexId(),
title = stringResource(SYMR.strings.mangadex_preffered_source), title = stringResource(SYMR.strings.mangadex_preffered_source),
subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary), subtitle = stringResource(SYMR.strings.mangadex_preffered_source_summary),
entries = MdUtil.getEnabledMangaDexs(sourcePreferences) entries = MdUtil.getEnabledMangaDexs(sourcePreferences)
@@ -250,7 +247,7 @@ object SettingsMangadexScreen : SearchableSettings {
} }
@Composable @Composable
fun syncMangaDexIntoThis(unsortedPreferences: UnsortedPreferences): Preference.PreferenceItem.TextPreference { fun syncMangaDexIntoThis(sourcePreferences: SourcePreferences): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current val context = LocalContext.current
var dialogOpen by remember { mutableStateOf(false) } var dialogOpen by remember { mutableStateOf(false) }
if (dialogOpen) { if (dialogOpen) {
@@ -258,7 +255,7 @@ object SettingsMangadexScreen : SearchableSettings {
onDismissRequest = { dialogOpen = false }, onDismissRequest = { dialogOpen = false },
onSelectionConfirmed = { items -> onSelectionConfirmed = { items ->
dialogOpen = false dialogOpen = false
unsortedPreferences.mangadexSyncToLibraryIndexes().set( sourcePreferences.mangadexSyncToLibraryIndexes().set(
List(items.size) { index -> (index + 1).toString() }.toSet(), List(items.size) { index -> (index + 1).toString() }.toSet(),
) )
LibraryUpdateJob.startNow( LibraryUpdateJob.startNow(
@@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen
import android.os.Build
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -12,6 +11,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
@@ -135,11 +135,9 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(MR.strings.pref_fullscreen), title = stringResource(MR.strings.pref_fullscreen),
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.cutoutShort(), preference = readerPreferences.drawUnderCutout(),
title = stringResource(MR.strings.pref_cutout_short), title = stringResource(MR.strings.pref_cutout_short),
enabled = fullscreen && enabled = LocalView.current.hasDisplayCutout() && fullscreen,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
LocalView.current.rootWindowInsets?.displayCutout != null, // has cutout
), ),
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.keepScreenOn(), preference = readerPreferences.keepScreenOn(),
@@ -177,23 +175,17 @@ object SettingsReaderScreen : SearchableSettings {
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
valueRange = 1..15, valueRange = 1..15,
title = stringResource(MR.strings.pref_flash_duration), title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
enabled = flashPageState, enabled = flashPageState,
onValueChanged = { onValueChanged = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
true
},
), ),
Preference.PreferenceItem.SliderPreference( Preference.PreferenceItem.SliderPreference(
value = flashInterval, value = flashInterval,
valueRange = 1..10, valueRange = 1..10,
title = stringResource(MR.strings.pref_flash_page_interval), title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
enabled = flashPageState, enabled = flashPageState,
onValueChanged = { onValueChanged = { flashIntervalPref.set(it) },
flashIntervalPref.set(it)
true
},
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = flashColorPref, preference = flashColorPref,
@@ -227,13 +219,6 @@ object SettingsReaderScreen : SearchableSettings {
preference = readerPreferences.skipDupe(), preference = readerPreferences.skipDupe(),
title = stringResource(MR.strings.pref_skip_dupe_chapters), title = stringResource(MR.strings.pref_skip_dupe_chapters),
), ),
// SY -->
Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.markReadDupe(),
title = stringResource(SYMR.strings.pref_mark_read_dupe_chapters),
subtitle = stringResource(SYMR.strings.pref_mark_read_dupe_chapters_summary),
),
// SY <--
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = readerPreferences.alwaysShowChapterTransition(), preference = readerPreferences.alwaysShowChapterTransition(),
title = stringResource(MR.strings.pref_always_show_chapter_transition), title = stringResource(MR.strings.pref_always_show_chapter_transition),
@@ -389,11 +374,8 @@ object SettingsReaderScreen : SearchableSettings {
it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX
}, },
title = stringResource(MR.strings.pref_webtoon_side_padding), title = stringResource(MR.strings.pref_webtoon_side_padding),
subtitle = numberFormat.format(webtoonSidePadding / 100f), valueString = numberFormat.format(webtoonSidePadding / 100f),
onValueChanged = { onValueChanged = { webtoonSidePaddingPref.set(it) },
webtoonSidePaddingPref.set(it)
true
},
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
preference = readerPreferences.readerHideThreshold(), preference = readerPreferences.readerHideThreshold(),
@@ -604,7 +586,7 @@ object SettingsReaderScreen : SearchableSettings {
title = stringResource(SYMR.strings.page_layout), title = stringResource(SYMR.strings.page_layout),
subtitle = stringResource(SYMR.strings.automatic_can_still_switch), subtitle = stringResource(SYMR.strings.automatic_can_still_switch),
entries = ReaderPreferences.PageLayouts entries = ReaderPreferences.PageLayouts
.mapIndexed { index, it -> index + 1 to stringResource(it) } .mapIndexed { index, it -> index to stringResource(it) }
.toMap() .toMap()
.toImmutableMap(), .toImmutableMap(),
), ),
@@ -183,7 +183,7 @@ private fun SearchResult(
emptySequence() emptySequence()
} }
} }
is Preference.PreferenceItem<*> -> sequenceOf(null to p) is Preference.PreferenceItem<*, *> -> sequenceOf(null to p)
} }
} }
// Don't show info preference // Don't show info preference
@@ -4,6 +4,7 @@ import androidx.compose.runtime.Immutable
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 dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.extension.ExtensionManager
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
@@ -27,6 +28,7 @@ class ExtensionReposScreenModel(
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(), private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(), private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(), private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
private val extensionManager: ExtensionManager = Injekt.get(),
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) { ) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE) private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
@@ -53,6 +55,7 @@ class ExtensionReposScreenModel(
fun createRepo(baseUrl: String) { fun createRepo(baseUrl: String) {
screenModelScope.launchIO { screenModelScope.launchIO {
when (val result = createExtensionRepo.await(baseUrl)) { when (val result = createExtensionRepo.await(baseUrl)) {
CreateExtensionRepo.Result.Success -> extensionManager.findAvailableExtensions()
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl) CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists) CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
is CreateExtensionRepo.Result.DuplicateFingerprint -> { is CreateExtensionRepo.Result.DuplicateFingerprint -> {
@@ -93,6 +96,7 @@ class ExtensionReposScreenModel(
fun deleteRepo(baseUrl: String) { fun deleteRepo(baseUrl: String) {
screenModelScope.launchIO { screenModelScope.launchIO {
deleteExtensionRepo.await(baseUrl) deleteExtensionRepo.await(baseUrl)
extensionManager.findAvailableExtensions()
} }
} }
@@ -57,7 +57,7 @@ fun ExtensionRepoCreateDialog(
}, },
text = { text = {
Column { Column {
Text(text = stringResource(MR.strings.action_add_repo_message)) Text(text = stringResource(MR.strings.action_add_repo_message, stringResource(MR.strings.app_name)))
OutlinedTextField( OutlinedTextField(
modifier = Modifier modifier = Modifier
@@ -98,7 +98,7 @@ class DebugInfoScreen : Screen() {
} }
private fun getDeviceInfoGroup(): Preference.PreferenceGroup { private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
val items = persistentListOf<Preference.PreferenceItem<out Any>>().mutate { val items = persistentListOf<Preference.PreferenceItem<out Any, out Any>>().mutate {
it.add( it.add(
Preference.PreferenceItem.TextPreference( Preference.PreferenceItem.TextPreference(
title = "Model", title = "Model",
@@ -74,6 +74,7 @@ fun ChapterListDialog(
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(
chapterItem.chapter.name, chapterItem.chapter.name,
chapterItem.chapter.scanlator, chapterItem.chapter.scanlator,
chapterItem.chapter.url,
chapterItem.manga.ogTitle, chapterItem.manga.ogTitle,
chapterItem.manga.source, chapterItem.manga.source,
) )
@@ -6,6 +6,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -15,13 +16,12 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.presentation.theme.TachiyomiPreviewTheme
@Composable @Composable
fun PageIndicatorText( fun ReaderPageIndicator(
// SY --> currentPage: Int,
currentPage: String,
// SY <--
totalPages: Int, totalPages: Int,
modifier: Modifier = Modifier,
) { ) {
if (currentPage.isEmpty() || totalPages <= 0) return if (currentPage <= 0 || totalPages <= 0) return
val text = "$currentPage / $totalPages" val text = "$currentPage / $totalPages"
@@ -38,6 +38,7 @@ fun PageIndicatorText(
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
modifier = modifier,
) { ) {
Text( Text(
text = text, text = text,
@@ -52,10 +53,10 @@ fun PageIndicatorText(
@PreviewLightDark @PreviewLightDark
@Composable @Composable
private fun PageIndicatorTextPreview() { private fun ReaderPageIndicatorPreview() {
TachiyomiPreviewTheme { TachiyomiPreviewTheme {
Surface { Surface {
PageIndicatorText(currentPage = "10", totalPages = 69) ReaderPageIndicator(currentPage = 10, totalPages = 69)
} }
} }
} }
@@ -2,10 +2,13 @@ package eu.kanade.presentation.reader.appbars
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -13,10 +16,13 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -27,7 +33,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.reader.components.ChapterNavigator import eu.kanade.presentation.reader.components.ChapterNavigator
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
@@ -36,7 +41,8 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.components.material.padding
private val animationSpec = tween<IntOffset>(200) private val readerBarsSlideAnimationSpec = tween<IntOffset>(200)
private val readerBarsFadeAnimationSpec = tween<Float>(150)
// SY --> // SY -->
enum class NavBarType { enum class NavBarType {
@@ -65,7 +71,6 @@ fun BoxIgnoreLayoutDirection(modifier: Modifier, content: @Composable BoxScope.(
@Composable @Composable
fun ReaderAppBars( fun ReaderAppBars(
visible: Boolean, visible: Boolean,
fullscreen: Boolean,
mangaTitle: String?, mangaTitle: String?,
chapterTitle: String?, chapterTitle: String?,
@@ -122,11 +127,7 @@ fun ReaderAppBars(
.surfaceColorAtElevation(3.dp) .surfaceColorAtElevation(3.dp)
.copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f) .copy(alpha = if (isSystemInDarkTheme()) 0.9f else 0.95f)
val modifierWithInsetsPadding = if (fullscreen) { val modifierWithInsetsPadding = Modifier.systemBarsPadding()
Modifier.systemBarsPadding()
} else {
Modifier
}
// SY --> // SY -->
BoxIgnoreLayoutDirection( BoxIgnoreLayoutDirection(
@@ -136,11 +137,11 @@ fun ReaderAppBars(
visible = visible && navBarType == NavBarType.VerticalLeft, visible = visible && navBarType == NavBarType.VerticalLeft,
enter = slideInHorizontally( enter = slideInHorizontally(
initialOffsetX = { -it }, initialOffsetX = { -it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ),
exit = slideOutHorizontally( exit = slideOutHorizontally(
targetOffsetX = { -it }, targetOffsetX = { -it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ),
modifier = modifierWithInsetsPadding modifier = modifierWithInsetsPadding
.padding(bottom = 48.dp, top = 120.dp) .padding(bottom = 48.dp, top = 120.dp)
@@ -164,11 +165,11 @@ fun ReaderAppBars(
visible = visible && navBarType == NavBarType.VerticalRight, visible = visible && navBarType == NavBarType.VerticalRight,
enter = slideInHorizontally( enter = slideInHorizontally(
initialOffsetX = { it }, initialOffsetX = { it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ),
exit = slideOutHorizontally( exit = slideOutHorizontally(
targetOffsetX = { it }, targetOffsetX = { it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ),
modifier = modifierWithInsetsPadding modifier = modifierWithInsetsPadding
.padding(bottom = 48.dp, top = 120.dp) .padding(bottom = 48.dp, top = 120.dp)
@@ -196,48 +197,23 @@ fun ReaderAppBars(
visible = visible, visible = visible,
enter = slideInVertically( enter = slideInVertically(
initialOffsetY = { -it }, initialOffsetY = { -it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ) + fadeIn(animationSpec = readerBarsFadeAnimationSpec),
exit = slideOutVertically( exit = slideOutVertically(
targetOffsetY = { -it }, targetOffsetY = { -it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ) + fadeOut(animationSpec = readerBarsFadeAnimationSpec),
) { ) {
// SY --> // SY -->
Column(modifierWithInsetsPadding) { Column {
// SY <-- // SY <--
AppBar( ReaderTopBar(
modifier = /*SY --> */ Modifier /*SY <-- */ modifier = Modifier
.background(backgroundColor)
.clickable(onClick = onClickTopAppBar), .clickable(onClick = onClickTopAppBar),
backgroundColor = backgroundColor, mangaTitle = mangaTitle,
title = mangaTitle, chapterTitle = chapterTitle,
subtitle = chapterTitle,
navigateUp = navigateUp, navigateUp = navigateUp,
/* SY --> actions = {
AppBarActions(
listOfNotNull(
AppBar.Action(
title = stringResource(
if (bookmarked) MR.strings.action_remove_bookmark else MR.strings.action_bookmark
),
icon = if (bookmarked) Icons.Outlined.Bookmark else Icons.Outlined.BookmarkBorder,
onClick = onToggleBookmarked,
),
onOpenInWebView?.let {
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_web_view),
onClick = it,
)
},
onShare?.let {
AppBar.OverflowAction(
title = stringResource(MR.strings.action_share),
onClick = it,
)
},
),
)
}, SY <-- */
) )
// SY --> // SY -->
ExhUtils( ExhUtils(
@@ -255,8 +231,8 @@ fun ReaderAppBars(
onClickBoostPage = onClickBoostPage, onClickBoostPage = onClickBoostPage,
onClickBoostPageHelp = onClickBoostPageHelp, onClickBoostPageHelp = onClickBoostPageHelp,
) )
// SY <--
} }
// SY <--
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
@@ -265,18 +241,18 @@ fun ReaderAppBars(
visible = visible, visible = visible,
enter = slideInVertically( enter = slideInVertically(
initialOffsetY = { it }, initialOffsetY = { it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ) + fadeIn(animationSpec = readerBarsFadeAnimationSpec),
exit = slideOutVertically( exit = slideOutVertically(
targetOffsetY = { it }, targetOffsetY = { it },
animationSpec = animationSpec, animationSpec = readerBarsSlideAnimationSpec,
), ) + fadeOut(animationSpec = readerBarsFadeAnimationSpec),
) { ) {
Column( Column(
modifier = modifierWithInsetsPadding,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
) { ) {
if (navBarType == NavBarType.Bottom) { // SY -->
if (navBarType == NavBarType.Bottom) { // <-- SY
ChapterNavigator( ChapterNavigator(
isRtl = isRtl, isRtl = isRtl,
onNextChapter = onNextChapter, onNextChapter = onNextChapter,
@@ -291,11 +267,10 @@ fun ReaderAppBars(
) )
} }
BottomReaderBar( ReaderBottomBar(
// SY --> // SY -->
enabledButtons = enabledButtons, enabledButtons = enabledButtons,
// SY <-- // SY <--
backgroundColor = backgroundColor,
readingMode = readingMode, readingMode = readingMode,
onClickReadingMode = onClickReadingMode, onClickReadingMode = onClickReadingMode,
orientation = orientation, orientation = orientation,
@@ -313,6 +288,12 @@ fun ReaderAppBars(
onClickShare = onShare, onClickShare = onShare,
onClickPageLayout = onClickPageLayout, onClickPageLayout = onClickPageLayout,
onClickShiftPage = onClickShiftPage, onClickShiftPage = onClickShiftPage,
// SY <--
modifier = Modifier
.fillMaxWidth()
.background(backgroundColor)
.padding(horizontal = MaterialTheme.padding.small)
.windowInsetsPadding(WindowInsets.navigationBars),
) )
} }
} }
@@ -1,10 +1,7 @@
package eu.kanade.presentation.reader.appbars package eu.kanade.presentation.reader.appbars
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FormatListNumbered import androidx.compose.material.icons.outlined.FormatListNumbered
import androidx.compose.material.icons.outlined.Public import androidx.compose.material.icons.outlined.Public
@@ -15,9 +12,8 @@ import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
@@ -28,11 +24,10 @@ import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@Composable @Composable
fun BottomReaderBar( fun ReaderBottomBar(
// SY --> // SY -->
enabledButtons: ImmutableSet<String>, enabledButtons: ImmutableSet<String>,
// SY <-- // SY <--
backgroundColor: Color,
readingMode: ReadingMode, readingMode: ReadingMode,
onClickReadingMode: () -> Unit, onClickReadingMode: () -> Unit,
orientation: ReaderOrientation, orientation: ReaderOrientation,
@@ -51,12 +46,11 @@ fun BottomReaderBar(
onClickPageLayout: () -> Unit, onClickPageLayout: () -> Unit,
onClickShiftPage: () -> Unit, onClickShiftPage: () -> Unit,
// SY <-- // SY <--
modifier: Modifier = Modifier,
) { ) {
Row( Row(
modifier = Modifier modifier = modifier
.fillMaxWidth() .pointerInput(Unit) {},
.background(backgroundColor)
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
@@ -0,0 +1,78 @@
package eu.kanade.presentation.reader.appbars
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import eu.kanade.presentation.components.AppBar
@Composable
fun ReaderTopBar(
mangaTitle: String?,
chapterTitle: String?,
navigateUp: () -> Unit,
// bookmarked: Boolean,
// onToggleBookmarked: () -> Unit,
// onOpenInWebView: (() -> Unit)?,
// onOpenInBrowser: (() -> Unit)?,
// onShare: (() -> Unit)?,
modifier: Modifier = Modifier,
) {
AppBar(
modifier = modifier,
backgroundColor = Color.Transparent,
title = mangaTitle,
subtitle = chapterTitle,
navigateUp = navigateUp,
/* SY ->
actions = {
AppBarActions(
actions = persistentListOf<AppBar.AppBarAction>().builder()
.apply {
add(
AppBar.Action(
title = stringResource(
if (bookmarked) {
MR.strings.action_remove_bookmark
} else {
MR.strings.action_bookmark
},
),
icon = if (bookmarked) {
Icons.Outlined.Bookmark
} else {
Icons.Outlined.BookmarkBorder
},
onClick = onToggleBookmarked,
),
)
onOpenInWebView?.let {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_web_view),
onClick = it,
),
)
}
onOpenInBrowser?.let {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_open_in_browser),
onClick = it,
),
)
}
onShare?.let {
add(
AppBar.OverflowAction(
title = stringResource(MR.strings.action_share),
onClick = it,
),
)
}
}
.build(),
)
},
<- SY */
)
}
@@ -28,7 +28,7 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
pref = screenModel.preferences.customBrightness(), pref = screenModel.preferences.customBrightness(),
) )
/** /*
* Sets the brightness of the screen. Range is [-75, 100]. * Sets the brightness of the screen. Range is [-75, 100].
* From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness. * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
* From 1 to 100 it sets that value as brightness. * From 1 to 100 it sets that value as brightness.
@@ -1,5 +1,6 @@
package eu.kanade.presentation.reader.settings package eu.kanade.presentation.reader.settings
import androidx.activity.compose.LocalActivity
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -8,6 +9,7 @@ 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.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
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
@@ -85,10 +87,11 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
pref = screenModel.preferences.fullscreen(), pref = screenModel.preferences.fullscreen(),
) )
if (screenModel.hasDisplayCutout && screenModel.preferences.fullscreen().get()) { val isFullscreen by screenModel.preferences.fullscreen().collectAsState()
if (LocalActivity.current?.hasDisplayCutout() == true && isFullscreen) {
CheckboxItem( CheckboxItem(
label = stringResource(MR.strings.pref_cutout_short), label = stringResource(MR.strings.pref_cutout_short),
pref = screenModel.preferences.cutoutShort(), pref = screenModel.preferences.drawUnderCutout(),
) )
} }
@@ -122,7 +125,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
value = flashMillis / ReaderPreferences.MILLI_CONVERSION, value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
valueRange = 1..15, valueRange = 1..15,
label = stringResource(MR.strings.pref_flash_duration), label = stringResource(MR.strings.pref_flash_duration),
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), valueString = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) }, onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
pillColor = MaterialTheme.colorScheme.surfaceContainerHighest, pillColor = MaterialTheme.colorScheme.surfaceContainerHighest,
) )
@@ -130,7 +133,7 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
value = flashInterval, value = flashInterval,
valueRange = 1..10, valueRange = 1..10,
label = stringResource(MR.strings.pref_flash_page_interval), label = stringResource(MR.strings.pref_flash_page_interval),
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), valueString = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = { onChange = {
flashIntervalPref.set(it) flashIntervalPref.set(it)
}, },
@@ -196,7 +196,7 @@ private fun ColumnScope.WebtoonViewerSettings(screenModel: ReaderSettingsScreenM
value = webtoonSidePadding, value = webtoonSidePadding,
valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX }, valueRange = ReaderPreferences.let { it.WEBTOON_PADDING_MIN..it.WEBTOON_PADDING_MAX },
label = stringResource(MR.strings.pref_webtoon_side_padding), label = stringResource(MR.strings.pref_webtoon_side_padding),
valueText = numberFormat.format(webtoonSidePadding / 100f), valueString = numberFormat.format(webtoonSidePadding / 100f),
onChange = { onChange = {
screenModel.preferences.webtoonSidePadding().set(it) screenModel.preferences.webtoonSidePadding().set(it)
}, },
@@ -9,6 +9,7 @@ import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.theme.colorscheme.BaseColorScheme import eu.kanade.presentation.theme.colorscheme.BaseColorScheme
import eu.kanade.presentation.theme.colorscheme.CatppuccinColorScheme
import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme
import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme
import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme
@@ -77,6 +78,7 @@ private fun getThemeColorScheme(
private val colorSchemes: Map<AppTheme, BaseColorScheme> = mapOf( private val colorSchemes: Map<AppTheme, BaseColorScheme> = mapOf(
AppTheme.DEFAULT to TachiyomiColorScheme, AppTheme.DEFAULT to TachiyomiColorScheme,
AppTheme.CATPPUCCIN to CatppuccinColorScheme,
AppTheme.GREEN_APPLE to GreenAppleColorScheme, AppTheme.GREEN_APPLE to GreenAppleColorScheme,
AppTheme.LAVENDER to LavenderColorScheme, AppTheme.LAVENDER to LavenderColorScheme,
AppTheme.MIDNIGHT_DUSK to MidnightDuskColorScheme, AppTheme.MIDNIGHT_DUSK to MidnightDuskColorScheme,
@@ -0,0 +1,103 @@
package eu.kanade.presentation.theme.colorscheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
/**
* Colors for Catppuccin theme
* MIT License
* Copyright (c) 2021 Catppuccin
* https://catppuccin.com
* M3 colors generated by Material Theme Builder (https://goo.gle/material-theme-builder-web)
*
* Key colors (dark):
* Primary #CBA6F4
* Secondary #CBA6F4
* Tertiary #CBA6F4
* Neutral #181825
* Key colors (light):
* Primary #8839EF
* Secondary #8839EF
* Tertiary #8839EF
* Neutral #E6E9EF
*/
internal object CatppuccinColorScheme : BaseColorScheme() {
override val darkScheme = darkColorScheme(
primary = Color(0xFFCBA6F7),
onPrimary = Color(0xFF11111B),
primaryContainer = Color(0xFFCBA6F7),
onPrimaryContainer = Color(0xFF11111B),
secondary = Color(0xFFCBA6F7), // Unread badge
onSecondary = Color(0xFF11111B), // Unread badge text
secondaryContainer = Color(0xFF313244), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFFCBA6F7), // Navigation bar selector icon
tertiary = Color(0xFFCBA6F7), // Volume and brightness bars, Downloaded badge
onTertiary = Color(0xFF11111B), // Downloaded badge text
tertiaryContainer = Color(0xFF1E1E2E),
onTertiaryContainer = Color(0xFFCDD6F4),
error = Color(0xFFF38BA8),
onError = Color(0xFF11111B),
errorContainer = Color(0xFFFF0558),
onErrorContainer = Color(0xFFEF9FB4),
background = Color(0xFF181825),
onBackground = Color(0xFFCDD6F4),
surface = Color(0xFF181825),
onSurface = Color(0xFFCDD6F4),
surfaceVariant = Color(0xFF1E1E2E), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFFCDD6F4), // Button (unselected)
outline = Color(0xFFCBA6F7),
outlineVariant = Color(0xFF585B70), // Outlines for buttons
scrim = Color(0xFF11111B),
inverseSurface = Color(0xFFEFF1F5), // Snackbar or whatever they called
inverseOnSurface = Color(0xFF4C4F69), // Snackbar text
inversePrimary = Color(0xFF8839EF), // Snackbar accent
surfaceDim = Color(0xFF181825),
surfaceBright = Color(0xFF313244),
surfaceContainerLowest = Color(0xFF181825),
surfaceContainerLow = Color(0xFF1E1E2E), // Repo cards
surfaceContainer = Color(0xFF1E1E2E),
surfaceContainerHigh = Color(0xFF1E1E2E), // Filter menu
surfaceContainerHighest = Color(0xFF313244), // Untoggleg button bg
)
override val lightScheme = lightColorScheme(
primary = Color(0xFF8839EF),
onPrimary = Color(0xFFDCE0E8),
primaryContainer = Color(0xFF8839EF),
onPrimaryContainer = Color(0xFFDCE0E8),
secondary = Color(0xFF8839EF), // Unread badge
onSecondary = Color(0xFFDCE0E8), // Unread badge text
secondaryContainer = Color(0xFFCDD0DA), // Navigation bar selector pill & progress indicator (remaining)
onSecondaryContainer = Color(0xFF8839EF), // Navigation bar selector icon
tertiary = Color(0xFF8839EF), // Volume and brightness bars, Downloaded badge
onTertiary = Color(0xFFDCE0E8), // Downloaded badge text
tertiaryContainer = Color(0xFFEFF1F5),
onTertiaryContainer = Color(0xFF4C4F69),
error = Color(0xFFD20F39),
onError = Color(0xFFDCE0E8),
errorContainer = Color(0xFF68001C),
onErrorContainer = Color(0xFFD61C41),
background = Color(0xFFE6E9EF),
onBackground = Color(0xFF4C4F69),
surface = Color(0xFFE6E9EF),
onSurface = Color(0xFF4C4F69),
surfaceVariant = Color(0xFFEFF1F5), // Navigation bar background (ThemePrefWidget)
onSurfaceVariant = Color(0xFF4C4F69), // Button (unselected)
outline = Color(0xFF8839EF),
outlineVariant = Color(0xFFACB0BE), // Outlines for buttons
scrim = Color(0xFFDCE0E8),
inverseSurface = Color(0xFF1E1E2E), // Snackbar
inverseOnSurface = Color(0xFFCDD6F4), // Snackbar text
inversePrimary = Color(0xFFCBA6F7), // Snackbar accent
surfaceDim = Color(0xFFE6E9EF),
surfaceBright = Color(0xFFCDD0DA),
surfaceContainerLowest = Color(0xFFE6E9EF),
surfaceContainerLow = Color(0xFFEFF1F5), // Repo cards
surfaceContainer = Color(0xFFEFF1F5), // Navigation bar background
surfaceContainerHigh = Color(0xFFEFF1F5), // Filter menu
surfaceContainerHighest = Color(0xFFCDD0DA), // Untoggleg bg
)
}
@@ -1,15 +1,11 @@
package eu.kanade.presentation.track.components package eu.kanade.presentation.track.components
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -30,18 +26,13 @@ fun TrackLogoIcon(
Modifier Modifier
} }
Box( Image(
painter = painterResource(tracker.getLogo()),
contentDescription = tracker.name,
modifier = modifier modifier = modifier
.size(48.dp) .size(48.dp)
.background(color = Color(tracker.getLogoColor()), shape = MaterialTheme.shapes.medium) .clip(MaterialTheme.shapes.medium),
.padding(4.dp), )
contentAlignment = Alignment.Center,
) {
Image(
painter = painterResource(tracker.getLogo()),
contentDescription = tracker.name,
)
}
} }
@PreviewLightDark @PreviewLightDark
@@ -1,8 +1,6 @@
package eu.kanade.presentation.track.components package eu.kanade.presentation.track.components
import android.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.Tracker
import eu.kanade.test.DummyTracker import eu.kanade.test.DummyTracker
@@ -13,8 +11,6 @@ internal class TrackLogoIconPreviewProvider : PreviewParameterProvider<Tracker>
DummyTracker( DummyTracker(
id = 1L, id = 1L,
name = "Dummy Tracker", name = "Dummy Tracker",
valLogoColor = Color.rgb(18, 25, 35),
valLogo = R.drawable.ic_tracker_anilist,
), ),
) )
} }
@@ -1,46 +1,13 @@
package eu.kanade.presentation.util package eu.kanade.presentation.util
import android.annotation.SuppressLint import androidx.activity.compose.BackHandler
import androidx.activity.BackEventCompat
import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animate
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.spring
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.ScreenModelStore import cafe.adriel.voyager.core.model.ScreenModelStore
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
@@ -49,28 +16,18 @@ import cafe.adriel.voyager.core.screen.uniqueScreenKey
import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.core.stack.StackEvent
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.ScreenTransitionContent import cafe.adriel.voyager.transitions.ScreenTransitionContent
import eu.kanade.tachiyomi.util.view.getWindowRadius
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import soup.compose.material.motion.animation.materialSharedAxisXIn import soup.compose.material.motion.animation.materialSharedAxisX
import soup.compose.material.motion.animation.materialSharedAxisXOut
import soup.compose.material.motion.animation.rememberSlideDistance import soup.compose.material.motion.animation.rememberSlideDistance
import tachiyomi.presentation.core.util.PredictiveBack
import kotlin.coroutines.cancellation.CancellationException
import kotlin.math.absoluteValue
/** /**
* For invoking back press to the parent activity * For invoking back press to the parent activity
*/ */
@SuppressLint("ComposeCompositionLocalUsage")
val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null }
interface Tab : cafe.adriel.voyager.navigator.tab.Tab { interface Tab : cafe.adriel.voyager.navigator.tab.Tab {
@@ -103,278 +60,41 @@ interface AssistContentScreen {
fun onProvideAssistUrl(): String? fun onProvideAssistUrl(): String?
} }
@OptIn(InternalVoyagerApi::class)
@Composable @Composable
fun DefaultNavigatorScreenTransition( fun DefaultNavigatorScreenTransition(
navigator: Navigator, navigator: Navigator,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) { val slideDistance = rememberSlideDistance()
mutableStateOf(emptySet())
}
val currentScreens = navigator.items
DisposableEffect(currentScreens) {
onDispose {
val newScreenKeys = navigator.items.map { it.key }
screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys }
}
}
val slideDistance = rememberSlideDistance(slideDistance = 30.dp)
ScreenTransition( ScreenTransition(
navigator = navigator, navigator = navigator,
transition = {
materialSharedAxisX(
forward = navigator.lastEvent != StackEvent.Pop,
slideDistance = slideDistance,
)
},
modifier = modifier, modifier = modifier,
enterTransition = {
if (it == SwipeEdge.Right) {
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
} else {
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
}
},
exitTransition = {
if (it == SwipeEdge.Right) {
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
} else {
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
}
},
popEnterTransition = {
if (it == SwipeEdge.Right) {
materialSharedAxisXIn(forward = true, slideDistance = slideDistance)
} else {
materialSharedAxisXIn(forward = false, slideDistance = slideDistance)
}
},
popExitTransition = {
if (it == SwipeEdge.Right) {
materialSharedAxisXOut(forward = true, slideDistance = slideDistance)
} else {
materialSharedAxisXOut(forward = false, slideDistance = slideDistance)
}
},
content = { screen ->
if (this.transition.targetState == this.transition.currentState) {
LaunchedEffect(Unit) {
val newScreens = navigator.items.map { it.key }
val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens }
if (screensToDispose.isNotEmpty()) {
screensToDispose.forEach { navigator.dispose(it) }
navigator.clearEvent()
}
screenCandidatesToDispose.value = emptySet()
}
}
screen.Content()
},
) )
} }
enum class SwipeEdge {
Unknown,
Left,
Right,
}
private enum class AnimationType {
Pop,
Cancel,
}
@Composable @Composable
fun ScreenTransition( fun ScreenTransition(
navigator: Navigator, navigator: Navigator,
transition: AnimatedContentTransitionScope<Screen>.() -> ContentTransform,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
enterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = { fadeIn() },
exitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = { fadeOut() },
popEnterTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> EnterTransition = enterTransition,
popExitTransition: AnimatedContentTransitionScope<Screen>.(SwipeEdge) -> ExitTransition = exitTransition,
sizeTransform: (AnimatedContentTransitionScope<Screen>.() -> SizeTransform?)? = null,
flingAnimationSpec: () -> AnimationSpec<Float> = { spring(stiffness = Spring.StiffnessLow) },
content: ScreenTransitionContent = { it.Content() }, content: ScreenTransitionContent = { it.Content() },
) { ) {
val view = LocalView.current AnimatedContent(
val viewConfig = LocalViewConfiguration.current targetState = navigator.lastItem,
val scope = rememberCoroutineScope() transitionSpec = transition,
val state = remember {
ScreenTransitionState(
navigator = navigator,
scope = scope,
flingAnimationSpec = flingAnimationSpec(),
windowCornerRadius = view.getWindowRadius().toFloat(),
)
}
val transitionState = remember { SeekableTransitionState(navigator.lastItem) }
val transition = rememberTransition(transitionState = transitionState)
if (state.isPredictiveBack || state.isAnimating) {
LaunchedEffect(state.progress) {
if (!state.isPredictiveBack) return@LaunchedEffect
val previousEntry = navigator.items.getOrNull(navigator.size - 2)
if (previousEntry != null) {
transitionState.seekTo(fraction = state.progress, targetState = previousEntry)
}
}
} else {
LaunchedEffect(navigator) {
snapshotFlow { navigator.lastItem }
.collect {
state.cancelCancelAnimation()
if (it != transitionState.currentState) {
transitionState.animateTo(it)
} else {
transitionState.snapTo(it)
}
}
}
}
PredictiveBackHandler(enabled = navigator.canPop) { backEvent ->
state.cancelCancelAnimation()
var startOffset: Offset? = null
backEvent
.dropWhile {
if (startOffset == null) startOffset = Offset(it.touchX, it.touchY)
if (state.isAnimating) return@dropWhile true
// Touch slop check
val diff = Offset(it.touchX, it.touchY) - startOffset!!
diff.x.absoluteValue < viewConfig.touchSlop && diff.y.absoluteValue < viewConfig.touchSlop
}
.onCompletion {
if (it == null) {
state.finish()
} else {
state.cancel()
}
}
.collect {
state.setPredictiveBackProgress(
progress = it.progress,
swipeEdge = when (it.swipeEdge) {
BackEventCompat.EDGE_LEFT -> SwipeEdge.Left
BackEventCompat.EDGE_RIGHT -> SwipeEdge.Right
else -> SwipeEdge.Unknown
},
)
}
}
transition.AnimatedContent(
modifier = modifier, modifier = modifier,
transitionSpec = { label = "transition",
val pop = navigator.lastEvent == StackEvent.Pop || state.isPredictiveBack ) { screen ->
ContentTransform( navigator.saveableState("transition", screen) {
targetContentEnter = if (pop) { content(screen)
popEnterTransition(state.swipeEdge)
} else {
enterTransition(state.swipeEdge)
},
initialContentExit = if (pop) {
popExitTransition(state.swipeEdge)
} else {
exitTransition(state.swipeEdge)
},
targetContentZIndex = if (pop) 0f else 1f,
sizeTransform = sizeTransform?.invoke(this),
)
},
contentKey = { it.key },
) {
navigator.saveableState("transition", it) {
content(it)
} }
} }
}
BackHandler(enabled = navigator.canPop, onBack = navigator::pop)
@Stable
private class ScreenTransitionState(
private val navigator: Navigator,
private val scope: CoroutineScope,
private val flingAnimationSpec: AnimationSpec<Float>,
windowCornerRadius: Float,
) {
var isPredictiveBack: Boolean by mutableStateOf(false)
private set
var progress: Float by mutableFloatStateOf(0f)
private set
var swipeEdge: SwipeEdge by mutableStateOf(SwipeEdge.Unknown)
private set
private var animationJob: Pair<Job, AnimationType>? by mutableStateOf(null)
val isAnimating: Boolean
get() = animationJob?.first?.isActive == true
val windowCornerShape = RoundedCornerShape(windowCornerRadius)
private fun reset() {
this.isPredictiveBack = false
this.swipeEdge = SwipeEdge.Unknown
this.animationJob = null
}
fun setPredictiveBackProgress(progress: Float, swipeEdge: SwipeEdge) {
this.progress = lerp(0f, 0.65f, PredictiveBack.transform(progress))
this.swipeEdge = swipeEdge
this.isPredictiveBack = true
}
fun finish() {
if (!isPredictiveBack) {
navigator.pop()
return
}
animationJob = scope.launch {
try {
animate(
initialValue = progress,
targetValue = 1f,
animationSpec = flingAnimationSpec,
block = { i, _ -> progress = i },
)
navigator.pop()
} catch (e: CancellationException) {
// Cancelled
progress = 0f
} finally {
reset()
}
} to AnimationType.Pop
}
fun cancel() {
if (!isPredictiveBack) {
return
}
animationJob = scope.launch {
try {
animate(
initialValue = progress,
targetValue = 0f,
animationSpec = flingAnimationSpec,
block = { i, _ -> progress = i },
)
} catch (e: CancellationException) {
// Cancelled
progress = 1f
} finally {
reset()
}
} to AnimationType.Cancel
}
fun cancelCancelAnimation() {
if (animationJob?.second == AnimationType.Cancel) {
animationJob?.first?.cancel()
animationJob = null
}
}
}
private fun screenCandidatesToDisposeSaver(): Saver<MutableState<Set<Screen>>, List<Screen>> {
return Saver(
save = { it.value.toList() },
restore = { mutableStateOf(it.toSet()) },
)
} }
@@ -2,8 +2,10 @@ package eu.kanade.presentation.webview
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.graphics.Bitmap import android.graphics.Bitmap
import android.os.Message
import android.webkit.WebResourceRequest import android.webkit.WebResourceRequest
import android.webkit.WebView import android.webkit.WebView
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -19,6 +21,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -26,17 +29,23 @@ 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.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.stack.mutableStateStackOf
import com.kevinnzou.web.AccompanistWebChromeClient
import com.kevinnzou.web.AccompanistWebViewClient import com.kevinnzou.web.AccompanistWebViewClient
import com.kevinnzou.web.LoadingState import com.kevinnzou.web.LoadingState
import com.kevinnzou.web.WebContent
import com.kevinnzou.web.WebView import com.kevinnzou.web.WebView
import com.kevinnzou.web.rememberWebViewNavigator import com.kevinnzou.web.WebViewNavigator
import com.kevinnzou.web.rememberWebViewState import com.kevinnzou.web.WebViewState
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.WarningBanner import eu.kanade.presentation.components.WarningBanner
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getHtml import eu.kanade.tachiyomi.util.system.getHtml
import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.setDefaultSettings
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -44,6 +53,18 @@ import kotlinx.coroutines.launch
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
class WebViewWindow(webContent: WebContent, val navigator: WebViewNavigator) {
var state by mutableStateOf(WebViewState(webContent))
var popupMessage: Message? = null
private set
var webView: WebView? = null
constructor(popupMessage: Message, navigator: WebViewNavigator) : this(WebContent.NavigatorOnly, navigator) {
this.popupMessage = popupMessage
}
}
@Composable @Composable
fun WebViewScreenContent( fun WebViewScreenContent(
onNavigateUp: () -> Unit, onNavigateUp: () -> Unit,
@@ -55,8 +76,20 @@ fun WebViewScreenContent(
headers: Map<String, String> = emptyMap(), headers: Map<String, String> = emptyMap(),
onUrlChange: (String) -> Unit = {}, onUrlChange: (String) -> Unit = {},
) { ) {
val state = rememberWebViewState(url = url, additionalHttpHeaders = headers) val coroutineScope = rememberCoroutineScope()
val navigator = rememberWebViewNavigator()
val windowStack = remember {
mutableStateStackOf(
WebViewWindow(
WebContent.Url(url = url, additionalHttpHeaders = headers),
WebViewNavigator(coroutineScope),
),
)
}
val currentWindow = windowStack.lastItemOrNull!!
val navigator = currentWindow.navigator
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -97,31 +130,67 @@ fun WebViewScreenContent(
view: WebView?, view: WebView?,
request: WebResourceRequest?, request: WebResourceRequest?,
): Boolean { ): Boolean {
request?.let { val url = request?.url?.toString() ?: return false
// Don't attempt to open blobs as webpages
if (it.url.toString().startsWith("blob:http")) {
return false
}
// Ignore intents urls // Ignore intents urls
if (it.url.toString().startsWith("intent://")) { if (url.startsWith("intent://")) return true
// Only open valid web urls
if (url.startsWith("http") || url.startsWith("https")) {
if (url != view?.url) {
view?.loadUrl(url, headers)
return true return true
} }
// Continue with request, but with custom headers
view?.loadUrl(it.url.toString(), headers)
} }
return super.shouldOverrideUrlLoading(view, request)
return false
} }
} }
} }
val webChromeClient = remember {
object : AccompanistWebChromeClient() {
override fun onCreateWindow(
view: WebView,
isDialog: Boolean,
isUserGesture: Boolean,
resultMsg: Message,
): Boolean {
// if it wasn't initiated by a user gesture, we should ignore it like a normal browser would
if (isUserGesture) {
windowStack.push(WebViewWindow(resultMsg, WebViewNavigator(coroutineScope)))
return true
}
return false
}
}
}
fun initializePopup(webView: WebView, message: Message): WebView {
val transport = message.obj as WebView.WebViewTransport
transport.webView = webView
message.sendToTarget()
return webView
}
val popState = remember<() -> Unit> {
{
if (windowStack.size == 1) {
onNavigateUp()
} else {
windowStack.pop()
}
}
}
BackHandler(windowStack.size > 1, popState)
Scaffold( Scaffold(
topBar = { topBar = {
Box { Box {
Column { Column {
AppBar( AppBar(
title = state.pageTitle ?: initialTitle, title = currentWindow.state.pageTitle ?: initialTitle,
subtitle = currentUrl, subtitle = currentUrl,
navigateUp = onNavigateUp, navigateUp = onNavigateUp,
navigationIcon = Icons.Outlined.Close, navigationIcon = Icons.Outlined.Close,
@@ -164,7 +233,18 @@ fun WebViewScreenContent(
title = stringResource(MR.strings.pref_clear_cookies), title = stringResource(MR.strings.pref_clear_cookies),
onClick = { onClearCookies(currentUrl) }, onClick = { onClearCookies(currentUrl) },
), ),
), ).builder().apply {
if (windowStack.size > 1) {
add(
0,
AppBar.Action(
title = stringResource(MR.strings.action_webview_close_tab),
icon = ImageVector.vectorResource(R.drawable.ic_tab_close_24px),
onClick = popState,
),
)
}
}.build(),
) )
}, },
) )
@@ -186,7 +266,7 @@ fun WebViewScreenContent(
} }
} }
} }
when (val loadingState = state.loadingState) { when (val loadingState = currentWindow.state.loadingState) {
is LoadingState.Initializing -> LinearProgressIndicator( is LoadingState.Initializing -> LinearProgressIndicator(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -203,27 +283,55 @@ fun WebViewScreenContent(
} }
}, },
) { contentPadding -> ) { contentPadding ->
WebView( // We need to key the WebView composable to the window object since simply updating the WebView composable will
state = state, // not cause it to re-invoke the WebView factory and render the new current window's WebView. This lets us
modifier = Modifier // completely reset the WebView composable when the current window switches.
.fillMaxSize() key(currentWindow) {
.padding(contentPadding), WebView(
navigator = navigator, state = currentWindow.state,
onCreated = { webView -> modifier = Modifier
webView.setDefaultSettings() .fillMaxSize()
.padding(contentPadding),
navigator = navigator,
onCreated = { webView ->
webView.setDefaultSettings()
// Debug mode (chrome://inspect/#devices) // Debug mode (chrome://inspect/#devices)
if (BuildConfig.DEBUG && if (BuildConfig.DEBUG &&
0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE 0 != webView.context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE
) { ) {
WebView.setWebContentsDebuggingEnabled(true) WebView.setWebContentsDebuggingEnabled(true)
} }
headers["user-agent"]?.let { headers["user-agent"]?.let {
webView.settings.userAgentString = it webView.settings.userAgentString = it
} }
}, },
client = webClient, onDispose = { webView ->
) val window = windowStack.items.find { it.webView == webView }
if (window == null) {
// If we couldn't find any window on the stack that owns this WebView, it means that we can
// safely dispose of it because the window containing it has been closed.
webView.destroy()
} else {
// The composable is being disposed but the WebView object is not.
// When the WebView element is recomposed, we will want the WebView to resume from its state
// before it was unmounted, we won't want it to reset back to its original target.
window.state.content = WebContent.NavigatorOnly
}
},
client = webClient,
chromeClient = webChromeClient,
factory = { context ->
currentWindow.webView
?: WebView(context).also { webView ->
currentWindow.webView = webView
currentWindow.popupMessage?.let {
initializePopup(webView, it)
}
}
},
)
}
} }
} }
+13 -5
View File
@@ -19,6 +19,7 @@ import androidx.work.Configuration
import androidx.work.WorkManager import androidx.work.WorkManager
import coil3.ImageLoader import coil3.ImageLoader
import coil3.SingletonImageLoader import coil3.SingletonImageLoader
import coil3.memory.MemoryCache
import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowRgb565 import coil3.request.allowRgb565
import coil3.request.crossfade import coil3.request.crossfade
@@ -130,7 +131,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
// SY <-- // SY <--
setupExhLogging() // EXH logging setupExhLogging() // EXH logging
LogcatLogger.install(XLogLogcatLogger()) // SY Redirect Logcat to XLog LogcatLogger.install()
LogcatLogger.loggers += XLogLogcatLogger() // SY Redirect Logcat to XLog
setupNotificationChannels() setupNotificationChannels()
@@ -155,7 +157,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
val pendingIntent = PendingIntent.getBroadcast( val pendingIntent = PendingIntent.getBroadcast(
this@App, this@App,
0, 0,
Intent(ACTION_DISABLE_INCOGNITO_MODE), Intent(ACTION_DISABLE_INCOGNITO_MODE).setPackage(BuildConfig.APPLICATION_ID),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
) )
setContentIntent(pendingIntent) setContentIntent(pendingIntent)
@@ -246,6 +248,12 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
// SY <-- // SY <--
} }
memoryCache(
MemoryCache.Builder()
.maxSizePercent(context)
.build(),
)
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())
@@ -278,8 +286,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
// Override the value passed as X-Requested-With in WebView requests // Override the value passed as X-Requested-With in WebView requests
val stackTrace = Looper.getMainLooper().thread.stackTrace val stackTrace = Looper.getMainLooper().thread.stackTrace
val isChromiumCall = stackTrace.any { trace -> val isChromiumCall = stackTrace.any { trace ->
trace.className.equals("org.chromium.base.BuildInfo", ignoreCase = true) && trace.className.lowercase() in setOf("org.chromium.base.buildinfo", "org.chromium.base.apkinfo") &&
setOf("getAll", "getPackageName", "<init>").any { trace.methodName.equals(it, ignoreCase = true) } trace.methodName.lowercase() in setOf("getall", "getpackagename", "<init>")
} }
if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext) if (isChromiumCall) return WebViewUtil.spoofedPackageName(applicationContext)
@@ -328,7 +336,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
return super.generateFileName( return super.generateFileName(
logLevel, logLevel,
timestamp, timestamp,
) + "-${BuildConfig.BUILD_TYPE}.log" ) + "-${BuildConfig.BUILD_TYPE}.txt"
} }
} }
flattener { timeMillis, level, tag, message -> flattener { timeMillis, level, tag, message ->
@@ -118,13 +118,15 @@ class MangaBackupCreator(
private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/* SY <-- */) = private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/* SY <-- */) =
BackupManga( BackupManga(
url = this.url, url = this.url,
title = this.title, // SY -->
artist = this.artist, title = this.ogTitle,
author = this.author, artist = this.ogArtist,
description = this.description, author = this.ogAuthor,
genre = this.genre.orEmpty(), description = this.ogDescription,
status = this.status.toInt(), genre = this.ogGenre.orEmpty(),
thumbnailUrl = this.thumbnailUrl, status = this.ogStatus.toInt(),
thumbnailUrl = this.ogThumbnailUrl,
// SY <--
favorite = this.favorite, favorite = this.favorite,
source = this.source, source = this.source,
dateAdded = this.dateAdded, dateAdded = this.dateAdded,
@@ -136,6 +138,7 @@ private fun Manga.toBackupManga(/* SY --> */customMangaInfo: CustomMangaInfo?/*
favoriteModifiedAt = this.favoriteModifiedAt, favoriteModifiedAt = this.favoriteModifiedAt,
version = this.version, version = this.version,
notes = this.notes, notes = this.notes,
initialized = this.initialized,
// SY --> // SY -->
).also { backupManga -> ).also { backupManga ->
customMangaInfo?.let { customMangaInfo?.let {
@@ -42,6 +42,7 @@ data class BackupManga(
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(), @ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
@ProtoNumber(109) var version: Long = 0, @ProtoNumber(109) var version: Long = 0,
@ProtoNumber(110) var notes: String = "", @ProtoNumber(110) var notes: String = "",
@ProtoNumber(111) var initialized: Boolean = false,
// SY specific values // SY specific values
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(), @ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
@@ -80,6 +81,7 @@ data class BackupManga(
favoriteModifiedAt = this@BackupManga.favoriteModifiedAt, favoriteModifiedAt = this@BackupManga.favoriteModifiedAt,
version = this@BackupManga.version, version = this@BackupManga.version,
notes = this@BackupManga.notes, notes = this@BackupManga.notes,
initialized = this@BackupManga.initialized,
) )
} }
} }
@@ -139,13 +139,15 @@ class MangaRestorer(
mangasQueries.update( mangasQueries.update(
source = manga.source, source = manga.source,
url = manga.url, url = manga.url,
artist = manga.artist, // SY -->
author = manga.author, artist = manga.ogArtist,
description = manga.description, author = manga.ogAuthor,
genre = manga.genre?.joinToString(separator = ", "), description = manga.ogDescription,
title = manga.title, genre = manga.ogGenre?.joinToString(separator = ", "),
status = manga.status, title = manga.ogTitle,
thumbnailUrl = manga.thumbnailUrl, status = manga.ogStatus,
thumbnailUrl = manga.ogThumbnailUrl,
// SY <--
favorite = manga.favorite, favorite = manga.favorite,
lastUpdate = manga.lastUpdate, lastUpdate = manga.lastUpdate,
nextUpdate = null, nextUpdate = null,
@@ -169,9 +171,7 @@ class MangaRestorer(
manga: Manga, manga: Manga,
): Manga { ): Manga {
return manga.copy( return manga.copy(
initialized = manga.description != null,
id = insertManga(manga), id = insertManga(manga),
version = manga.version,
) )
} }
@@ -275,13 +275,15 @@ class MangaRestorer(
mangasQueries.insert( mangasQueries.insert(
source = manga.source, source = manga.source,
url = manga.url, url = manga.url,
artist = manga.artist, // SY -->
author = manga.author, artist = manga.ogArtist,
description = manga.description, author = manga.ogAuthor,
genre = manga.genre, description = manga.ogDescription,
title = manga.title, genre = manga.ogGenre,
status = manga.status, title = manga.ogTitle,
thumbnailUrl = manga.thumbnailUrl, status = manga.ogStatus,
thumbnailUrl = manga.ogThumbnailUrl,
// SY <--
favorite = manga.favorite, favorite = manga.favorite,
lastUpdate = manga.lastUpdate, lastUpdate = manga.lastUpdate,
nextUpdate = 0L, nextUpdate = 0L,
@@ -458,6 +460,7 @@ class MangaRestorer(
} }
// SY --> // SY -->
/** /**
* Restore the categories from Json * Restore the categories from Json
* *
@@ -13,7 +13,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority import logcat.LogPriority
import okhttp3.Response import okhttp3.Response
@@ -148,7 +147,7 @@ class ChapterCache(
fun isImageInCache(imageUrl: String): Boolean { fun isImageInCache(imageUrl: String): Boolean {
return try { return try {
diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)).use { it != null } diskCache.get(DiskUtil.hashKeyForDisk(imageUrl)).use { it != null }
} catch (e: IOException) { } catch (_: IOException) {
false false
} }
} }
@@ -180,7 +179,7 @@ class ChapterCache(
try { try {
// Get editor from md5 key. // Get editor from md5 key.
val key = DiskUtil.hashKeyForDisk(imageUrl) val key = DiskUtil.hashKeyForDisk(imageUrl)
editor = diskCache.edit(key) ?: throw IOException("Unable to edit key") editor = diskCache.edit(key) ?: return
// Get OutputStream and write image with Okio. // Get OutputStream and write image with Okio.
response.body.source().saveTo(editor.newOutputStream(0)) response.body.source().saveTo(editor.newOutputStream(0))
@@ -6,7 +6,6 @@ import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.source.PagePreviewPage import eu.kanade.tachiyomi.source.PagePreviewPage
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority import logcat.LogPriority
import okio.Source import okio.Source
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.data.cache.PagePreviewCache
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.PagePreviewSource import eu.kanade.tachiyomi.source.PagePreviewSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import exh.source.getMainSource
import logcat.LogPriority import logcat.LogPriority
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.Call import okhttp3.Call
@@ -249,7 +250,7 @@ class PagePreviewFetcher(
isInCache = { pagePreviewCache.isImageInCache(data.imageUrl) }, isInCache = { pagePreviewCache.isImageInCache(data.imageUrl) },
writeToCache = { pagePreviewCache.putImageToCache(data.imageUrl, it) }, writeToCache = { pagePreviewCache.putImageToCache(data.imageUrl, it) },
diskCacheKeyLazy = lazy { imageLoader.components.key(data, options)!! }, diskCacheKeyLazy = lazy { imageLoader.components.key(data, options)!! },
sourceLazy = lazy { sourceManager.get(data.source) as? PagePreviewSource }, sourceLazy = lazy { sourceManager.get(data.source)?.getMainSource<PagePreviewSource>() },
callFactoryLazy = callFactoryLazy, callFactoryLazy = callFactoryLazy,
imageLoader = imageLoader, imageLoader = imageLoader,
) )
@@ -128,6 +128,7 @@ class DownloadCache(
* *
* @param chapterName the name of the chapter to query. * @param chapterName the name of the chapter to query.
* @param chapterScanlator scanlator of the chapter to query * @param chapterScanlator scanlator of the chapter to query
* @param chapterUrl the url of the chapter to query
* @param mangaTitle the title of the manga to query. * @param mangaTitle the title of the manga to query.
* @param sourceId the id of the source of the chapter. * @param sourceId the id of the source of the chapter.
* @param skipCache whether to skip the directory cache and check in the filesystem. * @param skipCache whether to skip the directory cache and check in the filesystem.
@@ -135,13 +136,14 @@ class DownloadCache(
fun isChapterDownloaded( fun isChapterDownloaded(
chapterName: String, chapterName: String,
chapterScanlator: String?, chapterScanlator: String?,
chapterUrl: String,
mangaTitle: String, mangaTitle: String,
sourceId: Long, sourceId: Long,
skipCache: Boolean, skipCache: Boolean,
): Boolean { ): Boolean {
if (skipCache) { if (skipCache) {
val source = sourceManager.getOrStub(sourceId) val source = sourceManager.getOrStub(sourceId)
return provider.findChapterDir(chapterName, chapterScanlator, mangaTitle, source) != null return provider.findChapterDir(chapterName, chapterScanlator, chapterUrl, mangaTitle, source) != null
} }
renewCache() renewCache()
@@ -153,6 +155,7 @@ class DownloadCache(
return provider.getValidChapterDirNames( return provider.getValidChapterDirNames(
chapterName, chapterName,
chapterScanlator, chapterScanlator,
chapterUrl,
).any { it in mangaDir.chapterDirs } ).any { it in mangaDir.chapterDirs }
} }
} }
@@ -239,7 +242,7 @@ class DownloadCache(
/* SY --> */ manga.ogTitle, /* SY <-- */ /* SY --> */ manga.ogTitle, /* SY <-- */
), ),
] ?: return ] ?: return
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { provider.getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).forEach {
if (it in mangaDir.chapterDirs) { if (it in mangaDir.chapterDirs) {
mangaDir.chapterDirs -= it mangaDir.chapterDirs -= it
} }
@@ -279,7 +282,7 @@ class DownloadCache(
), ),
] ?: return ] ?: return
chapters.forEach { chapter -> chapters.forEach { chapter ->
provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { provider.getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).forEach {
if (it in mangaDir.chapterDirs) { if (it in mangaDir.chapterDirs) {
mangaDir.chapterDirs -= it mangaDir.chapterDirs -= it
} }
@@ -167,6 +167,7 @@ class DownloadManager(
val chapterDir = provider.findChapterDir( val chapterDir = provider.findChapterDir(
chapter.name, chapter.name,
chapter.scanlator, chapter.scanlator,
chapter.url,
/* SY --> */ manga.ogTitle /* SY <-- */, /* SY --> */ manga.ogTitle /* SY <-- */,
source, source,
) )
@@ -195,11 +196,12 @@ class DownloadManager(
fun isChapterDownloaded( fun isChapterDownloaded(
chapterName: String, chapterName: String,
chapterScanlator: String?, chapterScanlator: String?,
chapterUrl: String,
mangaTitle: String, mangaTitle: String,
sourceId: Long, sourceId: Long,
skipCache: Boolean = false, skipCache: Boolean = false,
): Boolean { ): Boolean {
return cache.isChapterDownloaded(chapterName, chapterScanlator, mangaTitle, sourceId, skipCache) return cache.isChapterDownloaded(chapterName, chapterScanlator, chapterUrl, mangaTitle, sourceId, skipCache)
} }
/** /**
@@ -291,6 +293,7 @@ class DownloadManager(
} }
// SY --> // SY -->
/** /**
* return the list of all manga folders * return the list of all manga folders
*/ */
@@ -439,7 +442,7 @@ class DownloadManager(
* @param newChapter the target chapter with the new name. * @param newChapter the target chapter with the new name.
*/ */
suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator) val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator, oldChapter.url)
val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrElse { e -> val mangaDir = provider.getMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source).getOrElse { e ->
logcat(LogPriority.ERROR, e) { "Manga download folder doesn't exist. Skipping renaming after source sync" } logcat(LogPriority.ERROR, e) { "Manga download folder doesn't exist. Skipping renaming after source sync" }
return return
@@ -450,7 +453,7 @@ class DownloadManager(
.mapNotNull { mangaDir.findFile(it) } .mapNotNull { mangaDir.findFile(it) }
.firstOrNull() ?: return .firstOrNull() ?: return
var newName = provider.getChapterDirName(newChapter.name, newChapter.scanlator) var newName = provider.getChapterDirName(newChapter.name, newChapter.scanlator, newChapter.url)
if (oldDownload.isFile && oldDownload.extension == "cbz") { if (oldDownload.isFile && oldDownload.extension == "cbz") {
newName += ".cbz" newName += ".cbz"
} }
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
@@ -3,12 +3,15 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context import android.content.Context
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.Hash.md5
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.displayablePath import tachiyomi.core.common.storage.displayablePath
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.storage.service.StorageManager import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@@ -25,6 +28,8 @@ import java.io.IOException
class DownloadProvider( class DownloadProvider(
private val context: Context, private val context: Context,
private val storageManager: StorageManager = Injekt.get(), private val storageManager: StorageManager = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
) { ) {
private val downloadsDir: UniFile? private val downloadsDir: UniFile?
@@ -96,9 +101,15 @@ class DownloadProvider(
* @param mangaTitle the title of the manga to query. * @param mangaTitle the title of the manga to query.
* @param source the source of the chapter. * @param source the source of the chapter.
*/ */
fun findChapterDir(chapterName: String, chapterScanlator: String?, mangaTitle: String, source: Source): UniFile? { fun findChapterDir(
chapterName: String,
chapterScanlator: String?,
chapterUrl: String,
mangaTitle: String,
source: Source,
): UniFile? {
val mangaDir = findMangaDir(mangaTitle, source) val mangaDir = findMangaDir(mangaTitle, source)
return getValidChapterDirNames(chapterName, chapterScanlator).asSequence() return getValidChapterDirNames(chapterName, chapterScanlator, chapterUrl).asSequence()
.mapNotNull { mangaDir?.findFile(it) } .mapNotNull { mangaDir?.findFile(it) }
.firstOrNull() .firstOrNull()
} }
@@ -113,13 +124,14 @@ class DownloadProvider(
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): Pair<UniFile?, List<UniFile>> { fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): Pair<UniFile?, List<UniFile>> {
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList() val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList()
return mangaDir to chapters.mapNotNull { chapter -> return mangaDir to chapters.mapNotNull { chapter ->
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence() getValidChapterDirNames(chapter.name, chapter.scanlator, chapter.url).asSequence()
.mapNotNull { mangaDir.findFile(it) } .mapNotNull { mangaDir.findFile(it) }
.firstOrNull() .firstOrNull()
} }
} }
// SY --> // SY -->
/** /**
* Returns a list of all files in manga directory * Returns a list of all files in manga directory
* *
@@ -135,7 +147,7 @@ class DownloadProvider(
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return emptyList() val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return emptyList()
return mangaDir.listFiles().orEmpty().asList().filter { return mangaDir.listFiles().orEmpty().asList().filter {
chapters.find { chp -> chapters.find { chp ->
getValidChapterDirNames(chp.name, chp.scanlator).any { dir -> getValidChapterDirNames(chp.name, chp.scanlator, chp.url).any { dir ->
mangaDir.findFile(dir) != null mangaDir.findFile(dir) != null
} }
} == null || } == null ||
@@ -150,7 +162,10 @@ class DownloadProvider(
* @param source the source to query. * @param source the source to query.
*/ */
fun getSourceDirName(source: Source): String { fun getSourceDirName(source: Source): String {
return DiskUtil.buildValidFilename(source.toString()) return DiskUtil.buildValidFilename(
source.toString(),
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
)
} }
/** /**
@@ -159,23 +174,77 @@ class DownloadProvider(
* @param mangaTitle the title of the manga to query. * @param mangaTitle the title of the manga to query.
*/ */
fun getMangaDirName(mangaTitle: String): String { fun getMangaDirName(mangaTitle: String): String {
return DiskUtil.buildValidFilename(mangaTitle) return DiskUtil.buildValidFilename(
mangaTitle,
disallowNonAscii = libraryPreferences.disallowNonAsciiFilenames().get(),
)
} }
/** /**
* Returns the chapter directory name for a chapter. * Returns the chapter directory name for a chapter.
* *
* @param chapterName the name of the chapter to query. * @param chapterName the name of the chapter to query.
* @param chapterScanlator scanlator of the chapter to query * @param chapterScanlator scanlator of the chapter to query.
* @param chapterUrl url of the chapter to query.
*/ */
fun getChapterDirName(chapterName: String, chapterScanlator: String?): String { fun getChapterDirName(
val newChapterName = sanitizeChapterName(chapterName) chapterName: String,
return DiskUtil.buildValidFilename( chapterScanlator: String?,
chapterUrl: String,
disallowNonAsciiFilenames: Boolean = libraryPreferences.disallowNonAsciiFilenames().get(),
includeChapterUrlHash: Boolean = downloadPreferences.includeChapterUrlHash().get(),
): String {
var dirName = sanitizeChapterName(chapterName)
if (!chapterScanlator.isNullOrBlank()) {
dirName = chapterScanlator + "_" + dirName
}
// Subtract 7 bytes for hash and underscore, 4 bytes for .cbz
dirName = DiskUtil.buildValidFilename(dirName, DiskUtil.MAX_FILE_NAME_BYTES - 11, disallowNonAsciiFilenames)
if (includeChapterUrlHash) dirName += "_" + md5(chapterUrl).take(6)
return dirName
}
/**
* Returns list of names that might have been previously used as
* the directory name for a chapter.
* Add to this list if naming pattern ever changes.
*
* @param chapterName the name of the chapter to query.
* @param chapterScanlator scanlator of the chapter to query.
* @param chapterUrl url of the chapter to query.
*/
private fun getLegacyChapterDirNames(
chapterName: String,
chapterScanlator: String?,
chapterUrl: String,
): List<String> {
val sanitizedChapterName = sanitizeChapterName(chapterName)
val chapterNameV1 = DiskUtil.buildValidFilename(
when { when {
!chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$newChapterName" !chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$sanitizedChapterName"
else -> newChapterName else -> sanitizedChapterName
}, },
) )
// Get the filename that would be generated if the user were
// using the other value for the disallow non-ASCII
// filenames setting. This ensures that chapters downloaded
// before the user changed the setting can still be found.
val otherChapterDirName =
getChapterDirName(
chapterName,
chapterScanlator,
chapterUrl,
!libraryPreferences.disallowNonAsciiFilenames().get(),
!downloadPreferences.includeChapterUrlHash().get(),
)
return buildList(2) {
// Chapter name without hash (unable to handle duplicate
// chapter names)
add(chapterNameV1)
add(otherChapterDirName)
}
} }
/** /**
@@ -190,22 +259,22 @@ class DownloadProvider(
} }
fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean { fun isChapterDirNameChanged(oldChapter: Chapter, newChapter: Chapter): Boolean {
return oldChapter.name != newChapter.name || return getChapterDirName(oldChapter.name, oldChapter.scanlator, oldChapter.url) !=
oldChapter.scanlator?.takeIf { it.isNotBlank() } != newChapter.scanlator?.takeIf { it.isNotBlank() } getChapterDirName(newChapter.name, newChapter.scanlator, newChapter.url)
} }
/** /**
* Returns valid downloaded chapter directory names. * Returns valid downloaded chapter directory names.
* *
* @param chapterName the name of the chapter to query. * @param chapter the domain chapter object.
* @param chapterScanlator scanlator of the chapter to query
*/ */
fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?): List<String> { fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?, chapterUrl: String): List<String> {
val chapterDirName = getChapterDirName(chapterName, chapterScanlator) val chapterDirName = getChapterDirName(chapterName, chapterScanlator, chapterUrl)
return buildList(4) { val legacyChapterDirNames = getLegacyChapterDirNames(chapterName, chapterScanlator, chapterUrl)
return buildList {
// Folder of images // Folder of images
add(chapterDirName) add(chapterDirName)
// Archived chapters // Archived chapters
add("$chapterDirName.cbz") add("$chapterDirName.cbz")
@@ -217,6 +286,12 @@ class DownloadProvider(
// Legacy chapter directory name used in v0.9.2 and before // Legacy chapter directory name used in v0.9.2 and before
add(DiskUtil.buildValidFilename(chapterName)) add(DiskUtil.buildValidFilename(chapterName))
} }
// any legacy names
legacyChapterDirNames.forEach {
add(it)
add("$it.cbz")
}
} }
} }
} }
@@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import tachiyomi.domain.chapter.interactor.GetChapter import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
@@ -201,15 +201,17 @@ class Downloader(
if (isRunning) return if (isRunning) return
downloaderJob = scope.launch { downloaderJob = scope.launch {
val activeDownloadsFlow = queueState.transformLatest { queue -> val activeDownloadsFlow = combine(
queueState,
downloadPreferences.parallelSourceLimit().changes(),
) { a, b -> a to b }.transformLatest { (queue, parallelCount) ->
while (true) { while (true) {
val activeDownloads = queue.asSequence() val activeDownloads = queue.asSequence()
// Ignore completed downloads, leave them in the queue // Ignore completed downloads, leave them in the queue
.filter { it.status.value <= Download.State.DOWNLOADING.value } .filter { it.status.value <= Download.State.DOWNLOADING.value }
.groupBy { it.source } .groupBy { it.source }
.toList() .toList()
// Concurrently download from 5 different sources .take(parallelCount)
.take(5)
.map { (_, downloads) -> downloads.first() } .map { (_, downloads) -> downloads.first() }
emit(activeDownloads) emit(activeDownloads)
@@ -221,7 +223,8 @@ class Downloader(
}.filter { it } }.filter { it }
activeDownloadsErroredFlow.first() activeDownloadsErroredFlow.first()
} }
}.distinctUntilChanged() }
.distinctUntilChanged()
// Use supervisorScope to cancel child jobs when the downloader job is cancelled // Use supervisorScope to cancel child jobs when the downloader job is cancelled
supervisorScope { supervisorScope {
@@ -285,7 +288,13 @@ class Downloader(
val chaptersToQueue = chapters.asSequence() val chaptersToQueue = chapters.asSequence()
// Filter out those already downloaded. // Filter out those already downloaded.
.filter { .filter {
provider.findChapterDir(it.name, it.scanlator, /* SY --> */ manga.ogTitle /* SY <-- */, source) == null provider.findChapterDir(
it.name,
it.scanlator,
it.url,
/* SY --> */ manga.ogTitle, /* SY <-- */
source,
) == null
} }
// Add chapters to queue from the start. // Add chapters to queue from the start.
.sortedByDescending { it.sourceOrder } .sortedByDescending { it.sourceOrder }
@@ -311,7 +320,10 @@ class Downloader(
maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD
) { ) {
notifier.onWarning( notifier.onWarning(
context.stringResource(MR.strings.download_queue_size_warning), context.stringResource(
MR.strings.download_queue_size_warning,
context.stringResource(MR.strings.app_name),
),
WARNING_NOTIF_TIMEOUT_MS, WARNING_NOTIF_TIMEOUT_MS,
NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL), NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL),
) )
@@ -327,11 +339,12 @@ class Downloader(
* @param download the chapter to be downloaded. * @param download the chapter to be downloaded.
*/ */
private suspend fun downloadChapter(download: Download) { private suspend fun downloadChapter(download: Download) {
val mangaDir = provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source).getOrElse { e -> val mangaDir =
download.status = Download.State.ERROR provider.getMangaDir(/* SY --> */ download.manga.ogTitle /* SY <-- */, download.source).getOrElse { e ->
notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id) download.status = Download.State.ERROR
return notifier.onError(e.message, download.chapter.name, download.manga.title, download.manga.id)
} return
}
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir) val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) { if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
@@ -345,7 +358,11 @@ class Downloader(
return return
} }
val chapterDirname = provider.getChapterDirName(download.chapter.name, download.chapter.scanlator) val chapterDirname = provider.getChapterDirName(
download.chapter.name,
download.chapter.scanlator,
download.chapter.url,
)
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)!! val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)!!
try { try {
@@ -377,24 +394,23 @@ class Downloader(
download.status = Download.State.DOWNLOADING download.status = Download.State.DOWNLOADING
// Start downloading images, consider we can have downloaded images already // Start downloading images, consider we can have downloaded images already
// Concurrently do 2 pages at a time pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit().get()) { page ->
pageList.asFlow() flow {
.flatMapMerge(concurrency = 2) { page -> // Fetch image URL if necessary
flow { if (page.imageUrl.isNullOrEmpty()) {
// Fetch image URL if necessary page.status = Page.State.LoadPage
if (page.imageUrl.isNullOrEmpty()) { try {
page.status = Page.State.LoadPage page.imageUrl = download.source.getImageUrl(page)
try { } catch (e: Throwable) {
page.imageUrl = download.source.getImageUrl(page) page.status = Page.State.Error(e)
} catch (e: Throwable) {
page.status = Page.State.Error(e)
}
} }
}
withIOContext { getOrDownloadImage(page, download, tmpDir, dataSaver) } withIOContext { getOrDownloadImage(page, download, tmpDir, dataSaver) }
emit(page) emit(page)
}.flowOn(Dispatchers.IO)
} }
.flowOn(Dispatchers.IO)
}
.collect { .collect {
// Do when page is downloaded. // Do when page is downloaded.
notifier.onProgressChange(download) notifier.onProgressChange(download)
@@ -465,6 +481,7 @@ class Downloader(
imageFile != null -> imageFile imageFile != null -> imageFile
chapterCache.isImageInCache(page.imageUrl!!) -> chapterCache.isImageInCache(page.imageUrl!!) ->
copyImageFromCache(chapterCache.getImageFile(page.imageUrl!!), tmpDir, filename) copyImageFromCache(chapterCache.getImageFile(page.imageUrl!!), tmpDir, filename)
else -> downloadImage(page, download.source, tmpDir, filename, dataSaver) else -> downloadImage(page, download.source, tmpDir, filename, dataSaver)
} }
@@ -18,11 +18,11 @@ import androidx.work.WorkInfo
import androidx.work.WorkQuery import androidx.work.WorkQuery
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.workDataOf import androidx.work.workDataOf
import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.sync.SyncPreferences import eu.kanade.domain.sync.SyncPreferences
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
@@ -64,9 +64,7 @@ import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.getAndSet import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.category.model.Category import tachiyomi.domain.category.model.Category
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.NoChaptersException import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.library.model.GroupLibraryMode import tachiyomi.domain.library.model.GroupLibraryMode
@@ -130,8 +128,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private val insertTrack: InsertTrack = Injekt.get() private val insertTrack: InsertTrack = Injekt.get()
private val trackerManager: TrackerManager = Injekt.get() private val trackerManager: TrackerManager = Injekt.get()
private val mdList = trackerManager.mdList private val mdList = trackerManager.mdList
private val getChaptersByMangaId: GetChaptersByMangaId = Injekt.get()
private val setReadStatus: SetReadStatus = Injekt.get()
// SY <-- // SY <--
private val notifier = LibraryUpdateNotifier(context) private val notifier = LibraryUpdateNotifier(context)
@@ -156,7 +152,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely() setForegroundSafely()
val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) } ?: Target.CHAPTERS val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) }
?: Target.CHAPTERS
// If this is a chapter update, set the last update time to now // If this is a chapter update, set the last update time to now
if (target == Target.CHAPTERS) { if (target == Target.CHAPTERS) {
@@ -220,28 +217,23 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// SY <-- // SY <--
val listToUpdate = if (categoryId != -1L) { val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { it.category == categoryId } libraryManga.filter { categoryId in it.categories }
// SY -->
} else if ( } else if (
group == LibraryGroup.BY_DEFAULT || group == LibraryGroup.BY_DEFAULT ||
groupLibraryUpdateType == GroupLibraryMode.GLOBAL || groupLibraryUpdateType == GroupLibraryMode.GLOBAL ||
(groupLibraryUpdateType == GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED) (groupLibraryUpdateType == GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)
) { ) {
val categoriesToUpdate = libraryPreferences.updateCategories().get().map(String::toLong) // SY <--
val includedManga = if (categoriesToUpdate.isNotEmpty()) { val includedCategories = libraryPreferences.updateCategories().get().map { it.toLong() }.toSet()
libraryManga.filter { it.category in categoriesToUpdate } val excludedCategories = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() }.toSet()
} else {
libraryManga
}
val categoriesToExclude = libraryPreferences.updateCategoriesExclude().get().map { it.toLong() } libraryManga.filter {
val excludedMangaIds = if (categoriesToExclude.isNotEmpty()) { val included = includedCategories.isEmpty() || it.categories.intersect(includedCategories).isNotEmpty()
libraryManga.filter { it.category in categoriesToExclude }.map { it.manga.id } val excluded = it.categories.intersect(excludedCategories).isNotEmpty()
} else { included && !excluded
emptyList()
} }
// SY -->
includedManga
.filterNot { it.manga.id in excludedMangaIds }
} else { } else {
when (group) { when (group) {
LibraryGroup.BY_TRACK_STATUS -> { LibraryGroup.BY_TRACK_STATUS -> {
@@ -255,6 +247,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
status.int == trackingExtra status.int == trackingExtra
} }
} }
LibraryGroup.BY_SOURCE -> { LibraryGroup.BY_SOURCE -> {
val sourceExtra = groupExtra?.nullIfBlank()?.toIntOrNull() val sourceExtra = groupExtra?.nullIfBlank()?.toIntOrNull()
val source = libraryManga.map { it.manga.source } val source = libraryManga.map { it.manga.source }
@@ -264,12 +257,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (source != null) libraryManga.filter { it.manga.source == source } else emptyList() if (source != null) libraryManga.filter { it.manga.source == source } else emptyList()
} }
LibraryGroup.BY_STATUS -> { LibraryGroup.BY_STATUS -> {
val statusExtra = groupExtra?.toLongOrNull() ?: -1 val statusExtra = groupExtra?.toLongOrNull() ?: -1
libraryManga.filter { libraryManga.filter {
it.manga.status == statusExtra it.manga.status == statusExtra
} }
} }
LibraryGroup.UNGROUPED -> libraryManga LibraryGroup.UNGROUPED -> libraryManga
else -> libraryManga else -> libraryManga
} }
@@ -286,10 +281,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
// SY <-- // SY <--
.filter { .filter {
when { when {
it.manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> { it.manga.updateStrategy == UpdateStrategy.ONLY_FETCH_ONCE && it.totalChapters > 0L -> {
skippedUpdates.add( skippedUpdates.add(
it.manga to it.manga to context.stringResource(MR.strings.skipped_reason_not_always_update),
context.stringResource(MR.strings.skipped_reason_not_always_update),
) )
false false
} }
@@ -311,11 +305,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindowUpperBound -> { MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindowUpperBound -> {
skippedUpdates.add( skippedUpdates.add(
it.manga to it.manga to context.stringResource(MR.strings.skipped_reason_not_in_release_period),
context.stringResource(MR.strings.skipped_reason_not_in_release_period),
) )
false false
} }
else -> true else -> true
} }
} }
@@ -328,9 +322,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
logcat { logcat {
skippedUpdates skippedUpdates
.groupBy { it.second } .groupBy { it.second }
.map { (reason, entries) -> .map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
"$reason: [${entries.map { it.first.title }.sorted().joinToString()}]"
}
.joinToString() .joinToString()
} }
} }
@@ -421,13 +413,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
} }
} catch (e: Throwable) { } catch (e: Throwable) {
val errorMessage = when (e) { val errorMessage = when (e) {
is NoChaptersException -> is NoChaptersException -> context.stringResource(
context.stringResource(MR.strings.no_chapters_error) MR.strings.no_chapters_error,
// failedUpdates will already have the source, )
// don't need to copy it into the message // failedUpdates will already have the source, don't need to copy it into the message
is SourceNotInstalledException -> context.stringResource( is SourceNotInstalledException -> context.stringResource(
MR.strings.loader_not_implemented_error, MR.strings.loader_not_implemented_error,
) )
else -> e.message else -> e.message
} }
failedUpdates.add(manga to errorMessage) failedUpdates.add(manga to errorMessage)
@@ -539,7 +532,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.copyFrom(networkManga) .copyFrom(networkManga)
try { try {
updateManga.await(updatedManga.toMangaUpdate()) updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) { } catch (_: Exception) {
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" } logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
@@ -558,11 +551,12 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
} }
// SY --> // SY -->
/** /**
* filter all follows from Mangadex and only add reading or rereading manga to library * filter all follows from Mangadex and only add reading or rereading manga to library
*/ */
private suspend fun syncFollows() = coroutineScope { private suspend fun syncFollows() = coroutineScope {
val preferences = Injekt.get<UnsortedPreferences>() val preferences = Injekt.get<SourcePreferences>()
var count = 0 var count = 0
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager = sourceManager) val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager = sourceManager)
?: return@coroutineScope ?: return@coroutineScope
@@ -579,7 +573,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
count++ count++
notifier.showProgressNotification( notifier.showProgressNotification(
listOf(Manga.create().copy(ogTitle = networkManga.title)), count, size, listOf(Manga.create().copy(ogTitle = networkManga.title)),
count,
size,
) )
var dbManga = getManga.await(networkManga.url, mangaDex.id) var dbManga = getManga.await(networkManga.url, mangaDex.id)
@@ -696,7 +692,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
} }
return file return file
} }
} catch (_: Exception) {} } catch (_: Exception) {
}
return File("") return File("")
} }
@@ -721,8 +718,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private const val ERROR_LOG_HELP_URL = "https://mihon.app/docs/guides/troubleshooting/" private const val ERROR_LOG_HELP_URL = "https://mihon.app/docs/guides/troubleshooting/"
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
/** /**
* Key for category to update. * Key for category to update.
*/ */
@@ -734,6 +729,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private const val KEY_TARGET = "target" private const val KEY_TARGET = "target"
// SY --> // SY -->
/** /**
* Key for group to update. * Key for group to update.
*/ */
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.track package eu.kanade.tachiyomi.data.track
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -25,9 +24,6 @@ interface Tracker {
val supportsPrivateTracking: Boolean val supportsPrivateTracking: Boolean
@ColorInt
fun getLogoColor(): Int
@DrawableRes @DrawableRes
fun getLogo(): Int fun getLogo(): Int
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.anilist package eu.kanade.tachiyomi.data.track.anilist
import android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@@ -57,9 +56,7 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
} }
} }
override fun getLogo() = R.drawable.ic_tracker_anilist override fun getLogo() = R.drawable.brand_anilist
override fun getLogoColor() = Color.rgb(18, 25, 35)
override fun getStatusList(): List<Long> { override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING) return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
@@ -337,6 +337,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|staff { |staff {
|edges { |edges {
|role |role
|id
|node { |node {
|name { |name {
|userPreferred |userPreferred
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.bangumi package eu.kanade.tachiyomi.data.track.bangumi
import android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -84,9 +83,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return track return track
} }
override fun getLogo() = R.drawable.ic_tracker_bangumi override fun getLogo() = R.drawable.brand_bangumi
override fun getLogoColor() = Color.rgb(240, 145, 153)
override fun getStatusList(): List<Long> { override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kavita package eu.kanade.tachiyomi.data.track.kavita
import android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -34,9 +33,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
private val sourceManager: SourceManager by injectLazy() private val sourceManager: SourceManager by injectLazy()
override fun getLogo(): Int = R.drawable.ic_tracker_kavita override fun getLogo(): Int = R.drawable.brand_kavita
override fun getLogoColor() = Color.rgb(74, 198, 148)
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED) override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.kitsu package eu.kanade.tachiyomi.data.track.kitsu
import android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -37,9 +36,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
private val api by lazy { KitsuApi(client, interceptor) } private val api by lazy { KitsuApi(client, interceptor) }
override fun getLogo() = R.drawable.ic_tracker_kitsu override fun getLogo() = R.drawable.brand_kitsu
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getStatusList(): List<Long> { override fun getStatusList(): List<Long> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ) return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
@@ -105,6 +102,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
return if (remoteTrack != null) { return if (remoteTrack != null) {
track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false) track.copyPersonalFrom(remoteTrack, copyRemotePrivate = false)
track.remote_id = remoteTrack.remote_id track.remote_id = remoteTrack.remote_id
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) { if (track.status != COMPLETED) {
track.status = if (hasReadChapters) READING else track.status track.status = if (hasReadChapters) READING else track.status
@@ -79,7 +79,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.awaitSuccess() .awaitSuccess()
.parseAs<KitsuAddMangaResult>() .parseAs<KitsuAddMangaResult>()
.let { .let {
track.remote_id = it.data.id track.library_id = it.data.id
track track
} }
} }
@@ -91,7 +91,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
val data = buildJsonObject { val data = buildJsonObject {
putJsonObject("data") { putJsonObject("data") {
put("type", "libraryEntries") put("type", "libraryEntries")
put("id", track.remote_id) put("id", track.library_id)
putJsonObject("attributes") { putJsonObject("attributes") {
put("status", track.toApiStatus()) put("status", track.toApiStatus())
put("progress", track.last_chapter_read.toInt()) put("progress", track.last_chapter_read.toInt())
@@ -105,7 +105,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
authClient.newCall( authClient.newCall(
Request.Builder() Request.Builder()
.url("${BASE_URL}library-entries/${track.remote_id}") .url("${BASE_URL}library-entries/${track.library_id}")
.headers( .headers(
headersOf("Content-Type", VND_API_JSON), headersOf("Content-Type", VND_API_JSON),
) )
@@ -122,7 +122,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
withIOContext { withIOContext {
authClient.newCall( authClient.newCall(
DELETE( DELETE(
"${BASE_URL}library-entries/${track.remoteId}", "${BASE_URL}library-entries/${track.libraryId}",
headers = headersOf("Content-Type", VND_API_JSON), headers = headersOf("Content-Type", VND_API_JSON),
), ),
) )
@@ -195,7 +195,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun getLibManga(track: Track): Track { suspend fun getLibManga(track: Track): Track {
return withIOContext { return withIOContext {
val url = "${BASE_URL}library-entries".toUri().buildUpon() val url = "${BASE_URL}library-entries".toUri().buildUpon()
.encodedQuery("filter[id]=${track.remote_id}") .encodedQuery("filter[id]=${track.library_id}")
.appendQueryParameter("include", "manga") .appendQueryParameter("include", "manga")
.build() .build()
with(json) { with(json) {
@@ -21,7 +21,8 @@ data class KitsuListSearchResult(
val manga = included[0].attributes val manga = included[0].attributes
return TrackSearch.create(TrackerManager.KITSU).apply { return TrackSearch.create(TrackerManager.KITSU).apply {
remote_id = userData.id remote_id = included[0].id
library_id = userData.id
title = manga.canonicalTitle title = manga.canonicalTitle
total_chapters = manga.chapterCount ?: 0 total_chapters = manga.chapterCount ?: 0
cover_url = manga.posterImage?.original ?: "" cover_url = manga.posterImage?.original ?: ""
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.track.komga package eu.kanade.tachiyomi.data.track.komga
import android.graphics.Color
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
@@ -31,9 +30,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
val api by lazy { KomgaApi(id, client) } val api by lazy { KomgaApi(id, client) }
override fun getLogo() = R.drawable.ic_tracker_komga override fun getLogo() = R.drawable.brand_komga
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED) override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)

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