Compare commits

...

214 Commits

Author SHA1 Message Date
Jobobby04 63617f3079 Release 1.3.0
Validate Gradle Wrapper / Validation (push) Failing after 23s
2020-09-14 20:24:48 -04:00
Jobobby04 0f9f7ffc28 Cleanup 2020-09-14 14:36:56 -04:00
Jobobby04 9aab9d4ca4 Fix autofill exclusion 2020-09-14 14:06:48 -04:00
Jobobby04 92ae67630c Hide dedupe by priority for release 2020-09-14 12:47:54 -04:00
arkon 02b90000f0 Hide parental controls section for release
(cherry picked from commit 76c795d0d0)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
2020-09-14 12:42:39 -04:00
arkon 6ed5d858aa Fix Kotlinter name typo
(cherry picked from commit b20bced3ca)
2020-09-14 12:40:58 -04:00
arkon 83bc059573 Fix Chinese plurals
(cherry picked from commit 4f2da9a78f)
2020-09-14 12:40:47 -04:00
Jozef Hollý ad39af55d6 Translated using Weblate (Bulgarian) (#3646)
Currently translated at 99.4% (574 of 577 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (French)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Chuvash)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (German)

Currently translated at 100.0% (577 of 577 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (French)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (German)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Update translation files

Updated by "Cleanup translation files" hook in Weblate.

Translated using Weblate (Italian)

Currently translated at 98.9% (570 of 576 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (French)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Yakut)

Currently translated at 87.3% (503 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Bulgarian)

Currently translated at 96.7% (557 of 576 strings)

Translated using Weblate (Chuvash)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Vietnamese)

Currently translated at 86.2% (497 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/vi/

Translated using Weblate (Japanese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (German)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Arabic)

Currently translated at 95.4% (550 of 576 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Yakut)

Currently translated at 85.2% (491 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Croatian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Sardinian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 99.6% (574 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chuvash)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Hindi)

Currently translated at 99.3% (572 of 576 strings)

Translated using Weblate (German)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (576 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Greek)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (French)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Latvian)

Currently translated at 33.6% (194 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/

Translated using Weblate (Arabic)

Currently translated at 91.8% (529 of 576 strings)

Translated using Weblate (Latvian)

Currently translated at 33.3% (192 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/

Translated using Weblate (Latvian)

Currently translated at 33.3% (192 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/

Translated using Weblate (French)

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 98.7% (569 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/

Translated using Weblate (Indonesian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Croatian)

Currently translated at 99.4% (573 of 576 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Catalan)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Bengali (Bangladesh))

Currently translated at 0.5% (3 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn_BD/

Translated using Weblate (Filipino)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Japanese)

Currently translated at 97.7% (563 of 576 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (576 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/

Translated using Weblate (Turkish)

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chuvash)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Hindi)

Currently translated at 99.1% (571 of 576 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Polish)

Currently translated at 96.8% (558 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/

Translated using Weblate (Malay)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Bengali)

Currently translated at 57.6% (332 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/

Translated using Weblate (Bengali)

Currently translated at 57.6% (332 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/

Added translation using Weblate (Bengali (Bangladesh))

Translated using Weblate (Yakut)

Currently translated at 80.2% (462 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Russian)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Bengali)

Currently translated at 58.3% (336 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/

Translated using Weblate (Sardinian)

Currently translated at 99.6% (574 of 576 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 98.0% (565 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chuvash)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (576 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (576 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Finnish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Indonesian)

Currently translated at 98.9% (570 of 576 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Chuvash)

Currently translated at 99.6% (574 of 576 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 90.4% (521 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/

Translated using Weblate (Malay)

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (576 of 576 strings)

Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (576 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (575 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (574 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (574 of 576 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (573 of 576 strings)

Translated using Weblate (German)

Currently translated at 100.0% (576 of 576 strings)

Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alessandro Zangrandi <alessandro@mzit.it>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gabriel Lebis <gableb@hotmail.fr>
Co-authored-by: George <georgeramzy37@gmail.com>
Co-authored-by: Hara Desu <aqjbgr09@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: RealKC <mitrut.e.super@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Whod <whodizhod@gmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: İlle <derasetad@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bg/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ro/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uk/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/
Translation: Tachiyomi/Strings

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alessandro Zangrandi <alessandro@mzit.it>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gabriel Lebis <gableb@hotmail.fr>
Co-authored-by: George <georgeramzy37@gmail.com>
Co-authored-by: Hara Desu <aqjbgr09@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: RealKC <mitrut.e.super@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Whod <whodizhod@gmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: İlle <derasetad@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 8e0ba3650b)
2020-09-14 12:40:33 -04:00
arkon 8d5b2f40b3 Use Kolinter Gradle plugin for linting instead of ktlint directly
(cherry picked from commit 76f6fe4601)
2020-09-13 23:08:52 -04:00
arkon 49bee1af91 Update to Preivews new update checker, while keeping the SY repos and versions
(cherry picked from commit ca1373f36b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdateChecker.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubService.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/updater/github/GithubUpdateChecker.kt
2020-09-13 23:03:22 -04:00
Jobobby04 a48508a98e reader light theme chapters bottom sheet fixes 2020-09-13 22:52:11 -04:00
arkon 4e3288b2af Use background color for some lists
(cherry picked from commit c0789cd6ba)
2020-09-13 22:51:46 -04:00
arkon 0f16150613 Replace deprecated system window insets usage
(cherry picked from commit af47103707)
2020-09-13 22:24:06 -04:00
arkon 6dd7491ffe Remove list dividers
(cherry picked from commit c466baaa25)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterDividerItemDecoration.kt
2020-09-13 22:24:06 -04:00
Jobobby04 02e6eaae12 Cleanup and fix a few compiler warnings 2020-09-13 22:24:03 -04:00
Jobobby04 73523dbff8 Remove Manga Plus delegation as it is not needed with the new extension update 2020-09-13 18:54:38 -04:00
Jobobby04 58a503814d Update firebase crashlytics 2020-09-13 18:46:14 -04:00
Jobobby04 7a834ea9f4 Mangadex tweaks, fix global update crashing sometimes 2020-09-13 18:45:57 -04:00
Jobobby04 dcca19e6b8 Undo Reds manga title changes 2020-09-12 19:21:54 -04:00
arkon 210fd000b3 Update OkHttp and Conscrypt
(cherry picked from commit 670294a427)
2020-09-12 13:51:55 -04:00
arkon 3d6f8ddd13 Update to Kotlin 1.4.10
(cherry picked from commit 21ddae6a86)
2020-09-12 13:51:55 -04:00
Andreas E a4578611d7 Always show missing chapter warning if there are missing chapters (#3755)
* Always show missing chapter warning if there are missing chapters

* Change function parameter names

(cherry picked from commit 9f260c3513)
2020-09-12 13:51:55 -04:00
Jobobby04 bb6932ff80 Mangadex follows, tell browse there are no more pages 2020-09-12 13:51:54 -04:00
AbdullahM0hamed 8dce9a674b Fix title on light theme (#101) 2020-09-12 13:51:19 -04:00
Jobobby04 b93298c411 Add MangaDex only implementation of Mangadex Follows list
Add login dialog that pops up whenever you are not logged in when trying to browse MangaDex
Remove attempts at porting over chapter read history from older galleries to new ones
Disable latest for ExHentai, it was browse without buttons anyway
2020-09-11 23:12:13 -04:00
Jobobby04 8928aa77eb Disable logging thread info, it wasnt very useful and made the log difficult to read 2020-09-10 20:27:49 -04:00
jobobby04 a6e6fa0099 Merge pull request #96 from AbdullahM0hamed/patch-1
Remove navigationBarColor from Black-Red theme
2020-09-10 18:43:46 -04:00
AbdullahM0hamed c23edd5b72 Remove navigationBarColor from Black-Red theme 2020-09-10 23:42:14 +01:00
AbdullahM0hamed c8a4ec37e0 Add Black-Red theme (#95)
* Appveyor

* stuff

* resolve conflict

* Let's try this again

* try again

* More fixing

* remove appveyor

* revert build.gradle

* Revert "revert build.gradle"

This reverts commit feaaa78157ffe8d6d6af7d6d63a74bc14b92f584.

* Undo line change

* Update build.gradle

* Update MainActivity.kt

Co-authored-by: AbdullahM0hamed<AbdullahM0hamed@users.noreply.github.com>
2020-09-10 17:06:49 -04:00
Jobobby04 f62d277894 Opps, forgot to push these changes 2020-09-08 12:04:53 -04:00
Jobobby04 b7efc21ea9 Update pt-rBR translation(curtsy of 0k//lux)
Change some gallery references to manga
2020-09-08 11:57:10 -04:00
Jobobby04 238b2d108d Cleanup 2020-09-06 22:43:58 -04:00
Jobobby04 bdaf0f7492 Fix exclusion in library search 2020-09-06 22:43:45 -04:00
Jobobby04 ced8dc750a Cleanup 2020-09-06 21:30:36 -04:00
Jobobby04 035fb9e755 Add biometric lock times 2020-09-06 21:30:22 -04:00
Jobobby04 86defec57c Fix latest + browse page crash when source returns null 2020-09-06 20:05:10 -04:00
Jobobby04 f20e5d864d Move from kizitonwose time to kotlin duration 2020-09-06 16:24:43 -04:00
Jobobby04 d83f938e07 Add a option to allow local source to read hidden folders 2020-09-06 00:31:08 -04:00
Jobobby04 b4e73cb1eb Upgrade the cleanup downloads to the new J2k version 2020-09-05 19:43:12 -04:00
arkon 604c7c703a Fix text alignment in transition view when no more chapters available
(cherry picked from commit b55d394a1f)
2020-09-05 19:20:53 -04:00
Andreas E 20021dbf54 Add spacing on top of sources/extensions/migrate lists (#3751)
(cherry picked from commit 5e2e177aa9)
2020-09-05 19:20:42 -04:00
arkon 281fb9c67b Refactor common chapter transition views into separate view
(cherry picked from commit 86e59977de)
2020-09-05 19:19:49 -04:00
arkon 7579bb026f Localize "No chapters found" error
(cherry picked from commit 66baf01e43)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
2020-09-05 19:19:37 -04:00
Andreas E 61fb836be4 Add missing chapter warning (#3745)
* Add missing chapter warning

* Flip calculation instead of flipping variables

* Change logic

* Change tint based on reader theme

* Add missing chapter warning to WebtoonTransitionHolder

* Add chapter warning between current/finished and prev/next

* Fix mix up of TextViews

* Fix review comments

(cherry picked from commit 7a33e198dc)
2020-09-05 19:11:20 -04:00
scb261 d83361dfe3 Change sources sort to case-insensitive (#3743)
(cherry picked from commit 4b493ebbaf)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt
2020-09-05 19:11:02 -04:00
arkon a5a79c1127 Remove unused string, fix improperly formatted Slovak string
(cherry picked from commit 565e8cf00b)
2020-09-05 19:03:58 -04:00
arkon 2f0f938d5e Update Conscrypt
(cherry picked from commit 738a3999b4)

# Conflicts:
#	app/build.gradle
2020-09-05 19:03:52 -04:00
arkon 750a6c3d11 Move share manga button to toolbar menu
(cherry picked from commit 8bedc8f456)
2020-09-05 19:02:48 -04:00
arkon b65305c73e Update dependencies
(cherry picked from commit d9000f6fd1)
2020-09-05 19:02:38 -04:00
Jobobby04 85ef1031b5 Display read progress on read chapters for E/Exhentai manga 2020-09-05 19:02:09 -04:00
Jobobby04 69c530dc34 Fix migrate to source with most chapters 2020-09-05 18:19:04 -04:00
Jobobby04 ea620a8c74 Probably fix previously read exh chapters affect updated gallery chapters 2020-09-05 18:18:39 -04:00
Jobobby04 1fdbae5bf8 Cleanup 2020-09-05 18:17:48 -04:00
Jobobby04 a1d54880c3 Merged manga implementation, man this took forever to make and bugfix, its not even done 2020-09-05 18:17:33 -04:00
Jobobby04 d21a652944 Remake the merged database, has support for future features 2020-09-04 17:31:35 -04:00
Jobobby04 444d346874 Probably make previously read exh chapters affect updated gallery chapters when loading the new version in the source 2020-08-25 02:09:15 -04:00
Jobobby04 41b8786415 Cleanup some mangatype logs 2020-08-25 02:07:40 -04:00
Jobobby04 d97805e38b Respect manga chapter order for reader chapter list, as well as fix page progress updating 2020-08-25 00:12:07 -04:00
Jobobby04 eafe3a62e4 Fix the bottom sheet and remove the bookmark button at the top of the reader 2020-08-24 19:25:47 -04:00
Jobobby04 0a502fcf31 Cleanup merged source code so I can modify it easier later on 2020-08-24 17:28:14 -04:00
Jobobby04 80960d87f2 Cleanup 2020-08-24 17:25:10 -04:00
Jobobby04 166aebdf25 Grab started filter from J2k 2020-08-23 21:59:43 -04:00
Jobobby04 b8836b9b6f Update firebase 2020-08-23 21:57:06 -04:00
Jobobby04 1d70f0b1dd Lint 2020-08-22 22:13:14 -04:00
Jobobby04 1240cc5232 Reader bottom sheet transparency, as well as a half fix for the fullscreen reader bug 2020-08-22 22:12:59 -04:00
Jobobby04 0abee585fc Some more cleanup to Save as CBZ 2020-08-22 18:41:42 -04:00
arkon 4870bb153d Filter out hidden directories for local source (closes #3706)
(cherry picked from commit fe7c7e72f5)
2020-08-22 18:19:33 -04:00
Jobobby04 f2b90bd772 Cleanup 2020-08-22 18:19:33 -04:00
arkon d77c65b515 Clean up X-Requested-With change
This only really affects the initial request, subsequent requests may still use the package name.

(cherry picked from commit 9920ff617b)
2020-08-22 18:17:48 -04:00
armangido 5004e2d62c Update WebViewActivity.kt (#3617)
This code added is for some extension that blocks tachiyomi, by tricking it that it was sent by a android browser, nothing major changes,

(cherry picked from commit 3f1355c413)
2020-08-22 18:17:37 -04:00
arkon 6e4a0ca1ea Update ActionMode styling
(cherry picked from commit 4929e66ecc)
2020-08-22 18:17:29 -04:00
arkon 883ffaa815 AndroidX dependency updates
(cherry picked from commit 4c31e3fc5f)
2020-08-22 18:17:08 -04:00
Jobobby04 3967a569c4 Cleanup Save as CBZ 2020-08-22 18:12:32 -04:00
Jobobby04 79e4e3d2a0 Add chapters bottom sheet for reader 2020-08-22 17:37:42 -04:00
Jobobby04 4b12e977c0 Cleanup some code 2020-08-22 17:37:42 -04:00
Fahad1998 4333999b85 Add Save As CBZ (#84)
Co-authored-by: Fahad1998 <f1998>
2020-08-22 17:37:03 -04:00
Fahad1998 fae290cf22 data saver fix (#85)
Co-authored-by: Fahad1998 <f1998>
2020-08-22 17:35:50 -04:00
Jobobby04 9ecf83f842 Delegate mangaplus so that it doesnt crash the app, it is literally just a copy of the extension with no extra features, but at least the app doesnt crash when you use it now 2020-08-21 16:27:01 -04:00
Jobobby04 f594962731 Cleanup 2020-08-21 15:32:42 -04:00
Jobobby04 048eecf655 Optimize and cleanup data saver 2020-08-21 15:32:29 -04:00
Fahad1998 90253f3bd4 Add Data Saver (#82)
Co-authored-by: Fahad1998 <f1998>
2020-08-21 13:50:08 -04:00
Jobobby04 0deb6f6b8d Finish some more advanced mangadex delegation features, more to come later 2020-08-20 20:50:37 -04:00
Jobobby04 294ade035e Rename LewdSource to MetadataSource to account for that Metadata will not always be for lewd sources 2020-08-19 18:45:56 -04:00
Jobobby04 64bb34b50d Delegate Mangadex (Part 1) 2020-08-19 18:20:05 -04:00
Jobobby04 9efb1482f9 Updated delegation system to account for custom headers 2020-08-19 18:19:22 -04:00
Jobobby04 aff15b3ee2 Remove library search log 2020-08-19 18:14:42 -04:00
Jobobby04 d86f3ffad8 Add a custom view change handler, makes it fade only one way 2020-08-19 02:08:58 -04:00
arkon 2a3eef0610 Don't enqueue bookmarked chapters for deletion (fixes #3691)
(cherry picked from commit 4c8665c9f0)
2020-08-18 22:32:06 -04:00
arkon 3b87111f22 Minor wording edit
(cherry picked from commit ba67781431)

# Conflicts:
#	app/src/main/res/values/strings.xml
2020-08-18 22:31:50 -04:00
arkon b654613345 Use core-ktx for bolding chapter transition text
(cherry picked from commit 4ef25c75b7)
2020-08-18 22:31:20 -04:00
arkon 136b25fb92 Dependency updates
(cherry picked from commit 3aafc671f8)

# Conflicts:
#	app/build.gradle
2020-08-18 22:31:00 -04:00
arkon f3875bda50 Update to Kotlin 1.4
(cherry picked from commit 967df6f7a3)

# Conflicts:
#	app/build.gradle
2020-08-18 22:29:54 -04:00
Jobobby04 c41148b465 Make animations smoother, add change handlers to bottom nav transactions, make animations go by their default value instead of a custom fast one 2020-08-18 22:23:39 -04:00
Jobobby04 eb0a1668f8 Made the ViewHeightAnimator duration configurable 2020-08-18 22:07:39 -04:00
Jobobby04 f7bc3e0a82 Cleanup some strings 2020-08-18 22:07:14 -04:00
Jobobby04 2e4def13e3 Add custom browese view, disabled by default and can be enabled in the settings
Signed-off-by: Jobobby04 <jobobby04@users.noreply.github.com>
2020-08-18 22:05:56 -04:00
Jobobby04 9e0e2db25d Add dynamic category library update upgrades 2020-08-18 18:55:00 -04:00
Jobobby04 20aa5b9aa1 Cleanup E-Hentai 2020-08-18 18:31:00 -04:00
Jobobby04 6ce4612aa7 Fix latest packages 2020-08-16 20:57:10 -04:00
Jobobby04 b51d147986 Cleanup 2020-08-16 20:47:09 -04:00
Jobobby04 bc896cf605 Make XLog display debug info in a debug build 2020-08-16 20:40:30 -04:00
Jobobby04 e48f274072 Updates and cleanup build.gradle 2020-08-15 23:01:16 -04:00
Jobobby04 a6b98e24dc Undo linting 2020-08-15 15:57:07 -04:00
Jobobby04 9a26a3e5a2 Revert "Slight gradle cleanup, plugin updates"
This reverts commit 08d11914af.
2020-08-14 18:54:34 -04:00
Jobobby04 eeb0f76cce Cleanup 2020-08-14 18:41:21 -04:00
Jobobby04 bcd36c8fad Add tap to move by page for continues vertical reader 2020-08-14 18:41:21 -04:00
Jobobby04 7f7b2901cb Convert autoscroll to a coroutine 2020-08-14 18:41:21 -04:00
Jobobby04 84b9b4db55 Cleanup 2020-08-14 18:41:21 -04:00
Jobobby04 a5c10dbf28 Add no title grid option 2020-08-14 18:41:21 -04:00
Jobobby04 5fee3ac05a Add a option to open the library bottom sheet in the library Settings 2020-08-14 18:41:21 -04:00
Jobobby04 d3603a664c Un-break XLog network logging, code contributed from Neko 2020-08-14 18:41:21 -04:00
Jobobby04 7ccaea0d72 Remove unused hitomi class 2020-08-14 18:41:20 -04:00
Jobobby04 921f7aad01 Make FAB disappear on image expand 2020-08-14 18:41:20 -04:00
Jobobby04 bc549c56d6 Automatic linting fixes 2020-08-14 18:41:20 -04:00
Jobobby04 b639e1e4d7 Delete my pull request tester in favor of previews 2020-08-14 18:41:13 -04:00
arkon 4f877820b2 Add PR build check action
(cherry picked from commit 19a7f37efa)
2020-08-14 18:41:13 -04:00
arkon c35e0a0c29 Unhide parental controls settings
(cherry picked from commit c3084ac43a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
2020-08-14 18:41:13 -04:00
TacoTheDank 08d11914af Slight gradle cleanup, plugin updates
(cherry picked from commit 159146e197)

# Conflicts:
#	app/build.gradle
#	build.gradle.kts
2020-08-14 18:41:13 -04:00
Jobobby04 309bd83730 Update gradle wrapper
(cherry picked from commit 67ddf4a5b8)
2020-08-14 18:40:55 -04:00
Taco 6b158cc864 Optimize images using ezgif (#3649) 2020-08-12 22:45:32 -04:00
Jobobby04 7d82be964c Add long click to copy on the special manga views 2020-08-12 22:34:20 -04:00
Jobobby04 525c3f84e4 Enable click to copy to clipboard in the more info 2020-08-12 22:32:13 -04:00
Jobobby04 ef37811020 Expand manga thumbnails 2020-08-12 21:10:39 -04:00
Jobobby04 0d033c7080 Cleanup some delegation code 2020-08-12 18:46:05 -04:00
Jobobby04 7557369d1f Release 1.2.0 2020-08-12 15:06:34 -04:00
Jobobby04 3e1804da8a Lazily instantiate some variables in EHSourceHelpers 2020-08-12 14:20:32 -04:00
Jobobby04 2ab0927313 Add PervEden to clean titles 2020-08-12 14:04:00 -04:00
Jobobby04 98b6a221fd NHentai/Hitomi say "All" when toString is called 2020-08-12 13:45:25 -04:00
Jobobby04 4733a62980 Wrap hitomi upload date in a Try/Catch to solve errors 2020-08-12 13:41:03 -04:00
Jobobby04 a5276fdadc When refreshing in groups update globally(tmep fix, hopefully grouping will update the group later) 2020-08-12 01:19:52 -04:00
Jobobby04 906ac9e00c Fix bugs with migrating manga with the action mode 2020-08-12 01:18:45 -04:00
arkon 8ae3b8e313 Update issue templates
(cherry picked from commit aa607e0ecb)
2020-08-12 00:34:18 -04:00
arkon ce36e6b242 Split out NSFW source setting to separate section
Temporarily hidden until feature is ready for stable release.

(cherry picked from commit 65b32ddeb2)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
2020-08-12 00:34:10 -04:00
arkon cfd3d59516 Fix Chinese plural string
(cherry picked from commit 5e9bdc2690)
2020-08-12 00:33:33 -04:00
Jozef Hollý ae915d7823 Translated using Weblate (Swedish) (#3580)
Currently translated at 99.8% (573 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/

Translated using Weblate (Finnish)

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/

Translated using Weblate (Swedish)

Currently translated at 99.6% (572 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/

Translated using Weblate (Greek)

Currently translated at 99.4% (571 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/

Translated using Weblate (Russian)

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Malay)

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Translated using Weblate (Spanish)

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/

Translated using Weblate (German)

Currently translated at 100.0% (574 of 574 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Dutch)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/

Translated using Weblate (Yakut)

Currently translated at 74.8% (428 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Filipino)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/

Translated using Weblate (Finnish)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/

Translated using Weblate (Russian)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/

Translated using Weblate (Spanish)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/

Translated using Weblate (Turkish)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Malay)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Translated using Weblate (German)

Currently translated at 100.0% (572 of 572 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Indonesian)

Currently translated at 99.6% (568 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/

Translated using Weblate (Yakut)

Currently translated at 70.3% (401 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 70.3% (401 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Filipino)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/

Translated using Weblate (Chinese (Traditional))

Currently translated at 99.1% (565 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/

Translated using Weblate (Finnish)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/

Translated using Weblate (Greek)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/

Translated using Weblate (Turkish)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/

Translated using Weblate (Russian)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Dutch)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/

Translated using Weblate (Malay)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Translated using Weblate (Spanish)

Currently translated at 100.0% (570 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/

Translated using Weblate (German)

Currently translated at 99.4% (567 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Yakut)

Currently translated at 70.3% (401 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 70.3% (401 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 70.3% (401 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.8% (364 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.8% (364 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.5% (362 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.5% (362 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.3% (361 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 63.3% (361 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 62.4% (356 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 62.4% (356 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 61.7% (352 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 61.7% (352 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 61.7% (352 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 61.7% (352 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 61.5% (351 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 60.7% (346 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 60.7% (346 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 60.7% (346 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 60.0% (342 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.9% (336 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.9% (336 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.7% (335 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.7% (335 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.7% (335 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 58.7% (335 of 570 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Malay)

Currently translated at 100.0% (567 of 567 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Translated using Weblate (Russian)

Currently translated at 100.0% (567 of 567 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (567 of 567 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Spanish)

Currently translated at 100.0% (567 of 567 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/

Translated using Weblate (German)

Currently translated at 100.0% (567 of 567 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Yakut)

Currently translated at 49.6% (281 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Portuguese)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/

Translated using Weblate (Yakut)

Currently translated at 32.6% (185 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Yakut)

Currently translated at 10.4% (59 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Persian)

Currently translated at 96.6% (547 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fa/

Added translation using Weblate (Yakut)

Translated using Weblate (Catalan)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/

Translated using Weblate (Russian)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/

Translated using Weblate (Spanish)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/

Translated using Weblate (Spanish (Latin America))

Currently translated at 99.8% (565 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Spanish (Latin America))

Currently translated at 99.8% (565 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Greek)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/

Translated using Weblate (Dutch)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/

Translated using Weblate (Czech)

Currently translated at 64.1% (363 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/

Translated using Weblate (Italian)

Currently translated at 98.7% (559 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Sardinian)

Currently translated at 99.8% (565 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/

Translated using Weblate (Filipino)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/

Translated using Weblate (Turkish)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/

Translated using Weblate (Indonesian)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/

Translated using Weblate (Finnish)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/

Translated using Weblate (Norwegian Bokmål)

Currently translated at 90.4% (512 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Malay)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Translated using Weblate (German)

Currently translated at 100.0% (566 of 566 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uk/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/

Translated using Weblate (Sardinian)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/

Translated using Weblate (Chuvash)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/

Translated using Weblate (Filipino)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/

Translated using Weblate (Norwegian Bokmål)

Currently translated at 89.3% (505 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/

Translated using Weblate (Spanish (Latin America))

Currently translated at 99.6% (563 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/

Translated using Weblate (Portuguese)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/

Translated using Weblate (Catalan)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/

Translated using Weblate (Greek)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/

Translated using Weblate (Filipino)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/

Translated using Weblate (Dutch)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/

Translated using Weblate (Finnish)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/

Translated using Weblate (Turkish)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/

Translated using Weblate (Japanese)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/

Translated using Weblate (Japanese)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/

Translated using Weblate (German)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/

Translated using Weblate (Malay)

Currently translated at 100.0% (565 of 565 strings)

Translation: Tachiyomi/Strings
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/

Co-authored-by: Hosted Weblate <hosted@weblate.org>
(cherry picked from commit 9ce994168a)
2020-08-12 00:33:25 -04:00
arkon b4b4497e7e Lift toolbar on scroll in extension details and manga controllers
(cherry picked from commit 748a720199)
2020-08-12 00:33:17 -04:00
arkon 19055e1699 Allow annotating SourceFactory with @Nsfw to block all sources within it
(cherry picked from commit 8db34eb3dd)
2020-08-12 00:33:07 -04:00
arkon f006467138 Add 18+ warnings in extensions list
(cherry picked from commit b657bba96e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt
2020-08-12 00:32:57 -04:00
Jobobby04 a740f79b56 Update readme 2020-08-12 00:28:37 -04:00
Jobobby04 6c388ae906 Cleanup images, replace some icons 2020-08-12 00:23:18 -04:00
Jobobby04 3fa5322133 Delegate NHentai, to continue using NHentai download the extension, SY requires NHentai version 1.2.28 2020-08-12 00:23:18 -04:00
Jobobby04 5a1bc6e25b Delegate Perv Eden, to continue using it download the extensions(there is a English extension and a Italian extension) 2020-08-12 00:21:29 -04:00
Jobobby04 51d0a67908 Account for custom delegated image requests, fixes Hitomi pages 2020-08-11 15:20:11 -04:00
Jobobby04 69f4d1fd46 Likely fix Hitomi extension 2020-08-11 01:35:36 -04:00
Jobobby04 8b53568fc8 Cleanup some hitomi leftovers 2020-08-10 23:47:26 -04:00
Jobobby04 d978b6d088 Update readme 2020-08-10 23:31:32 -04:00
Jobobby04 9a3fdc23e6 Delegate hitomi, it is now the first fully delegated factory source. To continue using hitomi please download the extension. This comes with a lot of fixes for future delegated factory sources 2020-08-10 23:29:10 -04:00
Jobobby04 f8efe5d189 Make a new debug function to help delegation 2020-08-10 21:15:48 -04:00
Jobobby04 aae23f5ef3 Delegate 8Muses, please manually migrate over your comics to the extension, as the old version of the 8Muses comics cannot support the new comics format 2020-08-10 21:15:08 -04:00
Jobobby04 eee2c34abf Fix library actions overflow menu 2020-08-10 18:30:10 -04:00
Jobobby04 c272eb6059 Move the Realm init out of the globalscope in hope it fixes crashes 2020-08-10 14:45:18 -04:00
Jobobby04 74065afc27 Hopefully fix issues with certain actions for some people 2020-08-10 00:55:39 -04:00
Jobobby04 ead5a258be More drag protection 2020-08-10 00:54:56 -04:00
Jobobby04 f9cf017594 Set smart reader background as the default 2020-08-09 20:22:26 -04:00
Mike 1211b2c86a Fix Hitomi chapters (#75) 2020-08-09 20:00:49 -04:00
Jobobby04 2bc845b1d2 Simple way to get chapter date for nHentai 2020-08-09 19:44:17 -04:00
Jobobby04 da8ed5c74f Fix zoom out webtoon showing in pager mode 2020-08-09 19:42:03 -04:00
arkon d7d1d97f5f Add option to prevent deleting bookmarked chapters (closes #2082)
(cherry picked from commit dbaac69fad)
2020-08-09 19:20:01 -04:00
arkon 63510b2e60 Minor cleanup
(cherry picked from commit b6a1e89535)
2020-08-09 19:19:53 -04:00
arkon d7976e6054 Minor rewording of chapter deletion settings
(cherry picked from commit cce919750a)
2020-08-09 19:19:45 -04:00
arkon c82d7db570 Bubble up sources with results in global search (closes #3598)
(cherry picked from commit 9376b223bb)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchPresenter.kt
2020-08-09 19:19:32 -04:00
arkon b6f8db81ee Update OkHttp
(cherry picked from commit 6f047fb5aa)
2020-08-09 19:18:14 -04:00
arkon a2fb89066c Swallow errors when trying to determine available disk space when downloading (closes #3603)
(cherry picked from commit 3e6b0117fd)
2020-08-09 19:18:05 -04:00
arkon 6f71bb3abe Allow partially loading extensions with individually marked NSFW sources
(cherry picked from commit 421dfb4a2d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionPresenter.kt
2020-08-09 19:17:55 -04:00
Jobobby04 e945de74f2 Add manga grouping in library!! This was inspired by J2ks version of the feature 2020-08-09 19:13:47 -04:00
Jobobby04 0e43234c23 More cleanup for drag and drop 2020-08-08 17:09:30 -04:00
arkon f8c4bbdfd8 Option to hide NSFW extensions (closes #1312) (SY will expand more on this when preview finishes it)
(cherry picked from commit abaca6e676)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionPresenter.kt
2020-08-08 16:47:29 -04:00
Jobobby04 c834c2fbb0 Tweak dragging so it works in action mode if there is only 1 manga selected 2020-08-08 16:32:03 -04:00
arkon ad62a6b10b Add mark as read/unread to library (closes #156)
Adapted from https://github.com/CarlosEsco/Neko/commit/e51276a1acb233467bbb1c7214be0ec105c62cb7

(cherry picked from commit 8bab1d9798)
2020-08-08 16:07:34 -04:00
arkon 04fbb70981 Explicitly depend on core-ktx
(cherry picked from commit 13d31669ac)
2020-08-08 16:07:25 -04:00
Jobobby04 340e534ca9 Remove uneeded checks for should move, fixes moving a manga to the first positon 2020-08-08 16:07:18 -04:00
Jobobby04 5714f183a8 Update drag and drop to work like J2k's new version
Cleanup some stuff from the continue reading button
2020-08-07 23:34:58 -04:00
Jobobby04 b6f6607d91 Cleanup readme 2020-08-06 21:27:10 -04:00
arkon aa794f4703 Fix MAL 0/10 scores (closes #3623)
(cherry picked from commit c1dfdeb500)
2020-08-06 21:21:43 -04:00
arkon a6d6a0fca6 Dismiss add manga snackbar when leaving controller (closes #3614)
(cherry picked from commit dda7e677a5)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2020-08-06 21:21:34 -04:00
Jobobby04 aa6f1a0de5 Cleanup 2020-08-06 21:09:25 -04:00
Jobobby04 52b9a1dbd2 Fix resources being in the wrong spot for some reason 2020-08-06 18:32:29 -04:00
Jobobby04 2f98dd2046 Add continue reading button, some resources gotten from J2k 2020-08-06 17:45:18 -04:00
Jobobby04 f60b29c763 Replace cleanup downloads for loops with forEach loops 2020-08-06 16:15:40 -04:00
Jobobby04 c2adf2fe0a Copy smart background from J2k 2020-08-06 16:11:00 -04:00
Jobobby04 c340884adb Copy enable/disable zoom out in webtoon reader from J2k 2020-08-06 14:59:58 -04:00
Jobobby04 0125f326b4 Copy cleanup orphaned downloads from J2k 2020-08-06 14:59:31 -04:00
Jobobby04 2de87f8d29 Cleanup 2020-08-06 14:57:54 -04:00
arkon 165b243aab Warn before restoring backup if trackers aren't logged in
(cherry picked from commit b1fb401f63)
2020-08-05 22:56:46 -04:00
arkon 961f7f9b6d Fix toolbar being expanded when opening preference dialogs
(cherry picked from commit 885ace111e)
2020-08-05 22:56:35 -04:00
Jobobby04 61094edeed Cleanup some code 2020-08-05 22:46:04 -04:00
Jobobby04 fbe4f6ad62 Dont download new chapters after refreshing a manga if its from E/ExHentai 2020-08-05 20:46:14 -04:00
Jobobby04 c552934acc Fix quick clean nHentai manga 2020-08-05 18:26:02 -04:00
Jobobby04 44c9df8c9b Add quick clean E-Hentai/nHentai titles, select them in your library and you can quick clean them 2020-08-05 18:23:36 -04:00
jobobby04 594a02fa69 Fix release builder 2020-08-05 15:01:24 -04:00
Jobobby04 510a67a755 Finishing a E-Hentai chapter will now mark previous chapters as read 2020-08-05 14:28:40 -04:00
Jobobby04 882856a028 Lint 2020-08-05 13:32:28 -04:00
Jobobby04 76adeae5ed Fix bug when pressing download unread chapters on a E-Hentai manga from library will download the latest chapter even if its read 2020-08-05 13:32:11 -04:00
arkon eb3c9a1d58 Move tracker setting dialogs
(cherry picked from commit 885552b792)
2020-08-04 23:35:31 -04:00
arkon 29f74ba423 Minor cleanup
(cherry picked from commit 4f02872a84)
2020-08-04 23:35:22 -04:00
arkon 571778adc1 Revert "Use insetter library for handling inset padding" (fixes #3586)
This reverts commit 3ddd1033c3.

(cherry picked from commit ecec1bd102)
2020-08-04 23:35:12 -04:00
Jobobby04 64c5b70c78 Redo the EH library search engine, make it work for every manga, meaning exclusion, partial matching, and a bunch of other things now working the library search 2020-08-04 23:34:26 -04:00
Jobobby04 bb87392eef Code cleanup 2020-08-04 22:51:56 -04:00
Jobobby04 29e1697d2e Fix migration crash because fetch chapter list through a exception 2020-08-03 19:44:02 -04:00
arkon 025d794962 Fix snackbars not being in viewport properly
(cherry picked from commit 060f0682f4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2020-08-03 18:25:27 -04:00
arkon a284f5cd08 Use dialog to show what's new release info
(cherry picked from commit 88032e11df)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2020-08-03 18:24:11 -04:00
arkon ee6b536b94 Adjust vertical reading mode tap zones (closes #3551)
Basically L shapes, where top/left goes back, bottom/right goes forward, and middle opens the menu.

(cherry picked from commit 493c8b0943)
2020-08-03 18:14:22 -04:00
arkon 285f65ca4f Remove Tagalog translations (closes #3579)
(cherry picked from commit af2ef0621a)
2020-08-03 18:14:10 -04:00
arkon d07dbee9b0 Explicitly dismiss progress notification on downloader stop
(cherry picked from commit 095461e31b)
2020-08-03 18:14:02 -04:00
arkon a5e1f92b05 Use insetter library for handling inset padding
(cherry picked from commit 3ddd1033c3)
2020-08-03 18:13:53 -04:00
arkon 417a31cfad Adjust download badge color again
(cherry picked from commit 912687ac78)
2020-08-03 18:13:45 -04:00
arkon a84df3501a Request gzipped version of extensions repo
(cherry picked from commit 40a9595012)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt
2020-08-03 18:13:38 -04:00
arkon 01137bf476 Fix manga title disappearing in toolbar when pushing another controller
(cherry picked from commit 12ff37d052)
2020-08-03 18:08:33 -04:00
arkon 4c20ba38cb Revert "Use AndroidX WebKit library"
This reverts commit 7e7eb9f39f.

(cherry picked from commit 4857073f30)

# Conflicts:
#	app/build.gradle
2020-08-03 18:08:24 -04:00
Jobobby04 cb4daa81c4 Add a release builder action 2020-08-03 17:33:07 -04:00
Jobobby04 4f803494ff Update the EH search engine to fix issues with the current search features 2020-08-03 17:21:10 -04:00
Jobobby04 fb19f6b860 Update nHentai internal logic to be the same as the extension 2020-08-03 12:54:46 -04:00
jobobby04 885c94f9c8 Make the preview builder only happen on master 2020-08-03 12:47:16 -04:00
joseph619 a5b7ad6495 Update preview images to 1.1.0 2020-08-02 21:02:49 -04:00
530 changed files with 15654 additions and 5465 deletions
+2 -2
View File
@@ -2,7 +2,7 @@
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v1.1.1) - I have updated to the latest version of the app (stable is v1.3.0)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
@@ -10,7 +10,7 @@ I acknowledge that:
--- ---
### Device information ## Device information
* Tachiyomi version: ? * Tachiyomi version: ?
* Android version: ? * Android version: ?
* Device: ? * Device: ?
+3 -3
View File
@@ -9,7 +9,7 @@ labels: "bug"
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v1.1.1) - I have updated to the latest version of the app (stable is v1.3.0)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
@@ -17,7 +17,7 @@ I acknowledge that:
--- ---
### Device information ## Device information
* Tachiyomi version: ? * Tachiyomi version: ?
* Android version: ? * Android version: ?
* Device: ? * Device: ?
@@ -32,5 +32,5 @@ This should happen.
### Actual behavior ### Actual behavior
This happened instead. This happened instead.
### Other details ## Other details
Additional details and attachments. Additional details and attachments.
+8
View File
@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Tachiyomi help website
url: https://tachiyomi.org/help/
about: Common questions are answered here.
- name: Tachiyomi extensions GitHub repository
url: https://github.com/inorichi/tachiyomi-extensions
about: Issues about an extension/source/catalogue should be opened here instead.
+3 -3
View File
@@ -9,7 +9,7 @@ labels: "feature"
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v1.1.1) - I have updated to the latest version of the app (stable is v1.3.0)
- I have updated all extensions - I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions - If this is an issue with an extension, that I should be opening an issue in https://github.com/inorichi/tachiyomi-extensions
@@ -17,8 +17,8 @@ I acknowledge that:
--- ---
### Why/User Benefit/User Problem ## Why/User Benefit/User Problem
(explain why this feature should be added) (explain why this feature should be added)
### What/Requirements ## What/Requirements
(explain how this feature would behave) (explain how this feature would behave)
+1 -1
View File
@@ -2,7 +2,7 @@
name: "Extension/source/catalogue issue" name: "Extension/source/catalogue issue"
about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions" about: "Do not open an issue here. See https://github.com/inorichi/tachiyomi-extensions"
title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions" title: "THIS ISSUE IS IN THE WRONG REPO; SEE https://github.com/inorichi/tachiyomi-extensions"
labels: "catalog" labels: "catalog, invalid"
--- ---
DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions DO NOT OPEN AN ISSUE IN THIS REPO. SEE https://github.com/inorichi/tachiyomi-extensions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 482 KiB

+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="svg8" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 172 172" style="enable-background:new 0 0 172 172;" xml:space="preserve">
<style type="text/css">
.st0{stroke:#CE2828;stroke-width:14;stroke-linecap:round;stroke-linejoin:round;}
.st1{fill:#F7D009;}
.st2{fill:#E40F85;}
</style>
<title>sy_hobo_stds_mine</title>
<g id="layer1">
<path id="path4535" class="st0" d="M85.3,7C129,6.6,164.6,41.7,165,85.3c0.4,43.6-34.7,79.3-78.3,79.7C43.1,165.4,7.4,130.3,7,86.7
c0-0.5,0-0.9,0-1.4C7.4,42.2,42.2,7.4,85.3,7z"/>
<g id="text4543">
<path id="path4545" class="st1" d="M76,64.2c2.9,0,8.4-9.4,8.4-12.5S73.5,40.7,58.2,40.7c-21.4,0-27.4,15-27.4,23.8
c0,9.1,2.5,19.6,25.6,26.6c6.1,2,15.6,5.1,15.6,12.8c0,6.5-6.9,9.9-15,9.9c-22.6,0-20.9-21.3-22.6-21.3c-1.1,0-6.4,5.1-6.4,14.2
c0,16.7,15.2,24.7,30.1,24.7c22.3,0,31-15,31-27.2c0-9.9-4.5-20.7-26.7-28.1c-5.8-2-16.2-4.8-16.2-12.5c0-6.2,6.8-8.8,12-8.8
C69.2,54.8,73.3,64.2,76,64.2L76,64.2z"/>
<path id="path4547" class="st2" d="M95.4,128.7c0,1.4,1.1,2.6,2.6,2.6c23.2,0,47-29.8,46-60.7c0-4.5,0.3-7.9-1.7-8.2h-9.4
c-1.2,0-3.8-0.3-3.8,1.4s1.2,6.2,1.2,11.3c0,8.2-2.8,21-7.1,21c-2.1,0-12.4-11.6-12.4-24.1c0-3.1,1-6.2,1-7.9c0-2-2.3-1.7-3.7-1.7
h-8.6c-4.1,0-4,0-4,4.8c0,29.5,18.3,36.8,18.3,41.4c0,1.1-3.1,5.1-15.3,5.1c-2.8,0-3.1,3.1-3.1,4.3L95.4,128.7z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -2,6 +2,8 @@ name: Remote Dispatch Action Initiator
on: on:
push: push:
branches:
- 'master'
repository_dispatch: repository_dispatch:
jobs: jobs:
@@ -0,0 +1,67 @@
name: Release Builder
on:
push:
branches:
- 'release'
jobs:
apk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Get NDK
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Write google-services.json
uses: DamianReeves/write-file-action@v1.0
with:
# The path to the file to write
path: app/google-services.json
# The contents of the file
contents: ${{ secrets.GOOGLE_SERVICES_TEXT }}
# The mode of writing to use: `overwrite`, `append`, or `preserve`.
write-mode: overwrite # optional, default is preserve
- name: Build Release APK
run: bash ./gradlew assembleRelease --stacktrace
- name: Sign Android Release
uses: r0adkll/sign-android-release@v1
with:
# The directory to find your release to sign
releaseDirectory: app/build/outputs/apk/standard/release
# The key used to sign your release in base64 encoded format
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
# The key alias
alias: ${{ secrets.ALIAS }}
# The password to the keystore
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
# The password for the key
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.run_number }}
release_name: TachiyomiSY
draft: true
prerelease: false
- name: Upload Release APK
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.SIGNED_RELEASE_FILE }}
asset_name: TachiyomiSY.apk
asset_content_type: application/vnd.android.package-archive
@@ -0,0 +1,11 @@
name: Validate Gradle Wrapper
on: [push, pull_request]
jobs:
validation:
name: Validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: gradle/wrapper-validation-action@v1
+1
View File
@@ -1,5 +1,6 @@
name: Issue closer name: Issue closer
on: [issues] on: [issues]
jobs: jobs:
autoclose: autoclose:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -1,23 +1,21 @@
name: Pull Request Checker name: Pull request build check
on: [pull_request]
on:
pull_request:
jobs: jobs:
apk: build:
name: Generate APK runs-on: ubuntu-latest
runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - name: Clone repo
- name: set up JDK 1.8 uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 1.8 java-version: 1.8
- name: Get NDK - name: Install NDK
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669" run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
- name: Build Release APK - name: Build project
run: bash ./gradlew assembleDebug --stacktrace run: ./gradlew assembleDebug
- name: Upload APK - name: Upload APK
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
+14 -9
View File
@@ -1,6 +1,6 @@
| Preview Builds | Release Builds | Tachiyomi Support Server | | Preview Builds | Release Builds | Tachiyomi Support Server |
|-------|----------|----------| |-------|----------|----------|
| [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) | | [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/latest) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) |
# ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY # ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY
@@ -22,7 +22,6 @@ Features of Tachiyomi(original) include:
Features of TachiyomiSY include: Features of TachiyomiSY include:
* Uses the new Tachiyomi Stable UI * Uses the new Tachiyomi Stable UI
* Custom manga page, all your needs, such as info and chapters, in front of your face
* Latest tab, store up to 5 sources where you can easily view the latest manga by viewing the tab * Latest tab, store up to 5 sources where you can easily view the latest manga by viewing the tab
* Hentai features enable/disable, in advanced settings * Hentai features enable/disable, in advanced settings
* Automatic webtoon detection, allowing the reader to switch to webtoon mode automatically when viewing one * Automatic webtoon detection, allowing the reader to switch to webtoon mode automatically when viewing one
@@ -37,20 +36,25 @@ Features of TachiyomiSY include:
* Manga info edit * Manga info edit
* Enhanced views for internal and integrated sources * Enhanced views for internal and integrated sources
* Enhanced usability for internal and delegated sources * Enhanced usability for internal and delegated sources
* Dynamic Categories, view the library in multiple ways
* Smart background for reading modes like LTR or Vertical, changes the backgorund based on the page color
* Force disable webtoon zoom
* Continue reading button in library
* Quick clean titles
Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified in TachiyomiSY Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified in TachiyomiSY
* Source migration, migrate all your manga from one source to another * Source migration, migrate all your manga from one source to another
* Custom hentai sources: * Custom hentai sources:
* * E-Hentai/ExHentai * * E-Hentai/ExHentai
* * nHentai
* * Hitomi.la
* * 8Muses
* * Perv Eden
* Additional features for some extensions, features include custom description, opening in app, batch add to library: * Additional features for some extensions, features include custom description, opening in app, batch add to library:
* * 8Muses (EroMuse)
* * HBrowse
* * HentaiCafe (Foolside)
* * Hitomi.la
* * NHentai
* * PervEden (EN and IT)
* * Puruin * * Puruin
* * Tsumino * * Tsumino
* * HentaiCafe (Foolside)
* * HBrowse
* Saving searches * Saving searches
* Autoscroll * Autoscroll
* Page preload customization * Page preload customization
@@ -64,10 +68,11 @@ Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified
* Click tag for local search, long click tag for global search * Click tag for local search, long click tag for global search
* Merge multiple of the same manga from different sources * Merge multiple of the same manga from different sources
* Drag and drop library sorting * Drag and drop library sorting
* Library search engine, includes exclude, quotes as absolute, and a bunch of other ways to search
## Download ## Download
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases). Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases). If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/jobobby04/tachiyomisypreview/releases).
+43 -62
View File
@@ -7,6 +7,7 @@ apply plugin: 'com.mikepenz.aboutlibraries.plugin'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'kotlinx-serialization'
apply plugin: 'com.github.zellius.shortcut-helper' apply plugin: 'com.github.zellius.shortcut-helper'
// Realm (EH) // Realm (EH)
apply plugin: 'realm-android' apply plugin: 'realm-android'
@@ -42,8 +43,8 @@ android {
minSdkVersion AndroidConfig.minSdk minSdkVersion AndroidConfig.minSdk
targetSdkVersion AndroidConfig.targetSdk targetSdkVersion AndroidConfig.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 4 versionCode 8
versionName "1.1.1" versionName "1.3.0"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@@ -65,7 +66,6 @@ android {
debug { debug {
versionNameSuffix "-${getCommitCount()}" versionNameSuffix "-${getCommitCount()}"
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
ext.enableCrashlytics = false
} }
releaseTest { releaseTest {
applicationIdSuffix ".rt" applicationIdSuffix ".rt"
@@ -140,32 +140,32 @@ dependencies {
// AndroidX libraries // AndroidX libraries
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.appcompat:appcompat:1.3.0-alpha01' implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
implementation 'androidx.biometric:biometric:1.0.1' implementation 'androidx.biometric:biometric:1.1.0-alpha02'
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-rc1' implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.core:core-ktx:1.4.0-alpha01'
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha04' implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.webkit:webkit:1.3.0-rc01'
final lifecycle_version = '2.3.0-alpha05' final lifecycle_version = '2.3.0-alpha07'
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Job scheduling // Job scheduling
final work_version = '2.4.0-rc01' final work_version = '2.5.0-alpha01'
implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.work:work-runtime-ktx:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version"
// UI library // UI library
implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.material:material:1.3.0-alpha02'
standardImplementation 'com.google.firebase:firebase-core:17.4.4' standardImplementation 'com.google.firebase:firebase-core:17.5.0'
// ReactiveX // ReactiveX
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
@@ -174,14 +174,14 @@ dependencies {
implementation 'com.github.pwittchen:reactivenetwork:0.13.0' implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
// Network client // Network client
final okhttp_version = '4.7.2' final okhttp_version = '4.9.0'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version" implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
implementation 'com.squareup.okio:okio:2.6.0' implementation 'com.squareup.okio:okio:2.8.0'
// TLS 1.3 support for Android < 10 // TLS 1.3 support for Android < 10
implementation 'org.conscrypt:conscrypt-android:2.4.0' implementation 'org.conscrypt:conscrypt-android:2.5.1'
// REST // REST
final retrofit_version = '2.9.0' final retrofit_version = '2.9.0'
@@ -214,10 +214,10 @@ dependencies {
implementation 'androidx.sqlite:sqlite:2.1.0' implementation 'androidx.sqlite:sqlite:2.1.0'
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar' implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar' implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
implementation 'io.requery:sqlite-android:3.31.0' implementation 'io.requery:sqlite-android:3.32.2'
// Preferences // Preferences
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.0' implementation 'com.github.tfcporciuncula:flow-preferences:1.3.1'
// Model View Presenter // Model View Presenter
final nucleus_version = '3.0.0' final nucleus_version = '3.0.0'
@@ -239,8 +239,7 @@ dependencies {
implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.timber:timber:4.7.1'
// Crash reports // Crash reports
//final acra_version = '5.5.0' //implementation 'ch.acra:acra-http:5.7.0'
//implementation "ch.acra:acra-http:$acra_version"
// Sort // Sort
implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1' implementation 'com.github.gpanther:java-nat-sort:natural-comparator-1.1'
@@ -278,13 +277,12 @@ dependencies {
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version" implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
// Licenses // Licenses
final aboutlibraries_version = '8.2.0' // NOTE: REMEMBER TO UPDATE GRADLE PLUGIN
implementation "com.mikepenz:aboutlibraries-core:$aboutlibraries_version" implementation 'com.mikepenz:aboutlibraries:8.3.0'
implementation "com.mikepenz:aboutlibraries:$aboutlibraries_version"
// Tests // Tests
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
testImplementation 'org.assertj:assertj-core:3.12.2' testImplementation 'org.assertj:assertj-core:3.16.1'
testImplementation 'org.mockito:mockito-core:1.10.19' testImplementation 'org.mockito:mockito-core:1.10.19'
final robolectric_version = '3.1.4' final robolectric_version = '3.1.4'
@@ -292,54 +290,38 @@ dependencies {
testImplementation "org.robolectric:shadows-multidex:$robolectric_version" testImplementation "org.robolectric:shadows-multidex:$robolectric_version"
testImplementation "org.robolectric:shadows-play-services:$robolectric_version" testImplementation "org.robolectric:shadows-play-services:$robolectric_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$BuildPluginsVersion.KOTLIN"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// SY for mangadex utils
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.0-RC"
final coroutines_version = '1.3.8' final coroutines_version = '1.3.9'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutines_version"
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2' // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
// Debug tool; see https://fbflipper.com/
// debugImplementation 'com.facebook.flipper:flipper:0.49.0'
// debugImplementation 'com.facebook.soloader:soloader:0.9.0'
// Text distance (EH) // Text distance (EH)
implementation 'info.debatty:java-string-similarity:1.2.1' implementation 'info.debatty:java-string-similarity:1.2.1'
// Reprint (EH)
implementation 'com.github.ajalt.reprint:core:3.2.1@aar'
implementation 'com.github.ajalt.reprint:rxjava:3.2.1@aar' // optional: the RxJava 1 interface
// Swirl (EH)
implementation 'com.mattprecious.swirl:swirl:1.2.0'
// RxJava 2 interop for Realm (EH)
implementation 'com.github.akarnokd:rxjava2-interop:0.13.7'
// Firebase (EH) // Firebase (EH)
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.google.firebase:firebase-analytics-ktx:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics-ktx:17.2.1'
// Better logging (EH) // Better logging (EH)
implementation 'com.elvishew:xlog:1.6.1' implementation 'com.elvishew:xlog:1.6.1'
// Time utils (EH)
def typed_time_version = '1.0.2'
implementation "com.github.kizitonwose.time:time:$typed_time_version"
implementation "com.github.kizitonwose.time:time-android:$typed_time_version"
// Debug utils (EH) // Debug utils (EH)
debugImplementation 'com.ms-square:debugoverlay:1.1.3' final def debug_overlay_version = '1.1.3'
releaseTestImplementation 'com.ms-square:debugoverlay:1.1.3' debugImplementation "com.ms-square:debugoverlay:$debug_overlay_version"
releaseImplementation 'com.ms-square:debugoverlay-no-op:1.1.3' releaseTestImplementation "com.ms-square:debugoverlay:$debug_overlay_version"
testImplementation 'com.ms-square:debugoverlay-no-op:1.1.3' releaseImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
testImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
// Humanize (EH) // Humanize (EH) used for E-Hentai updater statistics
implementation 'com.github.mfornos:humanize-slim:1.2.2' implementation 'com.github.mfornos:humanize-slim:1.2.2'
// RatingBar (SY) // RatingBar (SY)
@@ -347,7 +329,7 @@ dependencies {
implementation 'androidx.gridlayout:gridlayout:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0'
final def markwon_version = '4.1.0' final def markwon_version = '4.5.1'
implementation "io.noties.markwon:core:$markwon_version" implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:ext-strikethrough:$markwon_version" implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
@@ -356,16 +338,15 @@ dependencies {
implementation "io.noties.markwon:image:$markwon_version" implementation "io.noties.markwon:image:$markwon_version"
implementation "io.noties.markwon:linkify:$markwon_version" implementation "io.noties.markwon:linkify:$markwon_version"
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:29.0-android'
} }
buildscript { buildscript {
ext.kotlin_version = '1.3.72'
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$BuildPluginsVersion.KOTLIN"
} }
} }
@@ -375,7 +356,7 @@ repositories {
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers // See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
tasks.withType(AbstractKotlinCompile).all { tasks.withType(AbstractKotlinCompile).all {
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.Experimental"] kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.Experimental"]
} }
// Duplicating Hebrew string assets due to some locale code issues on different devices // Duplicating Hebrew string assets due to some locale code issues on different devices
@@ -385,10 +366,10 @@ task copyResources(type: Copy) {
include '**/*' include '**/*'
} }
preBuild.dependsOn(ktlintFormat, copyResources) preBuild.dependsOn(formatKotlin, copyResources)
if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Debug")) { if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Debug")) {
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
// Firebase (EH) // Firebase Crashlytics
apply plugin: 'io.fabric' apply plugin: 'com.google.firebase.crashlytics'
} }
+8
View File
@@ -37,6 +37,14 @@
public *; public *;
} }
# Hitomi extension crash fix
-keepclassmembers class rx.Single {
*** onSubscribe;
final *;
protected *;
public *;
}
# RxJava 1.1.0 # RxJava 1.1.0
-dontwarn sun.misc.** -dontwarn sun.misc.**
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 14 KiB

+2 -2
View File
@@ -282,7 +282,7 @@
android:scheme="https" /> android:scheme="https" />
<!-- MangaDex --> <!-- MangaDex -->
<!-- <data <data
android:host="mangadex.org" android:host="mangadex.org"
android:pathPattern="\/(title|manga)\/" android:pathPattern="\/(title|manga)\/"
android:scheme="http" /> android:scheme="http" />
@@ -297,7 +297,7 @@
<data <data
android:host="www.mangadex.org" android:host="www.mangadex.org"
android:pathPattern="\/(title|manga)\/" android:pathPattern="\/(title|manga)\/"
android:scheme="https" />--> android:scheme="https" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
+45 -18
View File
@@ -23,7 +23,9 @@ import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.kizitonwose.time.days import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.ms_square.debugoverlay.DebugOverlay import com.ms_square.debugoverlay.DebugOverlay
import com.ms_square.debugoverlay.modules.FpsModule import com.ms_square.debugoverlay.modules.FpsModule
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
@@ -34,13 +36,9 @@ import exh.debug.DebugToggles
import exh.log.CrashlyticsPrinter import exh.log.CrashlyticsPrinter
import exh.log.EHDebugModeOverlay import exh.log.EHDebugModeOverlay
import exh.log.EHLogLevel import exh.log.EHLogLevel
import exh.syDebugVersion
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.io.File
import java.security.NoSuchAlgorithmException
import java.security.Security
import javax.net.ssl.SSLContext
import kotlin.concurrent.thread
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
@@ -49,13 +47,23 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektScope import uy.kohesive.injekt.api.InjektScope
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.registry.default.DefaultRegistrar import uy.kohesive.injekt.registry.default.DefaultRegistrar
import java.io.File
import java.security.NoSuchAlgorithmException
import java.security.Security
import javax.net.ssl.SSLContext
import kotlin.concurrent.thread
import kotlin.time.ExperimentalTime
import kotlin.time.days
open class App : Application(), LifecycleObserver { open class App : Application(), LifecycleObserver {
private lateinit var firebaseAnalytics: FirebaseAnalytics
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
setupExhLogging() // EXH logging setupExhLogging() // EXH logging
if (!BuildConfig.DEBUG) addAnalytics()
workaroundAndroid7BrokenSSL() workaroundAndroid7BrokenSSL()
@@ -77,8 +85,8 @@ open class App : Application(), LifecycleObserver {
Injekt.importModule(AppModule(this)) Injekt.importModule(AppModule(this))
setupNotificationChannels() setupNotificationChannels()
Realm.init(this)
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH) GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
// Reprint.initialize(this) //Setup fingerprint (EH)
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) { if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
setupDebugOverlay() setupDebugOverlay()
} }
@@ -118,6 +126,13 @@ open class App : Application(), LifecycleObserver {
} }
} }
private fun addAnalytics() {
firebaseAnalytics = Firebase.analytics
if (syDebugVersion != "0") {
firebaseAnalytics.setUserProperty("preview_version", syDebugVersion)
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP) @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@Suppress("unused") @Suppress("unused")
fun onAppBackgrounded() { fun onAppBackgrounded() {
@@ -133,7 +148,6 @@ open class App : Application(), LifecycleObserver {
// EXH // EXH
private fun deleteOldMetadataRealm() { private fun deleteOldMetadataRealm() {
Realm.init(this)
val config = RealmConfiguration.Builder() val config = RealmConfiguration.Builder()
.name("gallery-metadata.realm") .name("gallery-metadata.realm")
.schemaVersion(3) .schemaVersion(3)
@@ -159,15 +173,14 @@ open class App : Application(), LifecycleObserver {
private fun setupExhLogging() { private fun setupExhLogging() {
EHLogLevel.init(this) EHLogLevel.init(this)
val logLevel = if (EHLogLevel.shouldLog(EHLogLevel.EXTRA)) { val logLevel = when {
LogLevel.ALL EHLogLevel.shouldLog(EHLogLevel.EXTRA) -> LogLevel.ALL
} else { BuildConfig.DEBUG -> LogLevel.DEBUG
LogLevel.WARN else -> LogLevel.WARN
} }
val logConfig = LogConfiguration.Builder() val logConfig = LogConfiguration.Builder()
.logLevel(logLevel) .logLevel(logLevel)
.t()
.st(2) .st(2)
.nb() .nb()
.build() .build()
@@ -180,14 +193,17 @@ open class App : Application(), LifecycleObserver {
"logs" "logs"
) )
@OptIn(ExperimentalTime::class)
printers += FilePrinter printers += FilePrinter
.Builder(logFolder.absolutePath) .Builder(logFolder.absolutePath)
.fileNameGenerator(object : DateFileNameGenerator() { .fileNameGenerator(
override fun generateFileName(logLevel: Int, timestamp: Long): String { object : DateFileNameGenerator() {
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}" override fun generateFileName(logLevel: Int, timestamp: Long): String {
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}.log"
}
} }
}) )
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.inMilliseconds.longValue)) .cleanStrategy(FileLastModifiedCleanStrategy(7.days.toLongMilliseconds()))
.backupStrategy(NeverBackupStrategy()) .backupStrategy(NeverBackupStrategy())
.build() .build()
@@ -202,6 +218,17 @@ open class App : Application(), LifecycleObserver {
) )
XLog.d("Application booting...") XLog.d("Application booting...")
XLog.nst().d(
"App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})\n" +
"Preview build: $syDebugVersion\n" +
"Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) \n" +
"Android build ID: ${Build.DISPLAY}\n" +
"Device brand: ${Build.BRAND}\n" +
"Device manufacturer: ${Build.MANUFACTURER}\n" +
"Device name: ${Build.DEVICE}\n" +
"Device model: ${Build.MODEL}\n" +
"Device product name: ${Build.PRODUCT}"
)
} }
// EXH // EXH
@@ -0,0 +1,5 @@
package eu.kanade.tachiyomi.annoations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Nsfw
@@ -8,9 +8,9 @@ import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) : class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@@ -36,8 +36,10 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: preferences.backupInterval().get() val interval = prefInterval ?: preferences.backupInterval().get()
if (interval > 0) { if (interval > 0) {
val request = PeriodicWorkRequestBuilder<BackupCreatorJob>( val request = PeriodicWorkRequestBuilder<BackupCreatorJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES TimeUnit.HOURS,
10,
TimeUnit.MINUTES
) )
.addTag(TAG) .addTag(TAG)
.build() .build()
@@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION
import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
import eu.kanade.tachiyomi.data.backup.models.Backup.MERGEDMANGAREFERENCES
import eu.kanade.tachiyomi.data.backup.models.Backup.SAVEDSEARCHES import eu.kanade.tachiyomi.data.backup.models.Backup.SAVEDSEARCHES
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
import eu.kanade.tachiyomi.data.backup.models.DHistory import eu.kanade.tachiyomi.data.backup.models.DHistory
@@ -39,6 +40,7 @@ import eu.kanade.tachiyomi.data.backup.serializer.CategoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.ChapterTypeAdapter import eu.kanade.tachiyomi.data.backup.serializer.ChapterTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.HistoryTypeAdapter import eu.kanade.tachiyomi.data.backup.serializer.HistoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.MangaTypeAdapter import eu.kanade.tachiyomi.data.backup.serializer.MangaTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.MergedMangaReferenceTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.TrackTypeAdapter import eu.kanade.tachiyomi.data.backup.serializer.TrackTypeAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.CategoryImpl import eu.kanade.tachiyomi.data.database.models.CategoryImpl
@@ -57,17 +59,23 @@ import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.EXHSavedSearch import exh.EXHSavedSearch
import exh.MERGED_SOURCE_ID
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import java.lang.RuntimeException import exh.merged.sql.models.MergedMangaReference
import kotlin.math.max import exh.util.asObservable
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import xyz.nulldev.ts.api.http.serializer.FilterSerializer import xyz.nulldev.ts.api.http.serializer.FilterSerializer
import java.lang.RuntimeException
import kotlin.math.max
class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
@@ -106,6 +114,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build()) .registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
.registerTypeAdapter<DHistory>(HistoryTypeAdapter.build()) .registerTypeAdapter<DHistory>(HistoryTypeAdapter.build())
.registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build()) .registerTypeHierarchyAdapter<TrackImpl>(TrackTypeAdapter.build())
// SY -->
.registerTypeAdapter<MergedMangaReference>(MergedMangaReferenceTypeAdapter.build())
// SY <--
.create() .create()
else -> throw Exception("Json version unknown") else -> throw Exception("Json version unknown")
} }
@@ -129,15 +140,21 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// Create extension ID/name mapping // Create extension ID/name mapping
val extensionEntries = JsonArray() val extensionEntries = JsonArray()
// Merged Manga References
val mergedMangaReferenceEntries = JsonArray()
// Add value's to root // Add value's to root
root[Backup.VERSION] = CURRENT_VERSION root[Backup.VERSION] = CURRENT_VERSION
root[Backup.MANGAS] = mangaEntries root[Backup.MANGAS] = mangaEntries
root[CATEGORIES] = categoryEntries root[CATEGORIES] = categoryEntries
root[EXTENSIONS] = extensionEntries root[EXTENSIONS] = extensionEntries
// SY -->
root[MERGEDMANGAREFERENCES] = mergedMangaReferenceEntries
// SY <--
databaseHelper.inTransaction { databaseHelper.inTransaction {
// Get manga from database // Get manga from database
val mangas = getFavoriteManga() val mangas = getFavoriteManga() /* SY --> */ + getMergedManga() /* SY <-- */
val extensions: MutableSet<String> = mutableSetOf() val extensions: MutableSet<String> = mutableSetOf()
@@ -163,6 +180,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// SY --> // SY -->
root[SAVEDSEARCHES] = root[SAVEDSEARCHES] =
Injekt.get<PreferencesHelper>().eh_savedSearches().get().joinToString(separator = "***") Injekt.get<PreferencesHelper>().eh_savedSearches().get().joinToString(separator = "***")
backupMergedMangaReferences(mergedMangaReferenceEntries)
// SY <-- // SY <--
} }
@@ -212,6 +231,13 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
} }
} }
// SY -->
private fun backupMergedMangaReferences(root: JsonArray) {
val mergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
mergedMangaReferences.forEach { root.add(parser.toJsonTree(it)) }
}
// SY <--
/** /**
* Backup the categories of library * Backup the categories of library
* *
@@ -317,29 +343,40 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
*/ */
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> { fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
// SY --> // SY -->
return ( if (source is MergedSource) {
if (source is EHentai) { val syncedChapters = runBlocking { source.fetchChaptersAndSync(manga, false) }
source.fetchChapterList(manga, throttleManager::throttle) return syncedChapters.onEach { pair ->
} else {
source.fetchChapterList(manga)
}
).map {
if (it.last().chapter_number == -99F) {
chapters.forEach { chapter ->
chapter.name = "Chapter ${chapter.chapter_number} restored by dummy source"
}
syncChaptersWithSource(databaseHelper, chapters, manga, source)
} else {
syncChaptersWithSource(databaseHelper, it, manga, source)
}
}
// SY <--
.doOnNext { pair ->
if (pair.first.isNotEmpty()) { if (pair.first.isNotEmpty()) {
chapters.forEach { it.manga_id = manga.id } chapters.forEach { it.manga_id = manga.id }
insertChapters(chapters) insertChapters(chapters)
} }
}.asObservable()
} else {
return (
if (source is EHentai) {
source.fetchChapterList(manga, throttleManager::throttle)
} else {
source.fetchChapterList(manga)
}
).map {
if (it.last().chapter_number == -99F) {
chapters.forEach { chapter ->
chapter.name =
"Chapter ${chapter.chapter_number} restored by dummy source"
}
syncChaptersWithSource(databaseHelper, chapters, manga, source)
} else {
syncChaptersWithSource(databaseHelper, it, manga, source)
}
} }
// SY <--
.doOnNext { pair ->
if (pair.first.isNotEmpty()) {
chapters.forEach { it.manga_id = manga.id }
insertChapters(chapters)
}
}
}
} }
/** /**
@@ -584,6 +621,49 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
} }
preferences.eh_savedSearches().set((otherSerialized + newSerialized).toSet()) preferences.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
} }
/**
* Restore the categories from Json
*
* @param jsonMergedMangaReferences array containing md manga references
*/
internal fun restoreMergedMangaReferences(jsonMergedMangaReferences: JsonArray) {
// Get merged manga references from file and from db
val dbMergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
val backupMergedMangaReferences = parser.fromJson<List<MergedMangaReference>>(jsonMergedMangaReferences)
var lastMergeManga: Manga? = null
// Iterate over them
backupMergedMangaReferences.forEach { mergedMangaReference ->
// Used to know if the merged manga reference is already in the db
var found = false
for (dbMergedMangaReference in dbMergedMangaReferences) {
// If the mergedMangaReference is already in the db, assign the id to the file's mergedMangaReference
// and do nothing
if (mergedMangaReference.mergeUrl == dbMergedMangaReference.mergeUrl && mergedMangaReference.mangaUrl == dbMergedMangaReference.mangaUrl) {
mergedMangaReference.id = dbMergedMangaReference.id
mergedMangaReference.mergeId = dbMergedMangaReference.mergeId
mergedMangaReference.mangaId = dbMergedMangaReference.mangaId
found = true
break
}
}
// If the mergedMangaReference isn't in the db, remove the id and insert a new mergedMangaReference
// Store the inserted id in the mergedMangaReference
if (!found) {
// Let the db assign the id
val mergedManga = (if (mergedMangaReference.mergeUrl != lastMergeManga?.url) databaseHelper.getManga(mergedMangaReference.mergeUrl, MERGED_SOURCE_ID).executeAsBlocking() else lastMergeManga) ?: return@forEach
val manga = databaseHelper.getManga(mergedMangaReference.mangaUrl, mergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach
lastMergeManga = mergedManga
mergedMangaReference.mergeId = mergedManga.id
mergedMangaReference.mangaId = manga.id
mergedMangaReference.id = null
val result = databaseHelper.insertMergedManga(mergedMangaReference).executeAsBlocking()
mergedMangaReference.id = result.insertedId()
}
}
}
// SY <-- // SY <--
/** /**
@@ -602,6 +682,9 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
internal fun getFavoriteManga(): List<Manga> = internal fun getFavoriteManga(): List<Manga> =
databaseHelper.getFavoriteMangas().executeAsBlocking() databaseHelper.getFavoriteMangas().executeAsBlocking()
internal fun getMergedManga(): List<Manga> =
databaseHelper.getMergedMangas().executeAsBlocking()
/** /**
* Inserts manga and returns id * Inserts manga and returns id
* *
@@ -11,9 +11,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.injectLazy
internal class BackupNotifier(private val context: Context) { internal class BackupNotifier(private val context: Context) {
@@ -33,14 +33,11 @@ import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import exh.EXHMigrations import exh.EXHMigrations
import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiThrottleManager
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -48,6 +45,10 @@ import kotlinx.coroutines.launch
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/** /**
* Restores backup from a JSON file. * Restores backup from a JSON file.
@@ -238,7 +239,7 @@ class BackupRestoreService : Service() {
} }
totalAmount = mangasJson.size() totalAmount = mangasJson.size()
restoreAmount = validManga.count() + 1 // +1 for categories restoreAmount = validManga.count() + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
skippedAmount = mangasJson.size() - validManga.count() skippedAmount = mangasJson.size() - validManga.count()
// SY <-- // SY <--
restoreProgress = 0 restoreProgress = 0
@@ -288,6 +289,15 @@ class BackupRestoreService : Service() {
restoreProgress += 1 restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.saved_searches)) showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.saved_searches))
} }
private fun restoreMergedMangaReferences(mergedMangaReferencesJson: JsonElement) {
db.inTransaction {
backupManager.restoreMergedMangaReferences(mergedMangaReferencesJson.asJsonArray)
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
}
// SY <-- // SY <--
private fun restoreManga(mangaJson: JsonObject) { private fun restoreManga(mangaJson: JsonObject) {
@@ -445,7 +455,12 @@ class BackupRestoreService : Service() {
return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */) return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */)
// If there's any error, return empty update and continue. // If there's any error, return empty update and continue.
.onErrorReturn { .onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}") val errorMessage = if (it is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
it.message
}
errors.add(Date() to "${manga.title} - $errorMessage")
Pair(emptyList(), emptyList()) Pair(emptyList(), emptyList())
} }
} }
@@ -7,16 +7,22 @@ import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy
object BackupRestoreValidator { object BackupRestoreValidator {
private val sourceManager: SourceManager by injectLazy()
private val trackManager: TrackManager by injectLazy()
/** /**
* Checks for critical backup file data. * Checks for critical backup file data.
* *
* @throws Exception if version or manga cannot be found. * @throws Exception if version or manga cannot be found.
* @return List of required sources. * @return List of missing sources or missing trackers.
*/ */
fun validate(context: Context, uri: Uri): Map<Long, String> { fun validate(context: Context, uri: Uri): Results {
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader()) val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser.parseReader(reader).asJsonObject val json = JsonParser.parseReader(reader).asJsonObject
@@ -26,11 +32,29 @@ object BackupRestoreValidator {
throw Exception(context.getString(R.string.invalid_backup_file_missing_data)) throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
} }
if (mangasJson.asJsonArray.size() == 0) { val mangas = mangasJson.asJsonArray
if (mangas.size() == 0) {
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga)) throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
} }
return getSourceMapping(json) val sources = getSourceMapping(json)
val missingSources = sources
.filter { sourceManager.get(it.key) == null }
.values
.sorted()
val trackers = mangas
.filter { it.asJsonObject.has("track") }
.flatMap { it.asJsonObject["track"].asJsonArray }
.map { it.asJsonObject["s"].asInt }
.distinct()
val missingTrackers = trackers
.mapNotNull { trackManager.getService(it) }
.filter { !it.isLogged }
.map { it.name }
.sorted()
return Results(missingSources, missingTrackers)
} }
fun getSourceMapping(json: JsonObject): Map<Long, String> { fun getSourceMapping(json: JsonObject): Map<Long, String> {
@@ -43,4 +67,6 @@ object BackupRestoreValidator {
} }
.toMap() .toMap()
} }
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
} }
@@ -19,6 +19,7 @@ object Backup {
const val VERSION = "version" const val VERSION = "version"
// SY --> // SY -->
const val SAVEDSEARCHES = "savedsearches" const val SAVEDSEARCHES = "savedsearches"
const val MERGEDMANGAREFERENCES = "mergedmangareferences"
// SY <-- // SY <--
fun getDefaultFilename(): String { fun getDefaultFilename(): String {
@@ -0,0 +1,45 @@
package eu.kanade.tachiyomi.data.backup.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
import exh.merged.sql.models.MergedMangaReference
/**
* JSON Serializer used to write / read [MergedMangaReference] to / from json
*/
object MergedMangaReferenceTypeAdapter {
fun build(): TypeAdapter<MergedMangaReference> {
return typeAdapter {
write {
beginArray()
value(it.mangaUrl)
value(it.mergeUrl)
value(it.mangaSourceId)
value(it.chapterSortMode)
value(it.chapterPriority)
value(it.getChapterUpdates)
value(it.isInfoManga)
value(it.downloadChapters)
endArray()
}
read {
beginArray()
MergedMangaReference(
id = null,
mangaUrl = nextString(),
mergeUrl = nextString(),
mangaSourceId = nextLong(),
chapterSortMode = nextInt(),
chapterPriority = nextInt(),
getChapterUpdates = nextBoolean(),
isInfoManga = nextBoolean(),
downloadChapters = nextBoolean(),
mangaId = null,
mergeId = null
)
}
}
}
}
@@ -10,8 +10,6 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import java.io.File
import java.io.IOException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -22,6 +20,8 @@ import okio.buffer
import okio.sink import okio.sink
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.IOException
/** /**
* Class used to create chapter cache * Class used to create chapter cache
@@ -21,6 +21,9 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries
import eu.kanade.tachiyomi.data.database.queries.TrackQueries import eu.kanade.tachiyomi.data.database.queries.TrackQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries
import exh.metadata.sql.mappers.SearchMetadataTypeMapping import exh.metadata.sql.mappers.SearchMetadataTypeMapping
import exh.metadata.sql.mappers.SearchTagTypeMapping import exh.metadata.sql.mappers.SearchTagTypeMapping
import exh.metadata.sql.mappers.SearchTitleTypeMapping import exh.metadata.sql.mappers.SearchTitleTypeMapping
@@ -36,7 +39,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* EXH --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries /* EXH <-- */ { MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@@ -51,11 +54,12 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(Category::class.java, CategoryTypeMapping()) .addTypeMapping(Category::class.java, CategoryTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping()) .addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.addTypeMapping(History::class.java, HistoryTypeMapping()) .addTypeMapping(History::class.java, HistoryTypeMapping())
// EXH --> // SY -->
.addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping()) .addTypeMapping(SearchMetadata::class.java, SearchMetadataTypeMapping())
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
// EXH <-- .addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
// SY <--
.build() .build()
inline fun inTransaction(block: () -> Unit) = db.inTransaction(block) inline fun inTransaction(block: () -> Unit) = db.inTransaction(block)
@@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.HistoryTable import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.MergedTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable import eu.kanade.tachiyomi.data.database.tables.TrackTable
import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable
import exh.metadata.sql.tables.SearchTitleTable import exh.metadata.sql.tables.SearchTitleTable
@@ -24,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = /* SY --> */ 3 /* SY <-- */ const val DATABASE_VERSION = /* SY --> */ 4 /* SY <-- */
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@@ -34,14 +34,12 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(CategoryTable.createTableQuery) execSQL(CategoryTable.createTableQuery)
execSQL(MangaCategoryTable.createTableQuery) execSQL(MangaCategoryTable.createTableQuery)
execSQL(HistoryTable.createTableQuery) execSQL(HistoryTable.createTableQuery)
// EXH --> // SY -->
execSQL(SearchMetadataTable.createTableQuery) execSQL(SearchMetadataTable.createTableQuery)
execSQL(SearchTagTable.createTableQuery) execSQL(SearchTagTable.createTableQuery)
execSQL(SearchTitleTable.createTableQuery) execSQL(SearchTitleTable.createTableQuery)
// EXH <--
// AZ -->
execSQL(MergedTable.createTableQuery) execSQL(MergedTable.createTableQuery)
// AZ <-- // SY <--
// DB indexes // DB indexes
execSQL(MangaTable.createUrlIndexQuery) execSQL(MangaTable.createUrlIndexQuery)
@@ -49,17 +47,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(ChapterTable.createMangaIdIndexQuery) execSQL(ChapterTable.createMangaIdIndexQuery)
execSQL(ChapterTable.createUnreadChaptersIndexQuery) execSQL(ChapterTable.createUnreadChaptersIndexQuery)
execSQL(HistoryTable.createChapterIdIndexQuery) execSQL(HistoryTable.createChapterIdIndexQuery)
// EXH --> // SY -->
db.execSQL(SearchMetadataTable.createUploaderIndexQuery) execSQL(SearchMetadataTable.createUploaderIndexQuery)
db.execSQL(SearchMetadataTable.createIndexedExtraIndexQuery) execSQL(SearchMetadataTable.createIndexedExtraIndexQuery)
db.execSQL(SearchTagTable.createMangaIdIndexQuery) execSQL(SearchTagTable.createMangaIdIndexQuery)
db.execSQL(SearchTagTable.createNamespaceNameIndexQuery) execSQL(SearchTagTable.createNamespaceNameIndexQuery)
db.execSQL(SearchTitleTable.createMangaIdIndexQuery) execSQL(SearchTitleTable.createMangaIdIndexQuery)
db.execSQL(SearchTitleTable.createTitleIndexQuery) execSQL(SearchTitleTable.createTitleIndexQuery)
// EXH <--
// AZ -->
execSQL(MergedTable.createIndexQuery) execSQL(MergedTable.createIndexQuery)
// AZ <-- // SY <--
} }
override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) { override fun onUpgrade(db: SupportSQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -70,6 +66,11 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(MangaTable.addDateAdded) db.execSQL(MangaTable.addDateAdded)
db.execSQL(MangaTable.backfillDateAdded) db.execSQL(MangaTable.backfillDateAdded)
} }
if (oldVersion < 12) {
db.execSQL(MergedTable.dropTableQuery)
db.execSQL(MergedTable.createTableQuery)
db.execSQL(MergedTable.createIndexQuery)
}
} }
override fun onConfigure(db: SupportSQLiteDatabase) { override fun onConfigure(db: SupportSQLiteDatabase) {
@@ -5,4 +5,8 @@ class LibraryManga : MangaImpl() {
var unread: Int = 0 var unread: Int = 0
var category: Int = 0 var category: Int = 0
// SY -->
var read: Int = 0
// SY <--
} }
@@ -32,6 +32,12 @@ interface Manga : SManga {
return genre?.split(", ")?.map { it.trim() } return genre?.split(", ")?.map { it.trim() }
} }
// SY -->
fun getOriginalGenres(): List<String>? {
return originalGenre?.split(", ")?.map { it.trim() }
}
// SY <--
private fun setFlags(flag: Int, mask: Int) { private fun setFlags(flag: Int, mask: Int) {
chapter_flags = chapter_flags and mask.inv() or (flag and mask) chapter_flags = chapter_flags and mask.inv() or (flag and mask)
} }
@@ -15,9 +15,9 @@ import java.util.Date
interface ChapterQueries : DbProvider { interface ChapterQueries : DbProvider {
// SY --> // SY -->
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id) fun getChapters(manga: Manga) = getChapters(manga.id)
fun getChaptersByMangaId(mangaId: Long?) = db.get() fun getChapters(mangaId: Long?) = db.get()
.listOfObjects(Chapter::class.java) .listOfObjects(Chapter::class.java)
.withQuery( .withQuery(
Query.builder() Query.builder()
@@ -27,15 +27,6 @@ interface ChapterQueries : DbProvider {
.build() .build()
) )
.prepare() .prepare()
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedChaptersQuery(mangaId))
.build()
)
.prepare()
// SY <-- // SY <--
fun getRecentChapters(date: Date) = db.get() fun getRecentChapters(date: Date) = db.get()
@@ -94,6 +85,17 @@ interface ChapterQueries : DbProvider {
.build() .build()
) )
.prepare() .prepare()
fun getChaptersReadByUrls(urls: List<String>) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(
Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} IN (?) AND (${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0)")
.whereArgs(urls.joinToString { "\"$it\"" })
.build()
)
.prepare()
// SY <-- // SY <--
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare() fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
interface MangaQueries : DbProvider { interface MangaQueries : DbProvider {
@@ -77,15 +78,6 @@ interface MangaQueries : DbProvider {
.prepare() .prepare()
// SY --> // SY -->
fun getMergedMangas(id: Long) = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getMergedMangaQuery(id))
.build()
)
.prepare()
fun updateMangaInfo(manga: Manga) = db.put() fun updateMangaInfo(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaInfoPutResolver()) .withPutResolver(MangaInfoPutResolver())
@@ -139,7 +131,7 @@ interface MangaQueries : DbProvider {
.byQuery( .byQuery(
DeleteQuery.builder() DeleteQuery.builder()
.table(MangaTable.TABLE) .table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?") .where("${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE})")
.whereArgs(0) .whereArgs(0)
.build() .build()
) )
@@ -1,21 +1,48 @@
package eu.kanade.tachiyomi.data.database.queries package eu.kanade.tachiyomi.data.database.queries
import exh.MERGED_SOURCE_ID
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged import exh.merged.sql.tables.MergedTable as Merged
// SY --> // SY -->
/** /**
* Query to get the manga merged into a merged manga * Query to get the manga merged into a merged manga
*/ */
fun getMergedMangaQuery(id: Long) = fun getMergedMangaQuery() =
""" """
SELECT ${Manga.TABLE}.* SELECT ${Manga.TABLE}.*
FROM ( FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get all the manga that are merged into other manga
*/
fun getAllMergedMangaQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE}
) AS M
JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
"""
/**
* Query to get the manga merged into a merged manga using the Url
*/
fun getMergedMangaFromUrlQuery() =
"""
SELECT ${Manga.TABLE}.*
FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_URL} = ?
) AS M ) AS M
JOIN ${Manga.TABLE} JOIN ${Manga.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = M.${Merged.COL_MANGA_ID}
@@ -24,16 +51,15 @@ fun getMergedMangaQuery(id: Long) =
/** /**
* Query to get the chapters of all manga in a merged manga * Query to get the chapters of all manga in a merged manga
*/ */
fun getMergedChaptersQuery(id: Long) = fun getMergedChaptersQuery() =
""" """
SELECT ${Chapter.TABLE}.* SELECT ${Chapter.TABLE}.*
FROM ( FROM (
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE ${Merged.COL_MERGE_ID} = ?
) AS M ) AS M
JOIN ${Chapter.TABLE} JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID} ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = M.${Merged.COL_MANGA_ID}
""" """
// SY <--
/** /**
* Query to get the manga from the library, with their categories and unread count. * Query to get the manga from the library, with their categories and unread count.
@@ -42,23 +68,55 @@ val libraryQuery =
""" """
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY} SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM ( FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD} SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
LEFT JOIN ( LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unread SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}, COUNT(*) AS unread
FROM ${Chapter.TABLE} FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 0 WHERE ${Chapter.COL_READ} = 0
GROUP BY ${Chapter.COL_MANGA_ID} GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
) AS C ) AS C
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID} ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 LEFT JOIN (
GROUP BY ${Manga.COL_ID} SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS read
FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 1
GROUP BY ${Chapter.COL_MANGA_ID}
) AS R
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} <> $MERGED_SOURCE_ID
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
UNION
SELECT ${Manga.TABLE}.*, COALESCE(C.unread, 0) AS ${Manga.COL_UNREAD}, COALESCE(R.read, 0) AS ${Manga.COL_READ}
FROM ${Manga.TABLE}
LEFT JOIN (
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as unread
FROM ${Merged.TABLE}
JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 0
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
) AS C
ON ${Manga.TABLE}.${Manga.COL_ID} = C.${Merged.COL_MERGE_ID}
LEFT JOIN (
SELECT ${Merged.TABLE}.${Merged.COL_MERGE_ID}, COUNT(*) as read
FROM ${Merged.TABLE}
JOIN ${Chapter.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = ${Merged.TABLE}.${Merged.COL_MANGA_ID}
WHERE ${Chapter.TABLE}.${Chapter.COL_READ} = 1
GROUP BY ${Merged.TABLE}.${Merged.COL_MERGE_ID}
) AS R
ON ${Manga.TABLE}.${Manga.COL_ID} = R.${Merged.COL_MERGE_ID}
WHERE ${Manga.COL_FAVORITE} = 1 AND ${Manga.COL_SOURCE} = $MERGED_SOURCE_ID
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
ORDER BY ${Manga.COL_TITLE} ORDER BY ${Manga.COL_TITLE}
) AS M ) AS M
LEFT JOIN ( LEFT JOIN (
SELECT * FROM ${MangaCategory.TABLE}) AS MC SELECT * FROM ${MangaCategory.TABLE}
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID} ) AS MC
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID};
""" """
// SY <--
/** /**
* Query to get the recent chapters of manga from the library up to a date. * Query to get the recent chapters of manga from the library up to a date.
@@ -18,6 +18,9 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
mapBaseFromCursor(manga, cursor) mapBaseFromCursor(manga, cursor)
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD)) manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY)) manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY))
// SY -->
manga.read = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_READ))
// SY <--
return manga return manga
} }
@@ -38,6 +38,10 @@ object MangaTable {
const val COL_UNREAD = "unread" const val COL_UNREAD = "unread"
// SY ->>
const val COL_READ = "read"
// SY <--
const val COL_CATEGORY = "category" const val COL_CATEGORY = "category"
const val COL_COVER_LAST_MODIFIED = "cover_last_modified" const val COL_COVER_LAST_MODIFIED = "cover_last_modified"
@@ -1,20 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object MergedTable {
const val TABLE = "merged"
const val COL_MERGE_ID = "mergeID"
const val COL_MANGA_ID = "mangaID"
val createTableQuery: String
get() =
"""CREATE TABLE $TABLE(
$COL_MERGE_ID INTEGER NOT NULL,
$COL_MANGA_ID INTEGER NOT NULL
)"""
val createIndexQuery: String
get() = "CREATE INDEX ${TABLE}_${COL_MERGE_ID}_index ON $TABLE($COL_MERGE_ID)"
}
@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
/** /**
* Cache where we dump the downloads directory from the filesystem. This class is needed because * Cache where we dump the downloads directory from the filesystem. This class is needed because
@@ -81,7 +81,7 @@ class DownloadCache(
if (sourceDir != null) { if (sourceDir != null) {
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] val mangaDir = sourceDir.files[provider.getMangaDirName(manga)]
if (mangaDir != null) { if (mangaDir != null) {
return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files } return provider.getValidChapterDirNames(chapter).any { it in mangaDir.files || "$it.cbz" in mangaDir.files }
} }
} }
return false return false
@@ -145,7 +145,7 @@ class DownloadCache(
mangaDirs.values.forEach { mangaDir -> mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir.listFiles() val chapterDirs = mangaDir.dir.listFiles()
.orEmpty() .orEmpty()
.mapNotNull { it.name } .mapNotNull { it.name?.replace(".cbz", "") }
.toHashSet() .toHashSet()
mangaDir.files = chapterDirs mangaDir.files = chapterDirs
@@ -196,10 +196,24 @@ class DownloadCache(
provider.getValidChapterDirNames(chapter).forEach { provider.getValidChapterDirNames(chapter).forEach {
if (it in mangaDir.files) { if (it in mangaDir.files) {
mangaDir.files -= it mangaDir.files -= it
} else if ("$it.cbz" in mangaDir.files) {
mangaDir.files -= "$it.cbz"
} }
} }
} }
// SY -->
fun removeFolders(folders: List<String>, manga: Manga) {
val sourceDir = rootDir.files[manga.source] ?: return
val mangaDir = sourceDir.files[provider.getMangaDirName(manga)] ?: return
folders.forEach { chapter ->
if (chapter in mangaDir.files) {
mangaDir.files -= chapter
}
}
}
// SY <--
/** /**
* Removes a list of chapters that have been deleted from this cache. * Removes a list of chapters that have been deleted from this cache.
* *
@@ -214,6 +228,8 @@ class DownloadCache(
provider.getValidChapterDirNames(chapter).forEach { provider.getValidChapterDirNames(chapter).forEach {
if (it in mangaDir.files) { if (it in mangaDir.files) {
mangaDir.files -= it mangaDir.files -= it
} else if ("$it.cbz" in mangaDir.files) {
mangaDir.files -= "$it.cbz"
} }
} }
} }
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@@ -24,10 +25,8 @@ import uy.kohesive.injekt.injectLazy
*/ */
class DownloadManager(/* SY private */ val context: Context) { class DownloadManager(/* SY private */ val context: Context) {
/** private val sourceManager: SourceManager by injectLazy()
* The sources manager. private val preferences: PreferencesHelper by injectLazy()
*/
private val sourceManager by injectLazy<SourceManager>()
/** /**
* Downloads provider, used to retrieve the folders where the chapters are or should be stored. * Downloads provider, used to retrieve the folders where the chapters are or should be stored.
@@ -199,16 +198,74 @@ class DownloadManager(/* SY private */ val context: Context) {
* @param manga the manga of the chapters. * @param manga the manga of the chapters.
* @param source the source of the chapters. * @param source the source of the chapters.
*/ */
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source) { fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
queue.remove(chapters) val filteredChapters = getChaptersToDelete(chapters)
val chapterDirs = provider.findChapterDirs(chapters, manga, source)
queue.remove(filteredChapters)
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
chapterDirs.forEach { it.delete() } chapterDirs.forEach { it.delete() }
cache.removeChapters(chapters, manga) cache.removeChapters(filteredChapters, manga)
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
chapterDirs.firstOrNull()?.parentFile?.delete() chapterDirs.firstOrNull()?.parentFile?.delete()
} }
return filteredChapters
} }
// SY -->
/**
* return the list of all manga folders
*/
fun getMangaFolders(source: Source): List<UniFile> {
return provider.findSourceDir(source)?.listFiles()?.toList() ?: emptyList()
}
/**
* Deletes the directories of chapters that were read or have no match
*
* @param chapters the list of chapters to delete.
* @param manga the manga of the chapters.
* @param source the source of the chapters.
*/
fun cleanupChapters(allChapters: List<Chapter>, manga: Manga, source: Source, removeRead: Boolean, removeNonFavorite: Boolean): Int {
var cleaned = 0
if (removeNonFavorite && !manga.favorite) {
val mangaFolder = provider.getMangaDir(manga, source)
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0)
mangaFolder.delete()
cache.removeManga(manga)
return cleaned
}
val filesWithNoChapter = provider.findUnmatchedChapterDirs(allChapters, manga, source)
cleaned += filesWithNoChapter.size
cache.removeFolders(filesWithNoChapter.mapNotNull { it.name }, manga)
filesWithNoChapter.forEach { it.delete() }
if (removeRead) {
val readChapters = allChapters.filter { it.read }
val readChapterDirs = provider.findChapterDirs(readChapters, manga, source)
readChapterDirs.forEach { it.delete() }
cleaned += readChapterDirs.size
cache.removeChapters(readChapters, manga)
}
if (cache.getDownloadCount(manga) == 0) {
val mangaFolder = provider.getMangaDir(manga, source)
val size = mangaFolder.listFiles()?.size ?: 0
if (size == 0) {
mangaFolder.delete()
cache.removeManga(manga)
} else {
Timber.e("Cache and download folder doesn't match for %s", manga.title)
}
}
return cleaned
}
// SY <--
/** /**
* Deletes the directory of a downloaded manga. * Deletes the directory of a downloaded manga.
* *
@@ -228,7 +285,7 @@ class DownloadManager(/* SY private */ val context: Context) {
* @param manga the manga of the chapters. * @param manga the manga of the chapters.
*/ */
fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) { fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) {
pendingDeleter.addChapters(chapters, manga) pendingDeleter.addChapters(getChaptersToDelete(chapters), manga)
} }
/** /**
@@ -257,14 +314,22 @@ class DownloadManager(/* SY private */ val context: Context) {
// Assume there's only 1 version of the chapter name formats present // Assume there's only 1 version of the chapter name formats present
val oldFolder = oldNames.asSequence() val oldFolder = oldNames.asSequence()
.mapNotNull { mangaDir.findFile(it) } .mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") }
.firstOrNull() .firstOrNull()
if (oldFolder?.renameTo(newName) == true) { if (oldFolder?.renameTo(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "") == true) {
cache.removeChapter(oldChapter, manga) cache.removeChapter(oldChapter, manga)
cache.addChapter(newName, mangaDir, manga) cache.addChapter(newName + if (oldFolder.name?.endsWith(".cbz") == true) ".cbz" else "", mangaDir, manga)
} else { } else {
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString()) Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
} }
} }
private fun getChaptersToDelete(chapters: List<Chapter>): List<Chapter> {
return if (!preferences.removeBookmarkedChapters()) {
chapters.filterNot { it.bookmark }
} else {
chapters
}
}
} }
@@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import java.util.regex.Pattern
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.regex.Pattern
/** /**
* DownloadNotifier is used to show notifications when downloading one or multiple chapters. * DownloadNotifier is used to show notifications when downloading one or multiple chapters.
@@ -79,7 +79,7 @@ internal class DownloadNotifier(private val context: Context) {
* Dismiss the downloader's notification. Downloader error notifications use a different id, so * Dismiss the downloader's notification. Downloader error notifications use a different id, so
* those can only be dismissed by the user. * those can only be dismissed by the user.
*/ */
fun dismiss() { fun dismissProgress() {
context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS) context.notificationManager.cancel(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
} }
@@ -107,7 +107,9 @@ internal class DownloadNotifier(private val context: Context) {
} }
val downloadingProgressText = context.getString( val downloadingProgressText = context.getString(
R.string.chapter_downloading_progress, download.downloadedImages, download.pages!!.size R.string.chapter_downloading_progress,
download.downloadedImages,
download.pages!!.size
) )
if (preferences.hideNotificationContent()) { if (preferences.hideNotificationContent()) {
@@ -89,7 +89,7 @@ class DownloadProvider(private val context: Context) {
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? { fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source) val mangaDir = findMangaDir(manga, source)
return getValidChapterDirNames(chapter).asSequence() return getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir?.findFile(it) } .mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") }
.firstOrNull() .firstOrNull()
} }
@@ -104,11 +104,37 @@ class DownloadProvider(private val context: Context) {
val mangaDir = findMangaDir(manga, source) ?: return emptyList() val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return chapters.mapNotNull { chapter -> return chapters.mapNotNull { chapter ->
getValidChapterDirNames(chapter).asSequence() getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir.findFile(it) } .mapNotNull { mangaDir.findFile(it) ?: mangaDir.findFile("$it.cbz") }
.firstOrNull() .firstOrNull()
} }
} }
// SY -->
/**
* Returns a list of all files in manga directory
*
* @param chapters the chapters to query.
* @param manga the manga of the chapter.
* @param source the source of the chapter.
*/
fun findUnmatchedChapterDirs(
chapters: List<Chapter>,
manga: Manga,
source: Source
): List<UniFile> {
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return mangaDir.listFiles()!!.asList().filter {
(
chapters.find { chp ->
getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
}
} == null
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
}
}
// SY <--
/** /**
* Returns the download directory name for a source. * Returns the download directory name for a source.
* *
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@@ -22,7 +23,6 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
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 eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import java.io.File
import kotlinx.coroutines.async import kotlinx.coroutines.async
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
@@ -30,7 +30,13 @@ import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/** /**
* This class is the one in charge of downloading chapters. * This class is the one in charge of downloading chapters.
@@ -53,6 +59,8 @@ class Downloader(
private val sourceManager: SourceManager private val sourceManager: SourceManager
) { ) {
private val preferences: PreferencesHelper by injectLazy()
private val chapterCache: ChapterCache by injectLazy() private val chapterCache: ChapterCache by injectLazy()
/** /**
@@ -139,6 +147,7 @@ class Downloader(
notifier.paused = false notifier.paused = false
notifier.onPaused() notifier.onPaused()
} else { } else {
notifier.dismissProgress()
notifier.onComplete() notifier.onComplete()
} }
} }
@@ -170,7 +179,7 @@ class Downloader(
.forEach { it.status = Download.NOT_DOWNLOADED } .forEach { it.status = Download.NOT_DOWNLOADED }
} }
queue.clear() queue.clear()
notifier.dismiss() notifier.dismissProgress()
} }
/** /**
@@ -266,15 +275,16 @@ class Downloader(
* @param download the chapter to be downloaded. * @param download the chapter to be downloaded.
*/ */
private fun downloadChapter(download: Download): Observable<Download> = Observable.defer { private fun downloadChapter(download: Download): Observable<Download> = Observable.defer {
val chapterDirname = provider.getChapterDirName(download.chapter)
val mangaDir = provider.getMangaDir(download.manga, download.source) val mangaDir = provider.getMangaDir(download.manga, download.source)
if (DiskUtil.getAvailableStorageSpace(mangaDir) < MIN_DISK_SPACE) { val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
download.status = Download.ERROR download.status = Download.ERROR
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name) notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
return@defer Observable.just(download) return@defer Observable.just(download)
} }
val chapterDirname = provider.getChapterDirName(download.chapter)
val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX) val tmpDir = mangaDir.createDirectory(chapterDirname + TMP_DIR_SUFFIX)
val pageListObservable = if (download.pages == null) { val pageListObservable = if (download.pages == null) {
@@ -462,7 +472,39 @@ class Downloader(
// Only rename the directory if it's downloaded. // Only rename the directory if it's downloaded.
if (download.status == Download.DOWNLOADED) { if (download.status == Download.DOWNLOADED) {
tmpDir.renameTo(dirname) if (preferences.saveChaptersAsCBZ().get()) {
val zip = mangaDir.createFile("$dirname.cbz.tmp")
val zipOut = ZipOutputStream(BufferedOutputStream(zip.openOutputStream()))
val compressionLevel = preferences.saveChaptersAsCBZLevel().get()
zipOut.setLevel(compressionLevel)
if (compressionLevel == 0) {
zipOut.setMethod(ZipEntry.STORED)
}
tmpDir.listFiles()?.forEach { img ->
val input = img.openInputStream()
val data = input.readBytes()
val entry = ZipEntry(img.name)
if (compressionLevel == 0) {
val crc = CRC32()
val size = img.length()
crc.update(data)
entry.crc = crc.value
entry.compressedSize = size
entry.size = size
}
zipOut.putNextEntry(entry)
zipOut.write(data)
input.close()
}
zipOut.close()
zip.renameTo("$dirname.cbz")
tmpDir.delete()
} else {
tmpDir.renameTo(dirname)
}
cache.addChapter(dirname, mangaDir, download.manga) cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context) DiskUtil.createNoMediaFile(tmpDir, context)
@@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadStore import eu.kanade.tachiyomi.data.download.DownloadStore
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import java.util.concurrent.CopyOnWriteArrayList
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.concurrent.CopyOnWriteArrayList
class DownloadQueue( class DownloadQueue(
private val store: DownloadStore, private val store: DownloadStore,
@@ -5,12 +5,12 @@ import android.util.Log
import com.bumptech.glide.Priority import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher import com.bumptech.glide.load.data.DataFetcher
import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import timber.log.Timber
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> { open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
@@ -14,10 +14,10 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.isLocal
import java.io.InputStream
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.InputStream
/** /**
* A class for loading a cover associated with a [Manga] that can be present in our own cache. * A class for loading a cover associated with a [Manga] that can be present in our own cache.
@@ -14,9 +14,9 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.module.AppGlideModule import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import java.io.InputStream
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.InputStream
/** /**
* Class used to update Glide module settings * Class used to update Glide module settings
@@ -29,7 +29,8 @@ class CustomMangaManager(val context: Context) {
val json = try { val json = try {
Gson().fromJson( Gson().fromJson(
Scanner(editJson).useDelimiter("\\Z").next(), JsonObject::class.java Scanner(editJson).useDelimiter("\\Z").next(),
JsonObject::class.java
) )
} catch (e: Exception) { } catch (e: Exception) {
null null
@@ -83,7 +84,12 @@ class CustomMangaManager(val context: Context) {
fun Manga.toJson(): MangaJson { fun Manga.toJson(): MangaJson {
return MangaJson( return MangaJson(
id!!, title, author, artist, description, genre?.split(", ")?.toTypedArray() id!!,
title,
author,
artist,
description,
genre?.split(", ")?.toTypedArray()
) )
} }
@@ -9,9 +9,9 @@ import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.util.concurrent.TimeUnit
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) : class LibraryUpdateJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@@ -45,8 +45,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.build() .build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>( val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
interval.toLong(), TimeUnit.HOURS, interval.toLong(),
10, TimeUnit.MINUTES TimeUnit.HOURS,
10,
TimeUnit.MINUTES
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
@@ -17,14 +17,15 @@ import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols import java.text.DecimalFormatSymbols
import uy.kohesive.injekt.injectLazy
class LibraryUpdateNotifier(private val context: Context) { class LibraryUpdateNotifier(private val context: Context) {
@@ -65,7 +66,7 @@ class LibraryUpdateNotifier(private val context: Context) {
* @param current the current progress. * @param current the current progress.
* @param total the total progress. * @param total the total progress.
*/ */
fun showProgressNotification(manga: Manga, current: Int, total: Int) { fun showProgressNotification(manga: /* SY --> */ SManga /* SY <-- */, current: Int, total: Int) {
val title = if (preferences.hideNotificationContent()) { val title = if (preferences.hideNotificationContent()) {
context.getString(R.string.notification_check_updates) context.getString(R.string.notification_check_updates)
} else { } else {
@@ -198,18 +199,23 @@ class LibraryUpdateNotifier(private val context: Context) {
// Mark chapters as read action // Mark chapters as read action
addAction( addAction(
R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read), R.drawable.ic_glasses_black_24dp,
context.getString(R.string.action_mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast( NotificationReceiver.markAsReadPendingBroadcast(
context, context,
manga, chapters, Notifications.ID_NEW_CHAPTERS manga,
chapters,
Notifications.ID_NEW_CHAPTERS
) )
) )
// View chapters action // View chapters action
addAction( addAction(
R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters), R.drawable.ic_book_24dp,
context.getString(R.string.action_view_chapters),
NotificationReceiver.openChapterPendingActivity( NotificationReceiver.openChapterPendingActivity(
context, context,
manga, Notifications.ID_NEW_CHAPTERS manga,
Notifications.ID_NEW_CHAPTERS
) )
) )
} }
@@ -6,6 +6,8 @@ import android.content.Intent
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@@ -17,10 +19,15 @@ import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.library.LibraryGroup
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
@@ -28,14 +35,25 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES import exh.LIBRARY_UPDATE_EXCLUDED_SOURCES
import java.io.File import exh.MERGED_SOURCE_ID
import java.util.concurrent.atomic.AtomicInteger import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil
import exh.metadata.metadata.base.insertFlatMetadata
import exh.source.EnhancedHttpSource.Companion.getMainSource
import exh.util.asObservable
import exh.util.await
import exh.util.awaitSingle
import exh.util.nullIfBlank
import kotlinx.coroutines.runBlocking
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Date
import java.util.concurrent.atomic.AtomicInteger
/** /**
* This class will take care of updating the chapters of the manga from the library. It can be * This class will take care of updating the chapters of the manga from the library. It can be
@@ -72,7 +90,10 @@ class LibraryUpdateService(
enum class Target { enum class Target {
CHAPTERS, // Manga chapters CHAPTERS, // Manga chapters
COVERS, // Manga covers COVERS, // Manga covers
TRACKING // Tracking metadata TRACKING, // Tracking metadata
// SY -->
SYNC_FOLLOWS // MangaDex specific, pull mangadex manga in reading, rereading
// SY <--
} }
companion object { companion object {
@@ -87,6 +108,14 @@ class LibraryUpdateService(
*/ */
const val KEY_TARGET = "target" const val KEY_TARGET = "target"
// SY -->
/**
* Key for group to update.
*/
const val KEY_GROUP = "group"
const val KEY_GROUP_EXTRA = "group_extra"
// SY <--
/** /**
* Returns the status of the service. * Returns the status of the service.
* *
@@ -106,11 +135,15 @@ class LibraryUpdateService(
* @param target defines what should be updated. * @param target defines what should be updated.
* @return true if service newly started, false otherwise * @return true if service newly started, false otherwise
*/ */
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS): Boolean { fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS /* SY --> */, group: Int = LibraryGroup.BY_DEFAULT, groupExtra: String? = null /* SY <-- */): Boolean {
if (!isRunning(context)) { if (!isRunning(context)) {
val intent = Intent(context, LibraryUpdateService::class.java).apply { val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(KEY_TARGET, target) putExtra(KEY_TARGET, target)
category?.let { putExtra(KEY_CATEGORY, it.id) } category?.let { putExtra(KEY_CATEGORY, it.id) }
// SY -->
putExtra(KEY_GROUP, group)
groupExtra?.let { putExtra(KEY_GROUP_EXTRA, it) }
// SY <--
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent) context.startService(intent)
@@ -194,6 +227,9 @@ class LibraryUpdateService(
Target.CHAPTERS -> updateChapterList(mangaList) Target.CHAPTERS -> updateChapterList(mangaList)
Target.COVERS -> updateCovers(mangaList) Target.COVERS -> updateCovers(mangaList)
Target.TRACKING -> updateTrackings(mangaList) Target.TRACKING -> updateTrackings(mangaList)
// SY -->
Target.SYNC_FOLLOWS -> syncFollows()
// SY <--
} }
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -221,10 +257,15 @@ class LibraryUpdateService(
*/ */
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> { fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
// SY -->
val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
// SY <--
var listToUpdate = if (categoryId != -1) { var listToUpdate = if (categoryId != -1) {
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
} else { // SY -->
} else if (group == LibraryGroup.BY_DEFAULT || groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.GLOBAL || (groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)) {
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
if (categoriesToUpdate.isNotEmpty()) { if (categoriesToUpdate.isNotEmpty()) {
db.getLibraryMangas().executeAsBlocking() db.getLibraryMangas().executeAsBlocking()
@@ -233,6 +274,43 @@ class LibraryUpdateService(
} else { } else {
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id } db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
} }
} else {
val libraryManga = db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
when (group) {
LibraryGroup.BY_TRACK_STATUS -> {
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
libraryManga.filter {
val loggedServices = trackManager.services.filter { it.isLogged }
val status: String = {
val tracks = db.getTracks(it).executeAsBlocking()
val track = tracks.find { track ->
loggedServices.any { it.id == track?.sync_id }
}
val service = loggedServices.find { it.id == track?.sync_id }
if (track != null && service != null) {
service.getStatus(track.status)
} else {
"not tracked"
}
}()
trackManager.mapTrackingOrder(status, applicationContext) == trackingExtra
}
}
LibraryGroup.BY_SOURCE -> {
val sourceExtra = intent.getStringExtra(KEY_GROUP_EXTRA).nullIfBlank()
val source = sourceManager.getCatalogueSources().find { it.name == sourceExtra }
if (source != null) libraryManga.filter { it.source == source.id } else emptyList()
}
LibraryGroup.BY_STATUS -> {
val statusExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
libraryManga.filter {
it.status == statusExtra
}
}
LibraryGroup.UNGROUPED -> libraryManga
else -> libraryManga
}
// SY <--
} }
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) { if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED } listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
@@ -275,7 +353,12 @@ class LibraryUpdateService(
updateManga(manga) updateManga(manga)
// If there's any error, return empty update and continue. // If there's any error, return empty update and continue.
.onErrorReturn { .onErrorReturn {
failedUpdates.add(Pair(manga, it.message)) val errorMessage = if (it is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
it.message
}
failedUpdates.add(Pair(manga, errorMessage))
Pair(emptyList(), emptyList()) Pair(emptyList(), emptyList())
} }
// Filter out mangas without new chapters (or failed). // Filter out mangas without new chapters (or failed).
@@ -328,7 +411,12 @@ class LibraryUpdateService(
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
// We don't want to start downloading while the library is updating, because websites // We don't want to start downloading while the library is updating, because websites
// may don't like it and they could ban the user. // may don't like it and they could ban the user.
downloadManager.downloadChapters(manga, chapters, false) // SY -->
val chapterFilter = if (manga.source == MERGED_SOURCE_ID) {
db.getMergedMangaReferences(manga.id!!).executeAsBlocking().filterNot { it.downloadChapters }.mapNotNull { it.mangaId }
} else emptyList()
// SY <--
downloadManager.downloadChapters(manga, /* SY --> */ chapters.filter { it.manga_id !in chapterFilter } /* SY <-- */, false)
} }
/** /**
@@ -338,7 +426,7 @@ class LibraryUpdateService(
* @return a pair of the inserted and removed chapters. * @return a pair of the inserted and removed chapters.
*/ */
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> { fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
val source = sourceManager.get(manga.source) ?: return Observable.empty() val source = sourceManager.getOrStub(manga.source)
// Update manga details metadata in the background // Update manga details metadata in the background
if (preferences.autoUpdateMetadata()) { if (preferences.autoUpdateMetadata()) {
@@ -360,8 +448,38 @@ class LibraryUpdateService(
.subscribe() .subscribe()
} }
return source.fetchChapterList(manga) // SY -->
.map { syncChaptersWithSource(db, it, manga, source) } if (source.getMainSource() is MangaDex && trackManager.mdList.isLogged) {
try {
val tracks = db.getTracks(manga).executeAsBlocking()
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
var track = trackManager.mdList.createInitialTracker(manga)
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
db.insertTrack(track).executeAsBlocking()
}
} catch (e: Exception) {
XLog.e(e)
}
}
// SY <--
return (
/* SY --> */ if (source is MergedSource) runBlocking { source.fetchChaptersAndSync(manga, false).asObservable() }
else /* SY <-- */ source.fetchChapterList(manga)
.map { syncChaptersWithSource(db, it, manga, source) }
// SY -->
)
.doOnNext {
if (source.getMainSource() is MangaDex) {
val tracks = db.getTracks(manga).executeAsBlocking()
if (tracks.isEmpty() || tracks.all { it.sync_id != TrackManager.MDLIST }) {
var track = trackManager.mdList.createInitialTracker(manga)
track = runBlocking { trackManager.mdList.refresh(track).awaitSingle() }
db.insertTrack(track).executeAsBlocking()
}
}
}
// SY <--
} }
private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> { private fun updateCovers(mangaToUpdate: List<LibraryManga>): Observable<LibraryManga> {
@@ -427,6 +545,48 @@ class LibraryUpdateService(
} }
} }
// SY -->
// filter all follows from Mangadex and only add reading or rereading manga to library
private fun syncFollows(): Observable<LibraryManga> {
val count = AtomicInteger(0)
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager)!!
return mangaDex.fetchAllFollows(true)
.asObservable()
.map { listManga ->
listManga.filter { (_, metadata) ->
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
}
}
.doOnNext { listManga ->
listManga.forEach { (networkManga, metadata) ->
notifier.showProgressNotification(networkManga, count.andIncrement, listManga.size)
var dbManga = db.getManga(networkManga.url, mangaDex.id)
.executeAsBlocking()
if (dbManga == null) {
dbManga = Manga.create(
networkManga.url,
networkManga.title,
mangaDex.id
)
dbManga.date_added = Date().time
}
dbManga.copyFrom(networkManga)
dbManga.favorite = true
val id = db.insertManga(dbManga).executeAsBlocking().insertedId()
if (id != null) {
metadata.mangaId = id
db.insertFlatMetadata(metadata.flatten()).await()
}
}
}
.doOnCompleted {
notifier.cancelProgressNotification()
}
.map { LibraryManga() }
}
// SY <--
/** /**
* Writes basic file of update errors to cache dir. * Writes basic file of update errors to cache dir.
*/ */
@@ -437,7 +597,8 @@ class LibraryUpdateService(
destFile.bufferedWriter().use { out -> destFile.bufferedWriter().use { out ->
errors.forEach { (manga, error) -> errors.forEach { (manga, error) ->
out.write("${manga.title}: $error\n") val source = sourceManager.getOrStub(manga.source)
out.write("${manga.title} ($source): $error\n")
} }
} }
return destFile return destFile
@@ -7,7 +7,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -26,10 +25,11 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import java.io.File
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
/** /**
* Global [BroadcastReceiver] that runs on UI thread * Global [BroadcastReceiver] that runs on UI thread
@@ -56,19 +56,22 @@ class NotificationReceiver : BroadcastReceiver() {
// Launch share activity and dismiss notification // Launch share activity and dismiss notification
ACTION_SHARE_IMAGE -> ACTION_SHARE_IMAGE ->
shareImage( shareImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION), context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Delete image from path and dismiss notification // Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> ACTION_DELETE_IMAGE ->
deleteImage( deleteImage(
context, intent.getStringExtra(EXTRA_FILE_LOCATION), context,
intent.getStringExtra(EXTRA_FILE_LOCATION),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> ACTION_SHARE_BACKUP ->
shareBackup( shareBackup(
context, intent.getParcelableExtra(EXTRA_URI), context,
intent.getParcelableExtra(EXTRA_URI),
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
ACTION_CANCEL_RESTORE -> cancelRestore( ACTION_CANCEL_RESTORE -> cancelRestore(
@@ -80,7 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
// Open reader activity // Open reader activity
ACTION_OPEN_CHAPTER -> { ACTION_OPEN_CHAPTER -> {
openChapter( openChapter(
context, intent.getLongExtra(EXTRA_MANGA_ID, -1), context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1) intent.getLongExtra(EXTRA_CHAPTER_ID, -1)
) )
} }
@@ -155,7 +159,7 @@ class NotificationReceiver : BroadcastReceiver() {
* @param mangaId id of manga * @param mangaId id of manga
* @param chapterId id of chapter * @param chapterId id of chapter
*/ */
internal fun openChapter(context: Context, mangaId: Long, chapterId: Long) { private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
val db = DatabaseHelper(context) val db = DatabaseHelper(context)
val manga = db.getManga(mangaId).executeAsBlocking() val manga = db.getManga(mangaId).executeAsBlocking()
val chapter = db.getChapter(chapterId).executeAsBlocking() val chapter = db.getChapter(chapterId).executeAsBlocking()
@@ -82,53 +82,62 @@ object Notifications {
listOf( listOf(
NotificationChannel( NotificationChannel(
CHANNEL_COMMON, context.getString(R.string.channel_common), CHANNEL_COMMON,
context.getString(R.string.channel_common),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
), ),
NotificationChannel( NotificationChannel(
CHANNEL_LIBRARY, context.getString(R.string.channel_library), CHANNEL_LIBRARY,
context.getString(R.string.channel_library),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_PROGRESS, context.getString(R.string.channel_progress), CHANNEL_DOWNLOADER_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_COMPLETE, context.getString(R.string.channel_complete), CHANNEL_DOWNLOADER_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_DOWNLOADER_ERROR, context.getString(R.string.channel_errors), CHANNEL_DOWNLOADER_ERROR,
context.getString(R.string.channel_errors),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_DOWNLOADER group = GROUP_DOWNLOADER
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters), CHANNEL_NEW_CHAPTERS,
context.getString(R.string.channel_new_chapters),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), ),
NotificationChannel( NotificationChannel(
CHANNEL_UPDATES_TO_EXTS, context.getString(R.string.channel_ext_updates), CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.channel_ext_updates),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
), ),
NotificationChannel( NotificationChannel(
CHANNEL_BACKUP_RESTORE_PROGRESS, context.getString(R.string.channel_progress), CHANNEL_BACKUP_RESTORE_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { ).apply {
group = GROUP_BACKUP_RESTORE group = GROUP_BACKUP_RESTORE
setShowBadge(false) setShowBadge(false)
}, },
NotificationChannel( NotificationChannel(
CHANNEL_BACKUP_RESTORE_COMPLETE, context.getString(R.string.channel_complete), CHANNEL_BACKUP_RESTORE_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
).apply { ).apply {
group = GROUP_BACKUP_RESTORE group = GROUP_BACKUP_RESTORE
@@ -97,6 +97,8 @@ object PreferenceKeys {
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key" const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
const val removeBookmarkedChapters = "pref_remove_bookmarked"
const val libraryUpdateInterval = "pref_library_update_interval_key" const val libraryUpdateInterval = "pref_library_update_interval_key"
const val libraryUpdateRestriction = "library_update_restriction" const val libraryUpdateRestriction = "library_update_restriction"
@@ -113,6 +115,8 @@ object PreferenceKeys {
const val filterCompleted = "pref_filter_completed_key" const val filterCompleted = "pref_filter_completed_key"
const val filterStarted = "pref_filter_started_key"
const val filterTracked = "pref_filter_tracked_key" const val filterTracked = "pref_filter_tracked_key"
const val filterLewd = "pref_filter_lewd_key" const val filterLewd = "pref_filter_lewd_key"
@@ -121,6 +125,8 @@ object PreferenceKeys {
const val automaticExtUpdates = "automatic_ext_updates" const val automaticExtUpdates = "automatic_ext_updates"
const val allowNsfwSource = "allow_nsfw_source"
const val startScreen = "start_screen" const val startScreen = "start_screen"
const val useBiometricLock = "use_biometric_lock" const val useBiometricLock = "use_biometric_lock"
@@ -183,8 +189,6 @@ object PreferenceKeys {
const val eh_lock_manually = "eh_lock_manually" const val eh_lock_manually = "eh_lock_manually"
const val eh_nh_useHighQualityThumbs = "eh_nh_hq_thumbs"
const val eh_showSyncIntro = "eh_show_sync_intro" const val eh_showSyncIntro = "eh_show_sync_intro"
const val eh_readOnlySync = "eh_sync_read_only" const val eh_readOnlySync = "eh_sync_read_only"
@@ -237,8 +241,6 @@ object PreferenceKeys {
const val eh_aggressivePageLoading = "eh_aggressive_page_loading" const val eh_aggressivePageLoading = "eh_aggressive_page_loading"
const val eh_hl_useHighQualityThumbs = "eh_hl_hq_thumbs"
const val eh_preload_size = "eh_preload_size" const val eh_preload_size = "eh_preload_size"
const val eh_tag_filtering_value = "eh_tag_filtering_value" const val eh_tag_filtering_value = "eh_tag_filtering_value"
@@ -273,7 +275,45 @@ object PreferenceKeys {
const val recommendsInOverflow = "recommends_in_overflow" const val recommendsInOverflow = "recommends_in_overflow"
const val hitomiAlwaysWebp = "hitomi_always_webp"
const val enhancedEHentaiView = "enhanced_e_hentai_view" const val enhancedEHentaiView = "enhanced_e_hentai_view"
const val webtoonEnableZoomOut = "webtoon_enable_zoom_out"
const val startReadingButton = "start_reading_button"
const val groupLibraryBy = "group_library_by"
const val continuousVerticalTappingByPage = "continuous_vertical_tapping_by_page"
const val groupLibraryUpdateType = "group_library_update_type"
const val useNewSourceNavigation = "use_new_source_navigation"
const val mangaDexLowQualityCovers = "manga_dex_low_quality_covers"
const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers"
const val preferredMangaDexId = "preferred_mangaDex_id"
const val dataSaver = "data_saver"
const val ignoreJpeg = "ignore_jpeg"
const val ignoreGif = "ignore_gif"
const val dataSaverImageQuality = "data_saver_image_quality"
const val dataSaverImageFormatJpeg = "data_saver_image_format_jpeg"
const val dataSaverServer = "data_saver_server"
const val dataSaverColorBW = "data_saver_color_bw"
const val saveChaptersAsCBZ = "save_chapter_as_cbz"
const val saveChaptersAsCBZLevel = "save_chapter_as_cbz_level"
const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders"
const val biometricTimeRanges = "biometric_time_ranges"
} }
@@ -23,11 +23,15 @@ object PreferenceValues {
default, default,
blue, blue,
amoled, amoled,
red,
} }
enum class DisplayMode { enum class DisplayMode {
COMPACT_GRID, COMPACT_GRID,
COMFORTABLE_GRID, COMFORTABLE_GRID,
// SY -->
NO_TITLE_GRID,
// SY <--
LIST, LIST,
} }
@@ -37,4 +41,18 @@ object PreferenceValues {
VERTICAL, VERTICAL,
BOTH BOTH
} }
enum class NsfwAllowance {
ALLOWED,
PARTIAL,
BLOCKED
}
// SY -->
enum class GroupLibraryMode {
GLOBAL,
ALL_BUT_UNGROUPED,
ALL
}
// SY <--
} }
@@ -7,18 +7,19 @@ import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.preference.PreferenceValues.NsfwAllowance
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import java.io.File import java.io.File
import java.text.DateFormat import java.text.DateFormat
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import kotlinx.coroutines.flow.Flow import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
import kotlinx.coroutines.flow.onEach
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> { fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
@@ -113,7 +114,7 @@ class PreferencesHelper(val context: Context) {
fun zoomStart() = flowPrefs.getInt(Keys.zoomStart, 1) fun zoomStart() = flowPrefs.getInt(Keys.zoomStart, 1)
fun readerTheme() = flowPrefs.getInt(Keys.readerTheme, 1) fun readerTheme() = flowPrefs.getInt(Keys.readerTheme, 3)
fun alwaysShowChapterTransition() = flowPrefs.getBoolean(Keys.alwaysShowChapterTransition, true) fun alwaysShowChapterTransition() = flowPrefs.getBoolean(Keys.alwaysShowChapterTransition, true)
@@ -187,6 +188,8 @@ class PreferencesHelper(val context: Context) {
fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false) fun removeAfterMarkedAsRead() = prefs.getBoolean(Keys.removeAfterMarkedAsRead, false)
fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false)
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24) fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi")) fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi"))
@@ -212,6 +215,8 @@ class PreferencesHelper(val context: Context) {
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0) fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0)
fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, 0)
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0) fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0)
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0) fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0)
@@ -222,6 +227,8 @@ class PreferencesHelper(val context: Context) {
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true) fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
fun allowNsfwSource() = flowPrefs.getEnum(Keys.allowNsfwSource, NsfwAllowance.ALLOWED)
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0) fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0) fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
@@ -307,8 +314,6 @@ class PreferencesHelper(val context: Context) {
fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "") fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "") fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
fun eh_nh_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_nh_useHighQualityThumbs, false)
fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true) fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false) fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
@@ -351,8 +356,6 @@ class PreferencesHelper(val context: Context) {
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false) fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
fun eh_hl_useHighQualityThumbs() = flowPrefs.getBoolean(Keys.eh_hl_useHighQualityThumbs, false)
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4) fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true) fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
@@ -377,7 +380,45 @@ class PreferencesHelper(val context: Context) {
fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false) fun recommendsInOverflow() = flowPrefs.getBoolean(Keys.recommendsInOverflow, false)
fun hitomiAlwaysWebp() = flowPrefs.getBoolean(Keys.hitomiAlwaysWebp, true)
fun enhancedEHentaiView() = flowPrefs.getBoolean(Keys.enhancedEHentaiView, true) fun enhancedEHentaiView() = flowPrefs.getBoolean(Keys.enhancedEHentaiView, true)
fun webtoonEnableZoomOut() = flowPrefs.getBoolean(Keys.webtoonEnableZoomOut, false)
fun startReadingButton() = flowPrefs.getBoolean(Keys.startReadingButton, true)
fun groupLibraryBy() = flowPrefs.getInt(Keys.groupLibraryBy, 0)
fun continuousVerticalTappingByPage() = flowPrefs.getBoolean(Keys.continuousVerticalTappingByPage, false)
fun groupLibraryUpdateType() = flowPrefs.getEnum(Keys.groupLibraryUpdateType, Values.GroupLibraryMode.GLOBAL)
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, false)
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
fun mangaDexForceLatestCovers() = flowPrefs.getBoolean(Keys.mangaDexForceLatestCovers, false)
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)
fun ignoreGif() = flowPrefs.getBoolean(Keys.ignoreGif, true)
fun dataSaverImageQuality() = flowPrefs.getInt(Keys.dataSaverImageQuality, 80)
fun dataSaverImageFormatJpeg() = flowPrefs.getBoolean(Keys.dataSaverImageFormatJpeg, false)
fun dataSaverServer() = flowPrefs.getString(Keys.dataSaverServer, "")
fun dataSaverColorBW() = flowPrefs.getBoolean(Keys.dataSaverColorBW, false)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean(Keys.saveChaptersAsCBZ, false)
fun saveChaptersAsCBZLevel() = flowPrefs.getInt(Keys.saveChaptersAsCBZLevel, 0)
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false)
fun biometricTimeRanges() = flowPrefs.getStringSet(Keys.biometricTimeRanges, mutableSetOf())
} }
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.preference package eu.kanade.tachiyomi.data.preference
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.core.content.edit
import androidx.preference.PreferenceDataStore import androidx.preference.PreferenceDataStore
class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() { class SharedPreferencesDataStore(private val prefs: SharedPreferences) : PreferenceDataStore() {
@@ -10,7 +11,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putBoolean(key: String?, value: Boolean) { override fun putBoolean(key: String?, value: Boolean) {
prefs.edit().putBoolean(key, value).apply() prefs.edit {
putBoolean(key, value)
}
} }
override fun getInt(key: String?, defValue: Int): Int { override fun getInt(key: String?, defValue: Int): Int {
@@ -18,7 +21,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putInt(key: String?, value: Int) { override fun putInt(key: String?, value: Int) {
prefs.edit().putInt(key, value).apply() prefs.edit {
putInt(key, value)
}
} }
override fun getLong(key: String?, defValue: Long): Long { override fun getLong(key: String?, defValue: Long): Long {
@@ -26,7 +31,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putLong(key: String?, value: Long) { override fun putLong(key: String?, value: Long) {
prefs.edit().putLong(key, value).apply() prefs.edit {
putLong(key, value)
}
} }
override fun getFloat(key: String?, defValue: Float): Float { override fun getFloat(key: String?, defValue: Float): Float {
@@ -34,7 +41,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putFloat(key: String?, value: Float) { override fun putFloat(key: String?, value: Float) {
prefs.edit().putFloat(key, value).apply() prefs.edit {
putFloat(key, value)
}
} }
override fun getString(key: String?, defValue: String?): String? { override fun getString(key: String?, defValue: String?): String? {
@@ -42,7 +51,9 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putString(key: String?, value: String?) { override fun putString(key: String?, value: String?) {
prefs.edit().putString(key, value).apply() prefs.edit {
putString(key, value)
}
} }
override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? { override fun getStringSet(key: String?, defValues: MutableSet<String>?): MutableSet<String>? {
@@ -50,6 +61,8 @@ class SharedPreferencesDataStore(private val prefs: SharedPreferences) : Prefere
} }
override fun putStringSet(key: String?, values: MutableSet<String>?) { override fun putStringSet(key: String?, values: MutableSet<String>?) {
prefs.edit().putStringSet(key, values).apply() prefs.edit {
putStringSet(key, values)
}
} }
} }
@@ -4,6 +4,7 @@ import android.content.Context
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
@@ -15,8 +16,24 @@ class TrackManager(context: Context) {
const val KITSU = 3 const val KITSU = 3
const val SHIKIMORI = 4 const val SHIKIMORI = 4
const val BANGUMI = 5 const val BANGUMI = 5
// SY --> Mangadex from Neko
const val MDLIST = 6
// SY <--
// SY -->
const val READING = 1
const val REREADING = 2
const val PLANTOREAD = 3
const val PAUSED = 4
const val COMPLETED = 5
const val DROPPED = 6
const val OTHER = 7
// SY <--
} }
val mdList = MdList(context, MDLIST)
val myAnimeList = MyAnimeList(context, MYANIMELIST) val myAnimeList = MyAnimeList(context, MYANIMELIST)
val aniList = Anilist(context, ANILIST) val aniList = Anilist(context, ANILIST)
@@ -27,9 +44,25 @@ class TrackManager(context: Context) {
val bangumi = Bangumi(context, BANGUMI) val bangumi = Bangumi(context, BANGUMI)
val services = listOf(myAnimeList, aniList, kitsu, shikimori, bangumi) val services = listOf(mdList, myAnimeList, aniList, kitsu, shikimori, bangumi)
fun getService(id: Int) = services.find { it.id == id } fun getService(id: Int) = services.find { it.id == id }
fun hasLoggedServices() = services.any { it.isLogged } fun hasLoggedServices(isMangaDexManga: Boolean = true) = services.any { it.isLogged && ((it.id == MDLIST && isMangaDexManga) || it.id != MDLIST) }
// SY -->
fun mapTrackingOrder(status: String, context: Context): Int {
with(context) {
return when (status) {
getString(eu.kanade.tachiyomi.R.string.reading), getString(eu.kanade.tachiyomi.R.string.currently_reading) -> READING
getString(eu.kanade.tachiyomi.R.string.repeating) -> REREADING
getString(eu.kanade.tachiyomi.R.string.plan_to_read), getString(eu.kanade.tachiyomi.R.string.want_to_read) -> PLANTOREAD
getString(eu.kanade.tachiyomi.R.string.on_hold), getString(eu.kanade.tachiyomi.R.string.paused) -> PAUSED
getString(eu.kanade.tachiyomi.R.string.completed) -> COMPLETED
getString(eu.kanade.tachiyomi.R.string.dropped) -> DROPPED
else -> OTHER
}
}
}
// SY <--
} }
@@ -13,12 +13,12 @@ import com.google.gson.JsonParser
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import java.util.Calendar
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable import rx.Observable
import java.util.Calendar
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
@@ -271,9 +271,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
return ALManga( return ALManga(
struct["id"].asInt, struct["title"]["romaji"].asString, struct["coverImage"]["large"].asString, struct["id"].asInt,
struct["description"].nullString.orEmpty(), struct["type"].asString, struct["status"].nullString.orEmpty(), struct["title"]["romaji"].asString,
date, struct["chapters"].nullInt ?: 0 struct["coverImage"]["large"].asString,
struct["description"].nullString.orEmpty(),
struct["type"].asString,
struct["status"].nullString.orEmpty(),
date,
struct["chapters"].nullInt ?: 0
) )
} }
@@ -4,9 +4,9 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import uy.kohesive.injekt.injectLazy
data class ALManga( data class ALManga(
val media_id: Int, val media_id: Int,
@@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import java.net.URLEncoder
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) { class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
@@ -7,10 +7,10 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import java.text.DecimalFormat
import rx.Completable import rx.Completable
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
class Kitsu(private val context: Context, id: Int) : TrackService(id) { class Kitsu(private val context: Context, id: Int) : TrackService(id) {
@@ -0,0 +1,134 @@
package eu.kanade.tachiyomi.data.track.mdlist
import android.content.Context
import android.graphics.Color
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata
import exh.util.asObservable
import exh.util.floor
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.runBlocking
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
class MdList(private val context: Context, id: Int) : TrackService(id) {
private val mdex by lazy { MdUtil.getEnabledMangaDex() }
private val db: DatabaseHelper by injectLazy()
override val name = "MDList"
override fun getLogo(): Int {
return R.drawable.ic_tracker_mangadex_logo
}
override fun getLogoColor(): Int {
return Color.rgb(43, 48, 53)
}
override fun getStatusList(): List<Int> {
return FollowStatus.values().map { it.int }
}
override fun getStatus(status: Int): String =
context.resources.getStringArray(R.array.md_follows_options).asList()[status]
override fun getScoreList() = IntRange(0, 10).map(Int::toString)
override fun displayScore(track: Track) = track.score.toInt().toString()
override fun add(track: Track): Observable<Track> {
return update(track)
}
override fun update(track: Track): Observable<Track> {
val mdex = mdex ?: throw Exception("Mangadex not enabled")
return Observable.defer {
db.getManga(track.tracking_url.substringAfter(".org"), mdex.id)
.asRxObservable()
.map { manga ->
val mangaMetadata = db.getFlatMetadataForManga(manga.id!!).executeAsBlocking()?.raise(MangaDexSearchMetadata::class) ?: throw Exception("Invalid manga metadata")
val followStatus = FollowStatus.fromInt(track.status)!!
// allow follow status to update
if (mangaMetadata.follow_status != followStatus.int) {
runBlocking { mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus).collect() }
mangaMetadata.follow_status = followStatus.int
db.insertFlatMetadata(mangaMetadata.flatten()).await()
}
if (track.score.toInt() > 0) {
runBlocking { mdex.updateRating(track).collect() }
}
// mangadex wont update chapters if manga is not follows this prevents unneeded network call
if (followStatus != FollowStatus.UNFOLLOWED) {
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
track.status = FollowStatus.COMPLETED.int
}
runBlocking { mdex.updateReadingProgress(track).collect() }
} else if (track.last_chapter_read != 0) {
// When followStatus has been changed to unfollowed 0 out read chapters since dex does
track.last_chapter_read = 0
}
track
}
}
}
override fun getCompletionStatus(): Int = FollowStatus.COMPLETED.int
override fun bind(track: Track): Observable<Track> {
val mdex = mdex ?: throw Exception("Mangadex not enabled")
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
.doOnNext { remoteTrack ->
track.copyPersonalFrom(remoteTrack)
track.total_chapters = if (remoteTrack.total_chapters == 0) {
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
} else {
remoteTrack.total_chapters
}
update(track)
}
}
override fun refresh(track: Track): Observable<Track> {
val mdex = mdex ?: throw Exception("Mangadex not enabled")
return mdex.fetchTrackingInfo(track.tracking_url).asObservable()
.map { remoteTrack ->
track.copyPersonalFrom(remoteTrack)
track.total_chapters = if (remoteTrack.total_chapters == 0) {
db.getChapters(track.manga_id).executeAsBlocking().maxOfOrNull { it.chapter_number }?.floor() ?: remoteTrack.total_chapters
} else {
remoteTrack.total_chapters
}
track
}
}
fun createInitialTracker(manga: Manga): Track {
val track = Track.create(TrackManager.MDLIST)
track.manga_id = manga.id!!
track.status = FollowStatus.UNFOLLOWED.int
track.tracking_url = MdUtil.baseUrl + manga.url
track.title = manga.title
return track
}
override fun search(query: String): Observable<List<TrackSearch>> = throw Exception("not used")
override fun login(username: String, password: String): Completable = throw Exception("not used")
}
@@ -11,13 +11,6 @@ import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.lang.toCalendar import eu.kanade.tachiyomi.util.lang.toCalendar
import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText
import java.io.BufferedReader
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.Locale
import java.util.zip.GZIPInputStream
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@@ -30,6 +23,13 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.parser.Parser import org.jsoup.parser.Parser
import rx.Observable import rx.Observable
import java.io.BufferedReader
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.Locale
import java.util.zip.GZIPInputStream
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) { class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
@@ -476,7 +476,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
fun copyPersonalFrom(track: Track) { fun copyPersonalFrom(track: Track) {
num_read_chapters = track.last_chapter_read.toString() num_read_chapters = track.last_chapter_read.toString()
val numScore = track.score.toInt() val numScore = track.score.toInt()
if (numScore in 1..9) { if (numScore == 0) {
score = ""
} else if (numScore in 1..10) {
score = numScore.toString() score = numScore.toString()
} }
status = track.status.toString() status = track.status.toString()
@@ -1,19 +0,0 @@
package eu.kanade.tachiyomi.data.updater
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
abstract class UpdateChecker {
companion object {
fun getUpdateChecker(): UpdateChecker {
// SY -->
return GithubUpdateChecker()
// SY <--
}
}
/**
* Returns observable containing release information
*/
abstract suspend fun checkForUpdate(): UpdateResult
}
@@ -13,9 +13,10 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
@@ -23,7 +24,7 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
override fun doWork(): Result { override fun doWork(): Result {
return runBlocking { return runBlocking {
try { try {
val result = UpdateChecker.getUpdateChecker().checkForUpdate() val result = GithubUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) { if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink val url = result.release.downloadLink
@@ -65,8 +66,10 @@ class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
.build() .build()
val request = PeriodicWorkRequestBuilder<UpdaterJob>( val request = PeriodicWorkRequestBuilder<UpdaterJob>(
3, TimeUnit.DAYS, 3,
3, TimeUnit.HOURS TimeUnit.DAYS,
3,
TimeUnit.HOURS
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
@@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import java.io.File
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
class UpdaterService : Service() { class UpdaterService : Service() {
@@ -1,13 +0,0 @@
package eu.kanade.tachiyomi.data.updater.devrepo
import eu.kanade.tachiyomi.data.updater.Release
class DevRepoRelease(override val info: String) : Release {
override val downloadLink: String
get() = LATEST_URL
companion object {
const val LATEST_URL = "https://tachiyomi.kanade.eu/latest"
}
}
@@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.data.updater.devrepo
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class DevRepoUpdateChecker : UpdateChecker() {
private val client: OkHttpClient by lazy {
Injekt.get<NetworkHelper>().client.newBuilder()
.followRedirects(false)
.build()
}
private val versionRegex: Regex by lazy {
Regex("tachiyomi-r(\\d+).apk")
}
override suspend fun checkForUpdate(): UpdateResult {
val response = withContext(Dispatchers.IO) {
client.newCall(GET(DevRepoRelease.LATEST_URL)).await(assertSuccess = false)
}
// Get latest repo version number from header in format "Location: tachiyomi-r1512.apk"
val latestVersionNumber: String = versionRegex.find(response.header("Location")!!)!!.groupValues[1]
return if (latestVersionNumber.toInt() > BuildConfig.COMMIT_COUNT.toInt()) {
DevRepoUpdateResult.NewUpdate(DevRepoRelease("v$latestVersionNumber"))
} else {
DevRepoUpdateResult.NoNewUpdate()
}
}
}
@@ -1,9 +0,0 @@
package eu.kanade.tachiyomi.data.updater.devrepo
import eu.kanade.tachiyomi.data.updater.UpdateResult
sealed class DevRepoUpdateResult : UpdateResult() {
class NewUpdate(release: DevRepoRelease) : UpdateResult.NewUpdate<DevRepoRelease>(release)
class NoNewUpdate : UpdateResult.NoNewUpdate()
}
@@ -28,5 +28,5 @@ class GithubRelease(
* Assets class containing download url. * Assets class containing download url.
* @param downloadLink download url. * @param downloadLink download url.
*/ */
inner class Assets(@SerializedName("browser_download_url") val downloadLink: String) class Assets(@SerializedName("browser_download_url") val downloadLink: String)
} }
@@ -4,11 +4,12 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
/** /**
* Used to connect with the GitHub API. * Used to connect with the GitHub API to get the latest release version from a repo.
*/ */
interface GithubService { interface GithubService {
@@ -24,11 +25,6 @@ interface GithubService {
} }
} }
// SY --> @GET("/repos/{repo}/releases/latest")
@GET("/repos/jobobby04/tachiyomiSY/releases/latest") suspend fun getLatestVersion(@Path("repo", encoded = true) repo: String): GithubRelease
suspend fun getLatestVersion(): GithubRelease
@GET("/repos/jobobby04/TachiyomiSYPreview/releases/latest")
suspend fun getLatestDebugVersion(): GithubRelease
// SY <--
} }
@@ -1,28 +1,49 @@
package eu.kanade.tachiyomi.data.updater.github package eu.kanade.tachiyomi.data.updater.github
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.updater.UpdateChecker
import eu.kanade.tachiyomi.data.updater.UpdateResult import eu.kanade.tachiyomi.data.updater.UpdateResult
import exh.syDebugVersion import exh.syDebugVersion
// SY -->
class GithubUpdateChecker(val debug: Boolean = false) : UpdateChecker() { class GithubUpdateChecker {
private val service: GithubService = GithubService.create() private val service: GithubService = GithubService.create()
override suspend fun checkForUpdate(): UpdateResult { private val repo: String by lazy {
val release = if (syDebugVersion != "0") { // Sy -->
service.getLatestDebugVersion() if (syDebugVersion != "0") {
"jobobby04/TachiyomiSYPreview"
} else { } else {
service.getLatestVersion() "jobobby04/tachiyomiSY"
} }
// SY <--
}
suspend fun checkForUpdate(): UpdateResult {
val release = service.getLatestVersion(repo)
val newVersion = release.version
// Check if latest version is different from current version // Check if latest version is different from current version
// SY -->
val newVersion = release.version
return if ((newVersion != BuildConfig.VERSION_NAME && (syDebugVersion == "0")) || ((syDebugVersion != "0") && newVersion != syDebugVersion)) { return if ((newVersion != BuildConfig.VERSION_NAME && (syDebugVersion == "0")) || ((syDebugVersion != "0") && newVersion != syDebugVersion)) {
// SY <--
GithubUpdateResult.NewUpdate(release) GithubUpdateResult.NewUpdate(release)
} else { } else {
GithubUpdateResult.NoNewUpdate() GithubUpdateResult.NoNewUpdate()
} }
} }
private fun isNewVersion(versionTag: String): Boolean {
// Removes prefixes like "r" or "v"
val newVersion = versionTag.replace("[^\\d.]".toRegex(), "")
return if (BuildConfig.DEBUG) {
// Preview builds: based on releases in "tachiyomiorg/android-app-preview" repo
// tagged as something like "r1234"
newVersion.toInt() > BuildConfig.COMMIT_COUNT.toInt()
} else {
// Release builds: based on releases in "inorichi/tachiyomi" repo
// tagged as something like "v0.1.2"
newVersion != BuildConfig.VERSION_NAME
}
}
} }
// SY <--
@@ -19,13 +19,8 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.EH_SOURCE_ID import exh.EH_SOURCE_ID
import exh.EIGHTMUSES_SOURCE_ID
import exh.EXH_SOURCE_ID import exh.EXH_SOURCE_ID
import exh.HITOMI_SOURCE_ID
import exh.MERGED_SOURCE_ID import exh.MERGED_SOURCE_ID
import exh.NHENTAI_SOURCE_ID
import exh.PERV_EDEN_EN_SOURCE_ID
import exh.PERV_EDEN_IT_SOURCE_ID
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import kotlinx.coroutines.async import kotlinx.coroutines.async
import rx.Observable import rx.Observable
@@ -83,11 +78,6 @@ class ExtensionManager(
return when (source.id) { return when (source.id) {
EH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source) EH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
EXH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source) EXH_SOURCE_ID -> context.getDrawable(R.mipmap.ic_ehentai_source)
PERV_EDEN_EN_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
PERV_EDEN_IT_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source)
NHENTAI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_nhentai_source)
HITOMI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_hitomi_source)
EIGHTMUSES_SOURCE_ID -> context.getDrawable(R.mipmap.ic_8muses_source)
MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source) MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source)
else -> null else -> null
} }
@@ -16,10 +16,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) : class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
@@ -73,8 +73,10 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam
.build() .build()
val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>( val request = PeriodicWorkRequestBuilder<ExtensionUpdateJob>(
12, TimeUnit.HOURS, 12,
1, TimeUnit.HOURS TimeUnit.HOURS,
1,
TimeUnit.HOURS
) )
.addTag(TAG) .addTag(TAG)
.setConstraints(constraints) .setConstraints(constraints)
@@ -1,44 +1,30 @@
package eu.kanade.tachiyomi.extension.api package eu.kanade.tachiyomi.extension.api
import android.content.Context import android.content.Context
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.string import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonArray import com.google.gson.JsonArray
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import java.util.Date
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Response
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
internal class ExtensionGithubApi { internal class ExtensionGithubApi {
private val network: NetworkHelper by injectLazy()
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
private val gson: Gson by injectLazy()
suspend fun findExtensions(): List<Extension.Available> { suspend fun findExtensions(): List<Extension.Available> {
val call = GET(EXT_URL) val service: ExtensionGithubService = ExtensionGithubService.create()
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val response = network.client.newCall(call).await() val response = service.getRepo()
if (response.isSuccessful) { parseResponse(response)
parseResponse(response)
} else {
response.close()
throw Exception("Failed to get extensions")
}
} }
} }
@@ -72,11 +58,7 @@ internal class ExtensionGithubApi {
return extensionsWithUpdate return extensionsWithUpdate
} }
private fun parseResponse(response: Response): List<Extension.Available> { private fun parseResponse(json: JsonArray): List<Extension.Available> {
val text = response.body?.use { it.string() } ?: return emptyList()
val json = gson.fromJson<JsonArray>(text)
return json return json
.filter { element -> .filter { element ->
val versionName = element["version"].string val versionName = element["version"].string
@@ -90,14 +72,15 @@ internal class ExtensionGithubApi {
val versionName = element["version"].string val versionName = element["version"].string
val versionCode = element["code"].int val versionCode = element["code"].int
val lang = element["lang"].string val lang = element["lang"].string
val icon = "$REPO_URL/icon/${apkName.replace(".apk", ".png")}" val nsfw = element["nsfw"].int == 1
val icon = "$REPO_URL_PREFIX/icon/${apkName.replace(".apk", ".png")}"
Extension.Available(name, pkgName, versionName, versionCode, lang, apkName, icon) Extension.Available(name, pkgName, versionName, versionCode, lang, nsfw, apkName, icon)
} }
} }
fun getApkUrl(extension: Extension.Available): String { fun getApkUrl(extension: Extension.Available): String {
return "$REPO_URL/apk/${extension.apkName}" return "$REPO_URL_PREFIX/apk/${extension.apkName}"
} }
// SY --> // SY -->
@@ -110,7 +93,7 @@ internal class ExtensionGithubApi {
// SY <-- // SY <--
companion object { companion object {
private const val REPO_URL = "https://raw.githubusercontent.com/inorichi/tachiyomi-extensions/repo" const val BASE_URL = "https://raw.githubusercontent.com/"
private const val EXT_URL = "$REPO_URL/index.json" const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo/"
} }
} }
@@ -0,0 +1,42 @@
package eu.kanade.tachiyomi.extension.api
import com.google.gson.JsonArray
import eu.kanade.tachiyomi.network.NetworkHelper
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import uy.kohesive.injekt.injectLazy
/**
* Used to get the extension repo listing from GitHub.
*/
interface ExtensionGithubService {
companion object {
private val client by lazy {
val network: NetworkHelper by injectLazy()
network.client.newBuilder()
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.header("Content-Encoding", "gzip")
.header("Content-Type", "application/json")
.build()
}
.build()
}
fun create(): ExtensionGithubService {
val adapter = Retrofit.Builder()
.baseUrl(ExtensionGithubApi.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return adapter.create(ExtensionGithubService::class.java)
}
}
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}index.json.gz")
suspend fun getRepo(): JsonArray
}
@@ -9,14 +9,16 @@ sealed class Extension {
abstract val versionName: String abstract val versionName: String
abstract val versionCode: Int abstract val versionCode: Int
abstract val lang: String? abstract val lang: String?
abstract val isNsfw: Boolean
data class Installed( data class Installed(
override val name: String, override val name: String,
override val pkgName: String, override val pkgName: String,
override val versionName: String, override val versionName: String,
override val versionCode: Int, override val versionCode: Int,
val sources: List<Source>,
override val lang: String, override val lang: String,
override val isNsfw: Boolean,
val sources: List<Source>,
val hasUpdate: Boolean = false, val hasUpdate: Boolean = false,
val isObsolete: Boolean = false, val isObsolete: Boolean = false,
val isUnofficial: Boolean = false, val isUnofficial: Boolean = false,
@@ -31,6 +33,7 @@ sealed class Extension {
override val versionName: String, override val versionName: String,
override val versionCode: Int, override val versionCode: Int,
override val lang: String, override val lang: String,
override val isNsfw: Boolean,
val apkName: String, val apkName: String,
val iconUrl: String val iconUrl: String
) : Extension() ) : Extension()
@@ -41,6 +44,7 @@ sealed class Extension {
override val versionName: String, override val versionName: String,
override val versionCode: Int, override val versionCode: Int,
val signatureHash: String, val signatureHash: String,
override val lang: String? = null override val lang: String? = null,
override val isNsfw: Boolean = false
) : Extension() ) : Extension()
} }
@@ -13,11 +13,11 @@ import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import java.io.File
import java.util.concurrent.TimeUnit
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber import timber.log.Timber
import java.io.File
import java.util.concurrent.TimeUnit
/** /**
* The installer which installs, updates and uninstalls the extensions. * The installer which installs, updates and uninstalls the extensions.
@@ -5,6 +5,8 @@ import android.content.Context
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import dalvik.system.PathClassLoader import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.annoations.Nsfw
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.model.LoadResult
@@ -15,8 +17,7 @@ import eu.kanade.tachiyomi.util.lang.Hash
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.api.get
/** /**
* Class that handles the loading of the extensions installed in the system. * Class that handles the loading of the extensions installed in the system.
@@ -24,20 +25,25 @@ import uy.kohesive.injekt.api.get
@SuppressLint("PackageManagerGetSignatures") @SuppressLint("PackageManagerGetSignatures")
internal object ExtensionLoader { internal object ExtensionLoader {
private val preferences: PreferencesHelper by injectLazy()
private val allowNsfwSource by lazy {
preferences.allowNsfwSource().get()
}
private const val EXTENSION_FEATURE = "tachiyomi.extension" private const val EXTENSION_FEATURE = "tachiyomi.extension"
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val LIB_VERSION_MIN = 1.2 const val LIB_VERSION_MIN = 1.2
const val LIB_VERSION_MAX = 1.2 const val LIB_VERSION_MAX = 1.2
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
// inorichi's key // inorichi's key
val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
/** /**
* List of the trusted signatures. * List of the trusted signatures.
*/ */
var trustedSignatures = mutableSetOf<String>() + var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
Injekt.get<PreferencesHelper>().trustedSignatures().get() + officialSignature
/** /**
* Return a list of all the installed extensions initialized concurrently. * Return a list of all the installed extensions initialized concurrently.
@@ -125,6 +131,11 @@ internal object ExtensionLoader {
return LoadResult.Untrusted(extension) return LoadResult.Untrusted(extension)
} }
val isNsfw = appInfo.metaData.getInt(METADATA_NSFW) == 1
if (allowNsfwSource == PreferenceValues.NsfwAllowance.BLOCKED && isNsfw) {
return LoadResult.Error("NSFW extension $pkgName not allowed")
}
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader) val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!! val sources = appInfo.metaData.getString(METADATA_SOURCE_CLASS)!!
@@ -141,7 +152,13 @@ internal object ExtensionLoader {
try { try {
when (val obj = Class.forName(it, false, classLoader).newInstance()) { when (val obj = Class.forName(it, false, classLoader).newInstance()) {
is Source -> listOf(obj) is Source -> listOf(obj)
is SourceFactory -> obj.createSources() is SourceFactory -> {
if (isSourceNsfw(obj)) {
emptyList()
} else {
obj.createSources()
}
}
else -> throw Exception("Unknown source class type! ${obj.javaClass}") else -> throw Exception("Unknown source class type! ${obj.javaClass}")
} }
} catch (e: Throwable) { } catch (e: Throwable) {
@@ -149,10 +166,11 @@ internal object ExtensionLoader {
return LoadResult.Error(e) return LoadResult.Error(e)
} }
} }
.filter { !isSourceNsfw(it) }
val langs = sources.filterIsInstance<CatalogueSource>() val langs = sources.filterIsInstance<CatalogueSource>()
.map { it.lang } .map { it.lang }
.toSet() .toSet()
val lang = when (langs.size) { val lang = when (langs.size) {
0 -> "" 0 -> ""
1 -> langs.first() 1 -> langs.first()
@@ -160,7 +178,13 @@ internal object ExtensionLoader {
} }
val extension = Extension.Installed( val extension = Extension.Installed(
extName, pkgName, versionName, versionCode, sources, lang, extName,
pkgName,
versionName,
versionCode,
lang,
isNsfw,
sources,
isUnofficial = signatureHash != officialSignature isUnofficial = signatureHash != officialSignature
) )
return LoadResult.Success(extension) return LoadResult.Success(extension)
@@ -188,4 +212,22 @@ internal object ExtensionLoader {
null null
} }
} }
/**
* Checks whether a Source or SourceFactory is annotated with @Nsfw.
*/
private fun isSourceNsfw(clazz: Any): Boolean {
if (allowNsfwSource == PreferenceValues.NsfwAllowance.ALLOWED) {
return false
}
if (clazz !is Source && clazz !is SourceFactory) {
return false
}
// Annotations are proxied, hence this janky way of checking for them
return clazz.javaClass.annotations
.flatMap { it.javaClass.interfaces.map { it.simpleName } }
.firstOrNull { it == Nsfw::class.java.simpleName } != null
}
} }
@@ -2,31 +2,29 @@ package eu.kanade.tachiyomi.network
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import androidx.webkit.WebViewClientCompat
import androidx.webkit.WebViewFeature
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.isOutdated
import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.setDefaultSettings
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class CloudflareInterceptor(private val context: Context) : Interceptor { class CloudflareInterceptor(private val context: Context) : Interceptor {
@@ -91,7 +89,8 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
var isWebViewOutdated = false var isWebViewOutdated = false
val origRequestUrl = request.url.toString() val origRequestUrl = request.url.toString()
val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" } val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
handler.post { handler.post {
val webview = WebView(context) val webview = WebView(context)
@@ -116,7 +115,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
} }
// HTTP error codes are only received since M // HTTP error codes are only received since M
if (WebViewFeature.isFeatureSupported(WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR) && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
url == origRequestUrl && !challengeFound url == origRequestUrl && !challengeFound
) { ) {
// The first request didn't return the challenge, abort. // The first request didn't return the challenge, abort.
@@ -124,13 +123,15 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
} }
} }
override fun onReceivedHttpError( override fun onReceivedErrorCompat(
view: WebView, view: WebView,
request: WebResourceRequest, errorCode: Int,
errorResponse: WebResourceResponse description: String?,
failingUrl: String,
isMainFrame: Boolean
) { ) {
if (request.isForMainFrame) { if (isMainFrame) {
if (errorResponse.statusCode == 503) { if (errorCode == 503) {
// Found the Cloudflare challenge page. // Found the Cloudflare challenge page.
challengeFound = true challengeFound = true
} else { } else {
@@ -3,15 +3,15 @@ package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit
import okhttp3.Cache import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit
/* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) { /* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) {
@@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@@ -13,6 +9,10 @@ import okhttp3.Response
import rx.Observable import rx.Observable
import rx.Producer import rx.Producer
import rx.Subscription import rx.Subscription
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
fun Call.asObservable(): Observable<Response> { fun Call.asObservable(): Observable<Response> {
return Observable.unsafeCreate { subscriber -> return Observable.unsafeCreate { subscriber ->
@@ -54,22 +54,24 @@ fun Call.asObservable(): Observable<Response> {
// Based on https://github.com/gildor/kotlin-coroutines-okhttp // Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(assertSuccess: Boolean = false): Response { suspend fun Call.await(assertSuccess: Boolean = false): Response {
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback { enqueue(
override fun onResponse(call: Call, response: Response) { object : Callback {
if (assertSuccess && !response.isSuccessful) { override fun onResponse(call: Call, response: Response) {
continuation.resumeWithException(Exception("HTTP error ${response.code}")) if (assertSuccess && !response.isSuccessful) {
return continuation.resumeWithException(Exception("HTTP error ${response.code}"))
return
}
continuation.resume(response)
} }
continuation.resume(response) override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
} }
)
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation { continuation.invokeOnCancellation {
try { try {
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.io.IOException
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okio.Buffer import okio.Buffer
@@ -8,6 +7,7 @@ import okio.BufferedSource
import okio.ForwardingSource import okio.ForwardingSource
import okio.Source import okio.Source
import okio.buffer import okio.buffer
import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
@@ -1,11 +1,11 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import java.util.concurrent.TimeUnit.MINUTES
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import java.util.concurrent.TimeUnit.MINUTES
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
private val DEFAULT_HEADERS = Headers.Builder().build() private val DEFAULT_HEADERS = Headers.Builder().build()
@@ -4,6 +4,7 @@ import android.content.Context
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@@ -15,6 +16,12 @@ import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.EpubFile import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.InputStream import java.io.InputStream
@@ -22,10 +29,6 @@ import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipFile
import junrar.Archive
import junrar.rarfile.FileHeader
import rx.Observable
import timber.log.Timber
class LocalSource(private val context: Context) : CatalogueSource { class LocalSource(private val context: Context) : CatalogueSource {
companion object { companion object {
@@ -74,6 +77,9 @@ class LocalSource(private val context: Context) : CatalogueSource {
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val baseDirs = getBaseDirectories(context) val baseDirs = getBaseDirectories(context)
// SY -->
val allowLocalSourceHiddenFolders = Injekt.get<PreferencesHelper>().allowLocalSourceHiddenFolders().get()
// SY <--
val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L val time = if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L
var mangaDirs = baseDirs var mangaDirs = baseDirs
@@ -81,6 +87,7 @@ class LocalSource(private val context: Context) : CatalogueSource {
.mapNotNull { it.listFiles()?.toList() } .mapNotNull { it.listFiles()?.toList() }
.flatten() .flatten()
.filter { it.isDirectory } .filter { it.isDirectory }
.filterNot { it.name.startsWith('.') /* SY --> */ && !allowLocalSourceHiddenFolders /* SY <-- */ }
.filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time } .filter { if (time == 0L) it.name.contains(query, ignoreCase = true) else it.lastModified() >= time }
.distinctBy { it.name } .distinctBy { it.name }
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.Hitomi import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.source.online.all.PervEden import eu.kanade.tachiyomi.source.online.all.PervEden
@@ -20,20 +21,24 @@ import eu.kanade.tachiyomi.source.online.english.HentaiCafe
import eu.kanade.tachiyomi.source.online.english.Pururin import eu.kanade.tachiyomi.source.online.english.Pururin
import eu.kanade.tachiyomi.source.online.english.Tsumino import eu.kanade.tachiyomi.source.online.english.Tsumino
import exh.EH_SOURCE_ID import exh.EH_SOURCE_ID
import exh.EIGHTMUSES_SOURCE_ID
import exh.EXH_SOURCE_ID import exh.EXH_SOURCE_ID
import exh.HBROWSE_SOURCE_ID
import exh.HENTAI_CAFE_SOURCE_ID
import exh.PERV_EDEN_EN_SOURCE_ID import exh.PERV_EDEN_EN_SOURCE_ID
import exh.PERV_EDEN_IT_SOURCE_ID import exh.PERV_EDEN_IT_SOURCE_ID
import exh.metadata.metadata.PervEdenLang import exh.PURURIN_SOURCE_ID
import exh.TSUMINO_SOURCE_ID
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import exh.source.EnhancedHttpSource import exh.source.EnhancedHttpSource
import kotlin.reflect.KClass
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.reflect.KClass
open class SourceManager(private val context: Context) { open class SourceManager(private val context: Context) {
@@ -104,7 +109,7 @@ open class SourceManager(private val context: Context) {
source, source,
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context) delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context)
) )
val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.originalSourceQualifiedClassName } val map = listOf(DelegatedSource(enhancedSource.originalSource.name, enhancedSource.originalSource.id, enhancedSource.originalSource::class.qualifiedName ?: delegate.originalSourceQualifiedClassName, (enhancedSource.enhancedSource as DelegatedHttpSource)::class, delegate.factory)).associateBy { it.sourceId }
currentDelegatedSources.plusAssign(map) currentDelegatedSources.plusAssign(map)
enhancedSource enhancedSource
} else source } else source
@@ -136,11 +141,6 @@ open class SourceManager(private val context: Context) {
if (prefs.enableExhentai().get()) { if (prefs.enableExhentai().get()) {
exSrcs += EHentai(EXH_SOURCE_ID, true, context) exSrcs += EHentai(EXH_SOURCE_ID, true, context)
} }
exSrcs += PervEden(PERV_EDEN_EN_SOURCE_ID, PervEdenLang.en, context)
exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it, context)
exSrcs += NHentai(context)
exSrcs += Hitomi(context)
exSrcs += EightMuses(context)
return exSrcs return exSrcs
} }
// SY <-- // SY <--
@@ -173,42 +173,74 @@ open class SourceManager(private val context: Context) {
// SY --> // SY -->
companion object { companion object {
private const val fillInSourceId = 9999L private const val fillInSourceId = Long.MAX_VALUE
val DELEGATED_SOURCES = listOf( val DELEGATED_SOURCES = listOf(
DelegatedSource( DelegatedSource(
"Hentai Cafe", "Hentai Cafe",
260868874183818481, HENTAI_CAFE_SOURCE_ID,
"eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe", "eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe",
HentaiCafe::class HentaiCafe::class
), ),
DelegatedSource( DelegatedSource(
"Pururin", "Pururin",
2221515250486218861, PURURIN_SOURCE_ID,
"eu.kanade.tachiyomi.extension.en.pururin.Pururin", "eu.kanade.tachiyomi.extension.en.pururin.Pururin",
Pururin::class Pururin::class
), ),
DelegatedSource( DelegatedSource(
"Tsumino", "Tsumino",
6707338697138388238, TSUMINO_SOURCE_ID,
"eu.kanade.tachiyomi.extension.en.tsumino.Tsumino", "eu.kanade.tachiyomi.extension.en.tsumino.Tsumino",
Tsumino::class Tsumino::class
)/*, ),
DelegatedSource( DelegatedSource(
"MangaDex", "MangaDex",
fillInSourceId, fillInSourceId,
"eu.kanade.tachiyomi.extension.all.mangadex", "eu.kanade.tachiyomi.extension.all.mangadex",
MangaDex::class, MangaDex::class,
true true
)*/, ),
DelegatedSource( DelegatedSource(
"HBrowse", "HBrowse",
1401584337232758222, HBROWSE_SOURCE_ID,
"eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse", "eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse",
HBrowse::class HBrowse::class
),
DelegatedSource(
"8Muses",
EIGHTMUSES_SOURCE_ID,
"eu.kanade.tachiyomi.extension.all.eromuse.EroMuse",
EightMuses::class
),
DelegatedSource(
"Hitomi",
fillInSourceId,
"eu.kanade.tachiyomi.extension.all.hitomi.Hitomi",
Hitomi::class,
true
),
DelegatedSource(
"PervEden English",
PERV_EDEN_EN_SOURCE_ID,
"eu.kanade.tachiyomi.extension.en.perveden.Perveden",
PervEden::class
),
DelegatedSource(
"PervEden Italian",
PERV_EDEN_IT_SOURCE_ID,
"eu.kanade.tachiyomi.extension.it.perveden.Perveden",
PervEden::class
),
DelegatedSource(
"NHentai",
fillInSourceId,
"eu.kanade.tachiyomi.extension.all.nhentai.NHentai",
NHentai::class,
true
) )
).associateBy { it.originalSourceQualifiedClassName } ).associateBy { it.originalSourceQualifiedClassName }
var currentDelegatedSources = mutableMapOf<String, DelegatedSource>() var currentDelegatedSources = mutableMapOf<Long, DelegatedSource>()
data class DelegatedSource( data class DelegatedSource(
val sourceName: String, val sourceName: String,
@@ -2,16 +2,26 @@ package eu.kanade.tachiyomi.source.model
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.network.ProgressListener import eu.kanade.tachiyomi.network.ProgressListener
import exh.util.DataSaver
import rx.subjects.Subject import rx.subjects.Subject
open class Page( open class Page(
val index: Int, val index: Int,
/* SY --> */ /* SY --> */
var /* SY <-- */ url: String = "", var /* SY <-- */ url: String = "",
var imageUrl: String? = null, /* SY --> var <-- SY */
imageUrl: String? = null,
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions @Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
) : ProgressListener { ) : ProgressListener {
// SY -->
var imageUrl = imageUrl
get() {
if (field == null) return null
return DataSaver().compress(field!!)
}
// SY <--
val number: Int val number: Int
get() = index + 1 get() = index + 1
@@ -75,6 +75,11 @@ interface SManga : Serializable {
const val ONGOING = 1 const val ONGOING = 1
const val COMPLETED = 2 const val COMPLETED = 2
const val LICENSED = 3 const val LICENSED = 3
// SY --> Mangadex specific statuses
const val PUBLICATION_COMPLETE = 61
const val CANCELLED = 62
const val HIATUS = 63
// SY <--
fun create(): SManga { fun create(): SManga {
return SMangaImpl() return SMangaImpl()
@@ -0,0 +1,8 @@
package eu.kanade.tachiyomi.source.online
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.Controller
interface BrowseSourceFilterHeader {
fun getFilterHeader(controller: Controller): RecyclerView.Adapter<*>
}
@@ -0,0 +1,30 @@
package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.md.utils.FollowStatus
import exh.metadata.metadata.base.RaisedSearchMetadata
import kotlinx.coroutines.flow.Flow
import rx.Observable
interface FollowsSource {
fun fetchFollows(): Observable<MangasPage>
/**
* Returns a list of all Follows retrieved by Coroutines
*
* @param SManga all smanga found for user
*/
fun fetchAllFollows(forceHd: Boolean = false): Flow<List<Pair<SManga, RaisedSearchMetadata>>>
/**
* updates the follow status for a manga
*/
fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Flow<Boolean>
/**
* Get a MdList Track of the manga
*/
fun fetchTrackingInfo(url: String): Flow<Track>
}
@@ -13,11 +13,9 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import exh.log.maybeInjectEHLogger
import exh.patch.injectPatches import exh.patch.injectPatches
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@@ -25,6 +23,9 @@ import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
/** /**
* A simple implementation for sources from a website. * A simple implementation for sources from a website.
@@ -36,22 +37,24 @@ abstract class HttpSource : CatalogueSource {
*/ */
// SY --> // SY -->
protected val network: NetworkHelper by lazy { protected val network: NetworkHelper by lazy {
val original = Injekt.get<NetworkHelper>() val network = Injekt.get<NetworkHelper>()
object : NetworkHelper(Injekt.get<Application>()) { object : NetworkHelper(Injekt.get<Application>()) {
override val client: OkHttpClient override val client: OkHttpClient
get() = delegate?.networkHttpClient ?: original.client get() = delegate?.networkHttpClient ?: network.client
.newBuilder() .newBuilder()
.injectPatches { id } .injectPatches { id }
.maybeInjectEHLogger()
.build() .build()
override val cloudflareClient: OkHttpClient override val cloudflareClient: OkHttpClient
get() = delegate?.networkCloudflareClient ?: original.cloudflareClient get() = delegate?.networkCloudflareClient ?: network.cloudflareClient
.newBuilder() .newBuilder()
.injectPatches { id } .injectPatches { id }
.maybeInjectEHLogger()
.build() .build()
override val cookieManager: AndroidCookieJar override val cookieManager: AndroidCookieJar
get() = original.cookieManager get() = network.cookieManager
} }
} }
// SY <-- // SY <--
@@ -88,7 +91,7 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Headers used for requests. * Headers used for requests.
*/ */
val headers: Headers by lazy { headersBuilder().build() } /* SY --> */ open /* SY <-- */ val headers: Headers by lazy { headersBuilder().build() }
/** /**
* Default network client for doing requests. * Default network client for doing requests.

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