Compare commits

..

330 Commits

Author SHA1 Message Date
Jobobby04 b021f57273 Update Readme, images and issue templates 2020-12-12 14:06:15 -05:00
Jobobby04 958b6d4b71 Release 1.4.0 2020-12-12 13:27:13 -05:00
Jobobby04 6f712c7f17 Lint 2020-12-12 12:36:29 -05:00
arkon 134f776a86 Hide incomplete NSFW source labelling settings
(cherry picked from commit a3b1690d38)
2020-12-12 12:21:32 -05:00
arkon d91ac659fd Force MAL logout on update
(cherry picked from commit a3bad75899)
2020-12-12 12:20:39 -05:00
arkon bd8f703c28 Update Gradle
(cherry picked from commit d1aaafbfff)
2020-12-12 12:20:31 -05:00
Jozef Hollý bbeca97524 Weblate translations (#4078)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: C201 <derasetad@gmail.com>
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: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Mario Kevin D. A <programas013@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Woodyx <shiposhouyou@gmail.com>
Co-authored-by: Yassin <yassinelaoud@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: sunbeams001 <sunbeams001@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
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/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
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/pl/
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/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: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: C201 <derasetad@gmail.com>
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: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Mario Kevin D. A <programas013@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Woodyx <shiposhouyou@gmail.com>
Co-authored-by: Yassin <yassinelaoud@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: sunbeams001 <sunbeams001@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 93d4af99bf)
2020-12-12 12:20:22 -05:00
arkon 6e346b231e Revert "Distinguish between no results and loading when sorting global search results"
This reverts commit 2be9871d05.

(cherry picked from commit c950595fe3)
2020-12-12 12:20:13 -05:00
arkon cb1a1e29be Don't scroll to top when navigating back/forward in WebView
(cherry picked from commit 8ffd3a8ed2)
2020-12-12 12:20:02 -05:00
arkon 7d11cc4837 Add text selection shortcut to invoke global search (closes #4058)
(cherry picked from commit b6e246c6b2)
2020-12-12 12:19:53 -05:00
arkon 0137262e4c Update to Kotlin 1.4.21 and kotlinter 3.3.0
(cherry picked from commit 59859e124f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
2020-12-12 12:19:44 -05:00
arkon 45086af3ae Use WebView auth flow for MAL (fixes #4100)
(cherry picked from commit 2bb7a33bc3)
2020-12-12 12:18:40 -05:00
Taco d33cb59af5 Update libraries, some lints (#4099)
* Update some plugins

* Fix some miscellaneous lints

(cherry picked from commit c2b8fea291)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
2020-12-12 12:18:30 -05:00
arkon 161d4e237f Update to Kotlin 1.4.20
(cherry picked from commit 08ab7f6aa0)
2020-12-12 12:11:29 -05:00
arkon 0db60d68f0 Use more up-to-date fork of junrar
(cherry picked from commit 560f0bba5c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt
2020-12-12 12:11:19 -05:00
arkon 11b10f00dd Update AndroidX and Material Components dependencies
(cherry picked from commit 722437a022)
2020-12-12 12:09:30 -05:00
Jobobby04 8fbc6aa29b Convert intercept activity to a stateflow 2020-11-30 20:34:48 -05:00
Jobobby04 ee18f94788 Convert Favorites sync to a StateFlow 2020-11-30 20:22:29 -05:00
Jobobby04 9a2ed755b7 Cleanup EH and AZ preferences 2020-11-30 15:11:58 -05:00
Jobobby04 f1a6218a4b Throw a error when the 509 gif is given when your downloading or loading a chapter from Exh and E-H 2020-11-30 13:21:56 -05:00
Jobobby04 bff654eac8 Cleanup 2020-11-30 13:19:41 -05:00
Jobobby04 5e5b155992 Run getRecs in IO 2020-11-30 13:19:28 -05:00
Jobobby04 dacc5502fc Fix community recommendation stalling 2020-11-30 13:19:02 -05:00
Jobobby04 e7169bda19 Fix exclude from deletion 2020-11-30 13:02:47 -05:00
Jobobby04 8110a2cabd Cleanup 2020-11-29 20:38:36 -05:00
Jobobby04 1118fe7cf7 More recs cleanup 2020-11-29 20:38:22 -05:00
Jobobby04 5b54fc8885 Localize the similar manga no results error 2020-11-29 20:38:07 -05:00
Jobobby04 2e75cc0cc9 Add title to similar manga header 2020-11-29 20:35:46 -05:00
arkon 3f89d8ec99 Add Galician locale
(cherry picked from commit 8a44b1dabe)
2020-11-29 18:53:06 -05:00
arkon b44ffd1d63 Fix annotations package name
(cherry picked from commit b39191ff50)
2020-11-29 18:52:50 -05:00
Jobobby04 9bad33930a Fix weird recs map sort 2020-11-29 18:52:32 -05:00
Jobobby04 e23b048d53 Fixes to reader ui from @CrepTF 2020-11-29 18:52:01 -05:00
Jozef Hollý d291b41080 Weblate translations (#3997)
Co-authored-by: AXEL IVAN GARCIA BERNAL <ga419623@uaeh.edu.mx>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andreas E <andreas.everos@gmail.com>
Co-authored-by: Aria Moradi <aria.moradi007@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: C201 <derasetad@gmail.com>
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: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Johkum <jacobomur@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Simon Mattila <simon.mattila@protonmail.com>
Co-authored-by: The Pumpkin God <sbh13112002@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Tristan Garnier <espeletpro@gmail.com>
Co-authored-by: Woodyx <shiposhouyou@gmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: george k <norhorn@gmail.com>
Co-authored-by: Николаев Павел Дмитриевич <pavliknikolaev128@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/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/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fa/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/gl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/he/
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/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/
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/ru/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/
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: AXEL IVAN GARCIA BERNAL <ga419623@uaeh.edu.mx>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Andreas E <andreas.everos@gmail.com>
Co-authored-by: Aria Moradi <aria.moradi007@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: C201 <derasetad@gmail.com>
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: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Johkum <jacobomur@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Simon Mattila <simon.mattila@protonmail.com>
Co-authored-by: The Pumpkin God <sbh13112002@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Tristan Garnier <espeletpro@gmail.com>
Co-authored-by: Woodyx <shiposhouyou@gmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: george k <norhorn@gmail.com>
Co-authored-by: Николаев Павел Дмитриевич <pavliknikolaev128@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 9814d20404)
2020-11-29 17:20:39 -05:00
arkon 177981da6c Use regular minified extensions JSON instead of explicitly handling gzip encoding
Turns out GitHub *does* do gzip encoding by default, it just doesn't show up in the header logs for some reason...

(cherry picked from commit 6664dfb048)
2020-11-29 17:20:14 -05:00
arkon a19d59cdf0 Fix crash when a preference doesn't have a title (e.g. info preference)
(cherry picked from commit 3133a63cf8)
2020-11-29 17:20:05 -05:00
Andreas E 6a1a7275c8 Enhance incognito mode (#4073)
* When in Incognito Mode don't set lastUsedSource

* When in Incognito Mode don't save chapter progress

Still allows tracking and mark as read when reaching last page

* When in Incognito Mode don't mark as read (overwritten if hasTrackers)

(cherry picked from commit c9c0f3d014)
2020-11-29 17:19:40 -05:00
Jobobby04 4a1e832bf5 Revert "Cool gray theme (#169)"
This reverts commit ac31f12138.
2020-11-29 17:19:29 -05:00
CrepeTF edc2065ea3 Tweak default dark theme colors (#4074)
(cherry picked from commit ff66f307dd)
2020-11-29 17:19:19 -05:00
arkon 0bb153fba9 Revert edge to edge
This was a bigger headache than it was worth. To note, phone landscape wasn't considered, so the navbar overlaps the content on the side. Additionally, the ability to programatically control the navbar scrim is missing, so it'd have to manually be added.

(cherry picked from commit e048d66f74)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2020-11-29 17:19:09 -05:00
arkon 495d63e66b Replace more Kotlin synthetics
(cherry picked from commit 66e3fa7df8)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2020-11-29 17:16:20 -05:00
Jobobby04 ede4f40133 Cleanup rec changes 2020-11-29 17:13:44 -05:00
she11sh0cked 2cefc93797 Rework code to enable UI rendered error messages (#172) 2020-11-29 16:24:25 -05:00
arkon 9d16b0efd2 Replace more Kotlin synthetics
(cherry picked from commit 019a0f31c7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceItem.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
2020-11-29 00:23:09 -05:00
arkon c9c808a782 Replace Kotlin synthetic views in viewholders
(cherry picked from commit 749c2071af)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MangaHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SourceHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt
2020-11-29 00:23:08 -05:00
Jobobby04 6f9edb7903 Add this back 2020-11-29 00:23:08 -05:00
arkon 7017b7b3ea Replace more usages of Kotlin synthetic views
(cherry picked from commit 322d66d282)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2020-11-29 00:23:07 -05:00
arkon 65cf11ec10 Fix keyboard sometimes affecting bottom inset padding
(cherry picked from commit aa98cd0da0)
2020-11-29 00:23:07 -05:00
arkon 02946af081 Move NSFW source setting into Browse settings
(cherry picked from commit c8316c7254)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt
2020-11-29 00:23:06 -05:00
arkon 2b627128a6 Fall back to filename in backup creation notification if path not available
(cherry picked from commit 6b9180844d)
2020-11-29 00:23:06 -05:00
arkon efa1f47392 Fix restore in Android 11
(cherry picked from commit c0e4863229)
2020-11-29 00:23:05 -05:00
arkon 3f55759b8b Distinguish between no results and loading when sorting global search results
(cherry picked from commit 2be9871d05)
2020-11-29 00:23:05 -05:00
arkon 41433eb262 More bottom inset padding fixes
(cherry picked from commit 776f6a9a16)
2020-11-29 00:23:04 -05:00
Jobobby04 160d6ea013 Replace more menu Tachi logo with SY logo 2020-11-29 00:23:03 -05:00
CrepeTF ac31f12138 Cool gray theme (#169)
* Added "Cool gray" (Google gray)

* Added "Cool gray" (Google gray) theme
2020-11-27 13:14:31 -05:00
arkon 9f542aaed4 Use jcenter to get 1.x dependencies
(cherry picked from commit 10163aab21)
2020-11-27 13:11:25 -05:00
arkon ec31d8605a Handle bottom insets in FAB controllers too
(cherry picked from commit 60b2a4ea9d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2020-11-27 13:10:32 -05:00
Jobobby04 ad38a79752 Use a shared flow for the exh redirect to latest 2020-11-27 13:01:58 -05:00
Jobobby04 643aa377bf Update XLog 2020-11-26 19:12:00 -05:00
Jobobby04 fcc2b1773b Update firebase 2020-11-25 15:49:33 -05:00
Jobobby04 92970bb812 Log to file cleanup 2020-11-25 15:13:06 -05:00
arkon 7129b79785 Further abstract how bottom navbar padding is applied
(cherry picked from commit 56e1e3e205)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
2020-11-25 15:12:35 -05:00
arkon 6aaa9dcdb7 Handle bottom navbar padding when drawing edge to edge
(cherry picked from commit 0f805cd45e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
2020-11-25 15:07:38 -05:00
arkon 5c76faa638 Create extension function for applying system insets
(cherry picked from commit 1d7c692e89)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2020-11-25 15:05:57 -05:00
arkon 0f6745d4a3 Add global search progress bar (closes #3727)
(cherry picked from commit 38bc8ec6b4)
2020-11-25 15:05:10 -05:00
arkon 438646727e Remove 10 results per source limit from global search (still limited to 1 page)
(cherry picked from commit 2154e3aa2d)
2020-11-25 15:04:45 -05:00
arkon bb7b79a6e9 Add help shortcut for source migration (closes #3951)
(cherry picked from commit 56c19e57a9)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt
2020-11-25 15:04:32 -05:00
arkon d0293fef0a Fix transparent navbar when bottom sheets are opened (fixes #3848)
(cherry picked from commit d548c690d6)
2020-11-25 14:59:44 -05:00
arkon 0228d4611a Use ViewCompat for setting MainActivity inset paddings
(cherry picked from commit 3fa70dade3)
2020-11-25 14:59:34 -05:00
arkon f594ee66e5 More restore code cleanup
(cherry picked from commit 368c30a2cc)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt
2020-11-25 14:59:24 -05:00
arkon 610cad3bc5 Update Firebase
(cherry picked from commit 5539e4591f)
2020-11-25 14:52:20 -05:00
arkon b25e604bc2 Set preview/experimental compiler flags instead of using annotations
(cherry picked from commit 781971ee81)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2020-11-25 14:52:06 -05:00
arkon b4be82d021 Pad bottom of MoreController so bottom nav doesn't overlap content
(cherry picked from commit 1140316d1b)
2020-11-21 18:13:15 -05:00
Jobobby04 9985646e58 Lint 2020-11-21 16:54:33 -05:00
arkon 383a797469 Some more code cleanup
(cherry picked from commit cf6c48744a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/AbstractBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupRestore.kt
2020-11-21 16:53:42 -05:00
arkon 692e7e17d8 Default automatic legacy backups to true for now
(cherry picked from commit eed6db8e92)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
2020-11-21 16:34:29 -05:00
arkon 978acec659 Some more code cleanup
(cherry picked from commit 858664bfd7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt
2020-11-21 16:33:45 -05:00
arkon 22019a8046 Merge restore entry points, avoid unnecessary new strings
(cherry picked from commit eceac4d6e3)
2020-11-21 16:31:37 -05:00
arkon b456d69aec Remove "full_" from v3 backup default file names
(cherry picked from commit 47a172df1f)
2020-11-21 16:31:27 -05:00
arkon a5abdaa5b1 Minor cleanup
(cherry picked from commit f2c0732c40)
2020-11-21 16:31:18 -05:00
arkon 8156804f7a Fix manga info description scrim on Android 5
(cherry picked from commit a150762c63)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
2020-11-21 16:30:59 -05:00
arkon be6f9d4a9f Fix non-transparent navigation bar
(cherry picked from commit 2695bdddf8)
2020-11-21 16:28:23 -05:00
Dominik Chrástecký 61b0039a78 add support for S Pen actions (#4029)
(cherry picked from commit c9b1a425a7)
2020-11-21 16:28:14 -05:00
arkon 72e95ea6fc Draw under navbar in Android 9+
(cherry picked from commit 122b2b1a8e)
2020-11-21 16:28:03 -05:00
arkon d6e7c1851b Update AGP for AS 4.1.1
(cherry picked from commit 2351c1b426)
2020-11-21 16:27:52 -05:00
arkon 99a27376d6 Remove TravisCI build config
(cherry picked from commit 0b5128dfd1)
2020-11-21 16:27:22 -05:00
arkon 97452ba9c8 Remove a stray trailing dash
(cherry picked from commit c0519e8670)
2020-11-21 16:27:14 -05:00
arkon 197019c65b Update dependencies
(cherry picked from commit fd545db1bd)
2020-11-21 16:27:02 -05:00
arkon 2b9c25cf71 Tweak manga info header
(cherry picked from commit 6991c224b2)

# Conflicts:
#	app/src/main/res/layout/manga_info_header.xml
2020-11-21 16:26:48 -05:00
arkon 76330f51c9 Add 1.x page model converters
(cherry picked from commit 7dc70c9eab)
2020-11-21 16:25:17 -05:00
arkon 3898a72cf8 Hide locale subtitle for local source
(cherry picked from commit e32445f2cf)
2020-11-21 16:25:07 -05:00
Jobobby04 1e98b09f24 Use more core-ktx 2020-11-21 16:24:16 -05:00
Jobobby04 2bece67f6e Fix Mangadex random button not showing results for some manga because they have no chapters 2020-11-13 22:48:08 -05:00
Jobobby04 7978dc9d2c Fix Mangadex random button returning the same result multiple times 2020-11-13 22:48:08 -05:00
jobobby04 9e41605512 [SKIP CI] Move back to r0adkll's sign release 2020-11-12 19:35:04 -05:00
Jobobby04 6e8ac9cc10 Fix Full backup, DELETE ALL FULL BACKUPS MADE BEFORE THIS UPDATE 2020-11-12 14:58:58 -05:00
Jobobby04 0594efb1c8 Cleanup 2020-11-11 17:30:38 -05:00
Jobobby04 a35e7871e8 Enhance file logging 2020-11-11 17:28:54 -05:00
Jobobby04 67aafab46a Forgot to commit this 2020-11-08 15:11:42 -05:00
Jobobby04 89a20be7ef Fix E-H Sync favorites getting stuck 2020-11-08 15:02:26 -05:00
Jobobby04 a4e05f297c Fix icon tint for share and save cover 2020-11-07 14:44:04 -05:00
Jobobby04 803ee3d547 Fix mangadex login dialog crash 2020-11-06 18:19:11 -05:00
Jobobby04 c6b0d0c9a5 Update SY french translation(From Adam-sama) 2020-11-06 15:50:46 -05:00
Jobobby04 d1887d4847 New Icon! Thanks to Aytox who made it! 2020-11-06 15:50:44 -05:00
Jobobby04 e987ba8c3e Lets try this out for full backup categories 2020-11-06 14:24:55 -05:00
Jobobby04 acefd33e2e Cleanup 2020-11-06 14:24:28 -05:00
Jobobby04 319c41905e Update latest tab with the new global search update 2020-11-05 00:31:00 -05:00
Jobobby04 079dd953bd Replace elvis operators with .orEmpty where possible 2020-11-04 22:10:13 -05:00
Jobobby04 015c610205 Use stable versions of RxUtil functions 2020-11-04 21:44:08 -05:00
Jobobby04 56ef3fd018 Remove unused values 2020-11-04 21:41:06 -05:00
arkon fe064a067e Update to serialization 1.0.1 and coroutines 1.4.1
(cherry picked from commit 8aa6486bf7)

# Conflicts:
#	app/build.gradle
2020-11-04 21:03:17 -05:00
arkon f283fbfd6f Singleton instance of Json serializer
(cherry picked from commit d21c147203)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/AppModule.kt
2020-11-04 21:01:55 -05:00
arkon 505a0db164 Fix Bangumi login (fixes #3994)
(cherry picked from commit 9b10e851d1)
2020-11-04 21:01:16 -05:00
arkon 4d00b2f8ce Update dependencies
(cherry picked from commit 6675508b24)

# Conflicts:
#	app/build.gradle
2020-11-04 21:01:07 -05:00
arkon c2ccbe5aff Use 1.x API to fetch manga details in browse view
(cherry picked from commit 7310ec4fe4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
2020-11-04 20:59:51 -05:00
Andreas E 98dade1b4d Change list view font size back to 16sp (#3978)
Make list view font size bigger again

(cherry picked from commit b1ce3693ed)
2020-11-04 20:58:51 -05:00
arkon eed7ef0aa8 Add Uzbek locale
(cherry picked from commit deb1ed5623)
2020-11-04 20:58:41 -05:00
Jozef Hollý 3f71795d05 Weblate translations (#3895)
Co-authored-by: Adam <bessaa935@gmail.com>
Co-authored-by: Ashraf Is <rediancool@gmail.com>
Co-authored-by: Ashraf Isl <rediancool@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Credits125Cre <credits125@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eva Godoy <godoytraduccion@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: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Luka Paun <croluxgame@gmail.com>
Co-authored-by: Lyaiya <hipsnafoha@outlook.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mubarek Seyd Juhar <mubareksej@gmail.com>
Co-authored-by: Nico <nicknicola94@gmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Pxeexe <johongira192@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Ruud Schouten <ruud.schouten@outlook.com>
Co-authored-by: Salem Almashaikhi <salemnabil1@outlook.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: opb <myniceaddress@outlook.com>
Co-authored-by: İlle <derasetad@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/am/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
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/ko/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/
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/uk/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uz/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/
Translation: Tachiyomi/Strings

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Adam <bessaa935@gmail.com>
Co-authored-by: Ashraf Is <rediancool@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Credits125Cre <credits125@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eva Godoy <godoytraduccion@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: Luka Paun <croluxgame@gmail.com>
Co-authored-by: Lyaiya <hipsnafoha@outlook.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mubarek Seyd Juhar <mubareksej@gmail.com>
Co-authored-by: Nico <nicknicola94@gmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Pxeexe <johongira192@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Ruud Schouten <ruud.schouten@outlook.com>
Co-authored-by: Salem Almashaikhi <salemnabil1@outlook.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: opb <myniceaddress@outlook.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 0902d7cca9)
2020-11-04 20:58:32 -05:00
arkon d9b41ce4c5 Fix Bangumi API response deserialization (fixes #3977)
(cherry picked from commit 95ec903862)
2020-11-04 20:58:22 -05:00
arkon 195dbbf1c2 Consume and extend 1.x Source API
TODO: make the rest of the app actually call the 1.x functions
(cherry picked from commit 2ab6af6471)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
2020-11-04 20:58:14 -05:00
Andreas E c12fb337f5 Replace language code in Global Search with secondary text (#3972)
Replace language code in Global Search with secondary text

(cherry picked from commit 9493577de2)
2020-11-04 20:56:45 -05:00
Jobobby04 4a4cdcb682 Add french translation for SY strings 2020-11-04 20:52:24 -05:00
Jobobby04 76de2d4447 Bind to the correct dialog view, fixed manga info edit crash 2020-11-04 17:18:10 -05:00
Jobobby04 7cbd7bd419 Cleanup 2020-11-01 19:19:57 -05:00
Jobobby04 8f1f6d5a97 Fix mangadex update interval not updating the interval on first click 2020-10-31 22:04:54 -04:00
Jobobby04 98bfe34e18 Update lewd manga checker with new sources and tags 2020-10-31 17:33:59 -04:00
Jobobby04 95c331b8b4 Convert all SY specific things to use ViewBindings instead of synthetics 2020-10-29 23:43:24 -04:00
Jobobby04 528f6c7f65 Drop humanize and guava(an older version still gets compiled in), use custom humanize solution for E-Hentai updater stats 2020-10-29 23:41:54 -04:00
Jobobby04 fe798e40cb Remove kotlin synthetics from E-Hentai settings 2020-10-29 21:52:27 -04:00
Jobobby04 133fe61408 Change manga edit to say Edit info 2020-10-29 21:50:44 -04:00
Jobobby04 3b5249c8bc Convert java threads to kotlin coroutines 2020-10-29 15:29:43 -04:00
Jobobby04 46998d81f4 Do a bit of cleanup 2020-10-29 15:28:48 -04:00
Jobobby04 fccf609c81 Fix NHentai covers 2020-10-29 14:25:37 -04:00
Jobobby04 406b5a89c8 Fix smart background for real this time, fix issues with theme not changing 2020-10-29 00:37:07 -04:00
Jobobby04 4881571293 Big refactor for the E-Hentai config builder, I sucked when making my additions, now its better 2020-10-29 00:13:25 -04:00
Jobobby04 9cba544ffd Refactor and cleanup a bunch of code 2020-10-27 23:05:53 -04:00
Jobobby04 114fb723dc Trim Mangadex descriptions, fixes extra blank lines 2020-10-27 17:59:21 -04:00
Jobobby04 8e03375664 Deeplink using pathPattern instead of pathPrefix 2020-10-26 21:53:31 -04:00
Jobobby04 6142e9be1c More mangadex deeplink tweaks 2020-10-26 21:51:28 -04:00
Jobobby04 24be0aa50b For E-Hentai manga display chapter date with the E-Hentai date format 2020-10-26 19:40:12 -04:00
Jobobby04 a7cfae1603 Mangadex deeplink tweaks 2020-10-26 19:39:37 -04:00
Jobobby04 079405c17e More tweaks to delegated manga views 2020-10-26 19:23:59 -04:00
Jobobby04 105302aa7b Fix delegated sources trying to always open in SY, even with links SY doesnt support 2020-10-26 14:53:48 -04:00
Jobobby04 05269c557d Make TF happy, cleanup the more info menu 2020-10-26 14:28:56 -04:00
Jobobby04 35217036ce Fix chapters in delegated manga where you haven't filtered any groups 2020-10-26 13:07:55 -04:00
Jobobby04 eb3a987826 Implement Neko similar manga, Mangadex only recommendations 2020-10-26 02:13:02 -04:00
Jobobby04 3f1dede133 Fix a oppsies 2020-10-25 23:41:15 -04:00
Jobobby04 e9cef78d19 Filter scanlators on delegated manga 2020-10-25 20:24:30 -04:00
Jobobby04 32232c80aa Extra info updates 2020-10-25 20:19:02 -04:00
Jobobby04 112bbdfcf7 Remove outdated comment 2020-10-25 18:12:36 -04:00
Jobobby04 415f30f38a Manga metadata is now easier to use in the manga view 2020-10-25 13:37:08 -04:00
Jobobby04 2a24f659d2 Disable things that dont work how I want them to 2020-10-24 23:56:40 -04:00
Jobobby04 484cb86ca9 Cleanup 2020-10-24 22:53:43 -04:00
Jobobby04 a21d7f4f90 Request a new image from E-Hentai if it fails to download, should make E-Hentai downloads more relaible 2020-10-24 22:00:45 -04:00
Jobobby04 a75cf8ec53 Attempt a new way to move chapters between EH manga versions 2020-10-24 21:59:56 -04:00
Jobobby04 b252a9e060 Fixed EH update helper crashes 2020-10-24 19:41:16 -04:00
Jobobby04 fa7c6716f4 Remove EH legacy code 2020-10-24 19:07:46 -04:00
Jobobby04 99b1f6e56f Cleanup 2020-10-24 19:07:16 -04:00
Jobobby04 a2cda24e01 Fix broken menu after clicking source, move latest to the top 2020-10-24 17:17:00 -04:00
Jobobby04 c049ce9018 Convert E-Hentai Update helper to Flows 2020-10-24 16:44:20 -04:00
Jobobby04 1a0109df1d Cleanup 2020-10-24 15:44:52 -04:00
Jobobby04 c999229700 Use bundleOf everywhere I can 2020-10-24 14:28:03 -04:00
arkon 86fc50d62b Janky workaround for collapsed global search covers
(cherry picked from commit 837ce62844)
2020-10-24 14:15:19 -04:00
Andreas E b9bddd264b Update Global search layout to match Library and Browse (#3892)
* Update Global Search card item to be comfortable with rounded corners

* Remove card background

(cherry picked from commit a2b1acd70f)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchCardHolder.kt
#	app/src/main/res/layout/global_search_controller_card.xml
#	app/src/main/res/layout/global_search_controller_comfortable_card_item.xml
2020-10-24 14:08:28 -04:00
Andreas E 4937c7fff2 Fix missing chapter warning when chapter number is not recognized (#3928)
* Fix missing chapter warning when chapter number is not recognized

* Add case where ch number is recognized as zero but has no ch number

Yes this will ignore ch 0

* Use RegEx to double check if ch. name contains potential ch. number

(cherry picked from commit f1350bc33e)
2020-10-24 13:41:22 -04:00
arkon 6419d79960 Fix Shikimori login (closes #3966)
(cherry picked from commit 6af0eb4068)
2020-10-24 13:41:13 -04:00
arkon 295fe37a9d Use default height for extended FAB (closes #3949)
(cherry picked from commit 538c168641)
2020-10-24 13:41:04 -04:00
arkon 3ff2321a95 Lower minWidth for extended FAB (fixes #3939)
(cherry picked from commit 832a4fa68e)
2020-10-24 13:40:54 -04:00
arkon 8322f7b95f Show locale in sources list items
(cherry picked from commit ed41604f56)
2020-10-24 13:40:40 -04:00
Jobobby04 3ed0ef5e3e Cleanup 2020-10-24 13:39:28 -04:00
Jobobby04 b850a8729f Remove old backup code I tried for dummy restore 2020-10-24 13:39:04 -04:00
Jobobby04 024a39d06b Lets try this for sort by tag performance 2020-10-24 13:38:45 -04:00
Jobobby04 01496ab34c Cleanup some merged manga code 2020-10-24 13:38:25 -04:00
Jobobby04 9a45891ed6 Fix saving cover putting it in a weird folder, cleanup 2020-10-21 23:02:32 -04:00
Jobobby04 5e531ba469 Put manga metadata into backups 2020-10-21 23:01:57 -04:00
Jobobby04 cb6a991e9f Fix share cover name, make ainz happy by hiding other options 2020-10-21 17:03:15 -04:00
Jobobby04 96989bfa53 Likely fix smart background 2020-10-21 14:59:39 -04:00
Jobobby04 b1048d8014 Fix migrate extra data like chapter filters and viewer 2020-10-20 16:13:34 -04:00
Jobobby04 89d9768759 Re-add some stuff from EH that I lost along the way 2020-10-19 21:05:24 -04:00
Jobobby04 1dfe3890a9 Add intercept activity source choosing 2020-10-19 18:41:41 -04:00
Jobobby04 a4a1c2827c Fix some backup duplication issues 2020-10-19 18:39:08 -04:00
Jobobby04 0b68bb20a8 Copy categories over from the original manga to the merged manga 2020-10-19 16:08:11 -04:00
Jobobby04 a8b1e8fdb0 Switch to the Mangadex extension for the low quality covers setting 2020-10-19 15:47:14 -04:00
Jobobby04 9945752667 Support the short title setting in NHentai 2020-10-19 15:46:08 -04:00
Jobobby04 05c2b43ffb Fix the release builder for AGP 4.1.0 2020-10-19 00:46:00 -04:00
Jobobby04 b2cd5648bb Update to android gradle plugin 4.1.0 to see if I fixed the action 2020-10-18 23:12:20 -04:00
Jobobby04 853f195d0e Hide biometric lock times when biometric lock is not enabled 2020-10-17 17:07:35 -04:00
Jobobby04 a481f3239c Throttle E-Hentai migrations 2020-10-16 19:33:24 -04:00
Jobobby04 510ae46fbf Lint 2020-10-15 11:27:56 -04:00
Jobobby04 c3a5439d26 Fixes for delete if in category 2020-10-15 11:27:30 -04:00
Jobobby04 a4273bb9a2 Add share/save manga cover when expanded from J2k, not tested 2020-10-15 11:26:39 -04:00
Carlos 0b6f7c5e23 Add push to mangadex
(cherry picked from commit 869f24f2092c761bc2a7bf075784bc8b7cbaea23)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSiteController.kt
#	app/src/main/res/values/strings_neko.xml
2020-10-14 18:43:27 -04:00
Jobobby04 c789203ff8 fix some mangadex manga not being marked as complete in status 2020-10-14 17:48:12 -04:00
Carlos 7f73094bb4 follows sort yolo
(cherry picked from commit 097d44ef35b021ba11e164f45234356fd8395540)

# Conflicts:
#	app/src/main/java/exh/md/handlers/FollowsHandler.kt
2020-10-14 17:43:40 -04:00
Jobobby04 a2a21bbbb7 Fix strings(again) 2020-10-14 17:36:51 -04:00
Jobobby04 c42e8739f6 Move some functions to the abstract backup manager 2020-10-14 17:36:22 -04:00
Jobobby04 d9ce86aca6 Dedupe some code, move over read and bookmarks properly 2020-10-14 15:19:46 -04:00
Jobobby04 f4200e2146 Rewrite backup categories 2020-10-14 14:15:14 -04:00
Jobobby04 e32f4eb317 Replace migrate button with merge 2020-10-14 14:06:05 -04:00
Jobobby04 e7c16cd0d1 Also apply changes to online restore 2020-10-14 13:15:54 -04:00
Jobobby04 f91d1af373 Dont break after finding one chapter 2020-10-14 13:10:24 -04:00
Jobobby04 326dc27009 Find chapter based on the url 2020-10-14 13:05:52 -04:00
Jobobby04 0a790c3c25 Fix grammer 2020-10-14 13:05:36 -04:00
Jobobby04 f6fd8a8ddb Make offline the default 2020-10-14 12:46:39 -04:00
Jobobby04 413c88d99f Fix backups always loading the full backup restore mode, even when legacy restore was used 2020-10-13 23:29:21 -04:00
Jobobby04 d15a473ed1 Enable the proguard rules 2020-10-13 21:53:23 -04:00
Jobobby04 c31beccf3c Lets try this 2020-10-13 21:33:34 -04:00
Jobobby04 f92c09b0f6 More comments 2020-10-13 20:55:18 -04:00
Jobobby04 5b056c6b70 Comment it all out 2020-10-13 20:27:36 -04:00
Jobobby04 2281a7a03d Comment out even more for a test 2020-10-13 17:23:43 -04:00
Jobobby04 713dcdfe53 Comment out some new rules for testing 2020-10-13 16:51:47 -04:00
Jobobby04 f3fc479020 Revert temp fixes, add serialization progurd rules
This reverts commit 543e089982.
2020-10-13 13:49:28 -04:00
Soitora 13196a68b1 Darken the description colors (#3858)
* Darken the description colors

* Restore UpdatesHolder.kt

* Use the same color as the Extensions subtitles

(cherry picked from commit 9f05d563f9)
2020-10-13 12:39:14 -04:00
arkon 90452a7833 Use secondary color for manga info header action button default state
(cherry picked from commit 72d114d46a)
2020-10-13 12:39:00 -04:00
Taco 8722c1806e Use some more KTX and Compat stuff (#3933)
* Use more KTX extensions

* Update Android Studio

* Use more Compat classes

(cherry picked from commit 60755d0c26)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
2020-10-13 12:38:23 -04:00
Jobobby04 543e089982 Revert the converter factory back to gson 2020-10-13 11:43:46 -04:00
Jobobby04 b67db6a25e Manually deserialize saved searches, fix NHentai 2020-10-13 11:34:29 -04:00
Jobobby04 3dd10df45e Likely fixed the full backup crashing for some users 2020-10-12 23:03:38 -04:00
Jobobby04 22f81758b0 Refactor custom manga info manager 2020-10-12 22:35:45 -04:00
Jobobby04 405b0580fc Implement exclude categories from deletion 2020-10-12 22:11:16 -04:00
Jobobby04 eea1f696ca Update dependencies 2020-10-12 21:52:20 -04:00
Jobobby04 7e93557bd2 Remove markwan dependancy 2020-10-12 21:50:06 -04:00
Jobobby04 36c0d24143 Cleanup 2020-10-12 20:20:42 -04:00
jobobby04 445878794c Make a protobuf based backup system (#112)
* Make a protobuf based backup system, restore not tested

* Fix a number

* Remove uneeded change

* Remove more uneeded changes

* Use 1.x style models, backup should be 100% compatible with the 1.x backups

* Fix restore service not running

* Fix offline chapter restore
Cleanup saved searches restore(untested)

* Implement onlione/offline option, fix merged manga restore online, fix restore total

* Allow setting auto backup to use a full backup

* Fix for saved searches restore

* Edit some comments

* Convert flows back to observables

* Fix a model

* Fixes and comment only the SY specific things

* Move SY values range to 600 from 60

* Combine legacy and full backup services into one
Deduplicate a lot of code
Simplify a lot of stuff
Modify comments

* Cleanup

* Remove unneeded protobuf config edit because its now the default

* Migrate to kotlinx.serialization for backup saved searches

* Cleanup saved searches more, move gson type adapters to the legacy package
2020-10-12 14:41:56 -04:00
Jobobby04 f3365cef67 Convert SY specific usages of Gson to Kotlin Serialization
Cleanup saved searches a bit
Cleanup json parsing
2020-10-12 14:20:54 -04:00
arkon bbfce97125 Trust user-added CAs (closes https://github.com/inorichi/tachiyomi-extensions/issues/4581)
(cherry picked from commit a689e4e041)
2020-10-11 16:07:35 -04:00
arkon 232f5b8aab Migrate to kotlinx.serialization for Bangumi
(cherry picked from commit f5aa36c787)
2020-10-11 16:07:24 -04:00
arkon 4f575f37c0 Migrate to kotlinx.serialization for Shikimori
(cherry picked from commit f8d82cb052)
2020-10-11 16:07:11 -04:00
arkon 5b4f17777c Migrate to kotlinx.serialization for download store and deleter
(cherry picked from commit 980feb6c96)
2020-10-11 15:53:50 -04:00
arkon 38c3a926bb Migrate to kotlinx.serialization for Anilist
(cherry picked from commit e7d6605490)
2020-10-11 15:53:42 -04:00
arkon b4cf0e9723 Migrate to kotlinx.serialization for extensions and update fetching
(cherry picked from commit 7a476abb53)

# Conflicts:
#	build.gradle.kts
2020-10-11 15:53:27 -04:00
arkon 4d64734dec [SKIP CI] Remove dev build logic from Travis CI config (#3873)
(cherry picked from commit 19581792fc)

# Conflicts:
#	.travis/build.sh
2020-10-11 15:37:19 -04:00
specterflare 30d3d88d03 [SKIP CI] Replace dead link in README with correct one (#3929)
(cherry picked from commit aadc6a56cd)

# Conflicts:
#	README.md
2020-10-11 15:36:13 -04:00
arkon 57803bce2f Bump minimum WebView version to 84
(cherry picked from commit b88e444cbc)
2020-10-11 15:34:53 -04:00
arkon 6d235e23ff Update dependencies
(cherry picked from commit 6c792d2821)
2020-10-11 15:34:34 -04:00
arkon 428720fb74 Update AboutLibraries
(cherry picked from commit 9afb445620)

# Conflicts:
#	build.gradle.kts
2020-10-11 15:34:15 -04:00
jobobby04 f32ccf1c6d Remove pong 2020-10-10 01:01:39 -04:00
Jobobby04 776a4b2a24 History fixes 2020-10-10 00:32:39 -04:00
Jobobby04 883af56a9a Do some work on the workflows 2020-10-09 13:19:51 -04:00
arkon a9279fbb2e Cancel previous CI workflow runs
(cherry picked from commit a249373bf5)

# Conflicts:
#	README.md
2020-10-09 13:06:13 -04:00
arkon 6dc55a3fc4 Use eskatos/gradle-command-action instead of manually handling caching
(cherry picked from commit e1eb030b18)

# Conflicts:
#	.github/workflows/build_check.yml
2020-10-09 13:04:43 -04:00
Aakash Singh f0e2367071 skip ci in commit message condition check (#3902)
* skip ci condition check

* always Validate Gradle Wrapper

* naming workflow jobs

(cherry picked from commit 6aea0f48ed)

# Conflicts:
#	.github/workflows/build_check.yml
2020-10-09 13:04:02 -04:00
arkon 8e27ffcad7 Add Amharic locale
(cherry picked from commit 842295348e)
2020-10-09 13:02:30 -04:00
Jozef Hollý 856a149032 Translations (Continuous) (#3886)
* Weblate translations

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: Allan Nordhøy <epost@anotheragency.no>
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: Félix D <felix.desmaretz@protonmail.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: Josh Ciardi <jbciardi@gmail.com>
Co-authored-by: Juan Aranda <juan50055005@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mubarek Juhar <mubareksej@gmail.com>
Co-authored-by: Nikita Epifanov <nikgreens@protonmail.com>
Co-authored-by: Nin Gun <luis_noxer@hotmail.es>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redya Rachmandanu <redyarachmandanu@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Salem Almashaikhi <salemnabil1@outlook.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Simon Mattila <simon.mattila@protonmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: abidin toumi <abidin24@disroot.org>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: ssantos <ssantos@web.de>
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/am/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
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/en_US/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
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/kn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/
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/sv/
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/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/
Translation: Tachiyomi/Strings

* Deleted translation using Weblate (Belarusian)

* Deleted translation using Weblate (Bengali (Bangladesh))

* Deleted translation using Weblate (Spanish (Mexico))

* Deleted translation using Weblate (Tigrinya)

* Deleted translation using Weblate (English (United States))

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: Allan Nordhøy <epost@anotheragency.no>
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: Félix D <felix.desmaretz@protonmail.com>
Co-authored-by: Hara Desu <aqjbgr09@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Josh Ciardi <jbciardi@gmail.com>
Co-authored-by: Juan Aranda <juan50055005@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Mubarek Juhar <mubareksej@gmail.com>
Co-authored-by: Nikita Epifanov <nikgreens@protonmail.com>
Co-authored-by: Nin Gun <luis_noxer@hotmail.es>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redya Rachmandanu <redyarachmandanu@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Salem Almashaikhi <salemnabil1@outlook.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Simon Mattila <simon.mattila@protonmail.com>
Co-authored-by: Yassin El Aoud <yassinelaoud@gmail.com>
Co-authored-by: abidin toumi <abidin24@disroot.org>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: İlle <derasetad@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 2992a0f4d8)
2020-10-09 13:02:21 -04:00
Aakash Singh c94e6c0304 Workflow improvements (#3891)
* add caching steps to build_check workflow

* add gradle.properites for github runner

* refactor ci-gradle.properties

* specify ndk versiion in build.gradle

* remove ndk install and cache steps from build_check

* moved runner files to separate folder

* refactor build_check.yml

(cherry picked from commit e8f5963a57)
2020-10-09 13:01:50 -04:00
Jobobby04 292dbf85c9 Cleanup 2020-10-09 12:59:40 -04:00
Jobobby04 96a5b456f5 Remove tooltip 2020-10-03 17:43:10 -04:00
jobobby04 05bcdadbd5 Merge pull request #125 from jobobby04/pr
Update to preview
2020-10-03 17:27:39 -04:00
arkon 95de42ad80 Prevent tag search if browsing from latest (sort of closes #3625)
(cherry picked from commit 4cbe497770)
2020-10-03 13:05:29 -04:00
Aakash Singh d26b8a9e41 Update issue_closer.yml (#3885)
Limit the issue_closer workflow to run only when the issue is opened, edited or reopened

(cherry picked from commit 76a53097b1)
2020-10-03 13:02:51 -04:00
arkon 8404b1c0c2 Set activated states for manga info favorite/tracker buttons
(cherry picked from commit 0904692f15)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/res/layout/manga_info_header.xml
2020-10-03 13:01:25 -04:00
arkon eef8b776f6 Reorder chapter filters to better match library filters
(cherry picked from commit 65bacd288b)
2020-10-03 12:58:26 -04:00
Andreas E 527e26137f Remove from queue after read (#3870)
* Add option to remove chapter from download queue after marked as read

* Begone bug

There was a bug when one queued a new download to the downloader. Which resulted in getChapterDownloadOrNull() would not find the corresponding download for the provided chapter

* Fix review comment

* Remove preference and just remove from queue

(cherry picked from commit 11ab3b2c2e)
2020-10-03 12:57:42 -04:00
Andreas E f6d3c38d03 Ability to delete reading history (#3871)
* Added ability to delete history for all manga

* Fix review comments

(cherry picked from commit 812368e332)
2020-10-03 12:57:29 -04:00
arkon 2772e4960e Add banner for Incognito Mode
(cherry picked from commit cf39ae0000)
2020-10-03 12:57:20 -04:00
arkon 085722e077 Show downloaded only banner in all views
(cherry picked from commit 7194f65203)
2020-10-03 12:57:10 -04:00
arkon 6a3a5c58d4 Minor string cleanup for Incognito Mode
(cherry picked from commit 4b78ff324d)
2020-10-03 12:57:01 -04:00
Platiplus e1d7713f14 [Feature Request] Option to turn off History - #3772 (#3855)
* Implementing do not save chapter history preference

* Refactor of labels

* Changing incognito mode from Readers section to More

* Fixing imports and removing unused strings

(cherry picked from commit 79ccfcd553)
2020-10-03 12:56:53 -04:00
arkon 925e9d9516 Update AndroidX dependencies
(cherry picked from commit 2df6a4dde8)
2020-10-03 12:54:58 -04:00
Jobobby04 a0cf7730f5 Undo some weird thing SY had 2020-10-02 22:01:25 -04:00
Unlocked e1fc94e6d3 Fix visual issue with bottom padding in filters sheet (#3881)
(cherry picked from commit 25d1c40cda)
2020-10-02 12:14:49 -04:00
arkon dc7a492d75 Update subsampling-scale-image-view
(cherry picked from commit 969b57ade9)
2020-10-02 12:14:18 -04:00
Jobobby04 63aab6f11e Fix deleting history not refreshing the view 2020-10-02 12:06:49 -04:00
Jobobby04 32748fa056 Allow migrating extra info like viewer, chapter filters, and other stuff 2020-10-01 22:12:37 -04:00
Jobobby04 1a55f4845c Fix manual search using browse's menu 2020-10-01 21:43:43 -04:00
Jobobby04 b4194de9e0 Fix SY filters not properly setting 2020-10-01 10:07:44 -04:00
Jobobby04 4f864152ea Cleanup some code 2020-09-30 19:48:26 -04:00
Jobobby04 c501c9ecc3 Modify the looks of the extra info for a few sources 2020-09-30 19:29:44 -04:00
Jobobby04 4bd88fa194 Add source categories removing the source in the language as a option 2020-09-30 18:27:09 -04:00
Jobobby04 604b4ec01e Fix expanding tags on E-Hentai and similar manga 2020-09-30 17:52:24 -04:00
Jobobby04 789f1392ac Fix clicking the updates button multiple times opening multiple download menus 2020-09-29 19:16:33 -04:00
Jobobby04 1b9c66896a Add a double click action on the updates bottom bar button to open downloads 2020-09-29 15:52:39 -04:00
Andreas E c6db5a01dd Fix color filter sheet content alignment (#3862)
Fix color filter sheet content alignment

(cherry picked from commit bddeb86223)
2020-09-29 12:41:56 -04:00
arkon 3a07ee3deb Fix chapter filter icon tinting
(cherry picked from commit b5986b509e)
2020-09-29 12:41:28 -04:00
jobobby04 9c76f1fd8f Match infinite history and search history from preview (#3827)
* Add infinite history and search history

* Cleanup code

(cherry picked from commit 9d2adcd512)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryPresenter.kt
2020-09-29 12:41:10 -04:00
arkon 8acd834783 Use tristate checkboxes for chapters list filters
(cherry picked from commit fb3756420b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2020-09-29 12:35:40 -04:00
arkon 7494033b90 Fix scrolled content being visible behind navigation view group
(cherry picked from commit 2eab43a669)
2020-09-29 12:26:30 -04:00
arkon 703263246e Clean up bottom nav bar scrolling behavior toggling
(cherry picked from commit caeab0a63b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2020-09-29 12:26:21 -04:00
arkon 59020deb6f Tweak navigation view group header style
(cherry picked from commit 7c69b1b649)
2020-09-29 12:25:33 -04:00
scb261 2130a2a67e Add option to hide bottom bar when scrolling (#3758)
* [wip] hardcoded flag for hide on scroll

* add corresponding option in settings

* clean up a little

Co-authored-by: arkon <arkon@users.noreply.github.com>
(cherry picked from commit 3784d1a8f2)
2020-09-29 12:25:25 -04:00
scb261 a8946e4f98 Add ability to choose what to remove for selected mangas (#3796)
* [wip] started to change manga delete options

* Change dialog for deleting selected manga

* Move array to appropriate file

* Make first option enabled by default

* Use radio buttons to reduce amount of clicks

* Revert "Use radio buttons to reduce amount of clicks"

(cherry picked from commit 458e761b45)
2020-09-29 12:25:16 -04:00
arkon c59ca51944 Color selected bottom nav item with accent color when theme isn't blue
(cherry picked from commit 371b0b2132)
2020-09-29 12:25:05 -04:00
arkon ad03dfee97 Use same padding as source icons for extensions
(cherry picked from commit 5d1ca64768)
2020-09-29 12:24:55 -04:00
arkon 3b5a869fd0 Color selected tab with accent color when theme isn't blue
(cherry picked from commit 972a595c74)
2020-09-29 12:24:47 -04:00
arkon 815ac9d55b Remove card backgrounds from browse lists
(cherry picked from commit a3c598a3e1)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceHolder.kt
2020-09-29 12:24:33 -04:00
arkon 7b76823b60 Use background color for list view
(cherry picked from commit 9ce8c5c160)
2020-09-29 12:17:18 -04:00
arkon ab7bd3ebc2 Shorten settings search package name
(cherry picked from commit 79bbc99882)
2020-09-29 12:17:10 -04:00
Carlos 4faaf577be Update Ben Manes versions lib (#3843)
Update Ben Manes versions lib

(cherry picked from commit 31867362dc)
2020-09-29 12:16:55 -04:00
Jobobby04 4805d3c0c0 Manage Exhentai better, if login is disabled the source will disappear 2020-09-29 12:15:41 -04:00
Jobobby04 3c8fe2ed0e Make mangadex icon in settings smol 2020-09-27 17:23:42 -04:00
Jobobby04 d7433a0cd8 Change reader preload amount to Page Preload amount 2020-09-25 14:36:21 -04:00
Jobobby04 a8e80c663e String tweaks 2020-09-25 10:27:58 -04:00
Jobobby04 1b90590260 Cleanup Autoscroll coroutine code 2020-09-25 10:26:42 -04:00
Jobobby04 a903a48718 Add source setting controllers to search settings, hide settings in the E-Hentai settings when not logged in 2020-09-25 10:26:42 -04:00
Carlos 1038913e2c Update strings_sy.xml (#120)
remove neko strings
2020-09-25 10:25:58 -04:00
Jobobby04 748e5d5b0f History tweak 2020-09-24 09:19:36 -04:00
arkon ea3f90f107 Minor settings search code cleanup
(cherry picked from commit 3bce07e873)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsMainController.kt
2020-09-24 09:13:48 -04:00
arkon 26557b3257 #3520 Searchable Settings (#3683)
* Adding class stubs for settings search, UI elements.

* -  implement searchable settings
- `SettingsController.setupPreferenceScreen` must return a PreferenceScreen

* Remove unneeded SettingsControllerFactory.

* Set query hint, clean up code smell.

* Add search button to MoreController, stop infinite recursion.

* - initialize SearchResultCollection once in Activity.onCreate

* - implement prefernce highlighting after settings search

* - Ensure all Preferences have a key set or else the highlighting effect will have no effect on it.
- remove ExtensionFilterController and SourceFilterController from settingControllersList in SettingsSearchHelper, since those are related to Extensions and not Settings

* Limiting search to settings menu only, localized breadcrumb string, and code cleanup after code review.

* - moved call to SettingsSearchHelper.initPreferenceSearchResultCollection() into SettingsSearchController

* Code review cleanup and refactoring.

* Inlined non-reused key strings.

* Adding more UI polish, add comments for future enhancements.

* - retain search query when navigating *away* from SettingsSearchController
- keep `searchItem` in `expandActionView` state until user goes back (fixes the empty view in `SettingsSearchSearchController` issue)

Co-authored-by: mpm11011 <markuscicero5@gmail.com>
Co-authored-by: lmj0011 <9396189+lmj0011@users.noreply.github.com>
(cherry picked from commit 766f9e37b5)

# Conflicts:
#	app/build.gradle
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceFilterController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
2020-09-24 09:09:45 -04:00
arkon 89eea4ab91 Move manga info summary more/less button toggle
(cherry picked from commit a9bed90d02)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/res/layout/manga_info_header.xml
2020-09-24 08:41:48 -04:00
arkon 616fbe4afe Center reader transition text properly (fixes #3819)
(cherry picked from commit 01ad405dd2)
2020-09-24 08:24:16 -04:00
arkon c57abf67eb Move Gradle wrapper validation into build action
(cherry picked from commit 274f0edd76)

# Conflicts:
#	.github/workflows/build_check.yml
2020-09-24 08:23:58 -04:00
arkon 4aff2d79b7 Remove support for backup v1
(cherry picked from commit 88aea311f8)
2020-09-24 08:22:09 -04:00
arkon a87f6811e5 Minor updates
(cherry picked from commit 477aedbffa)
2020-09-24 08:22:00 -04:00
arkon 10f15d54f3 Add sah locale (closes #3818)
(cherry picked from commit b898442fe3)
2020-09-24 08:21:48 -04:00
Jobobby04 91c05e76bc Fix mangadex manga getting set to completed when not actually finished 2020-09-22 14:22:21 -04:00
Jobobby04 caa6dd6d62 Fix issues with mangadex tracking, infinite loading and showing up on non-mangadex manga 2020-09-22 13:45:50 -04:00
Jobobby04 ffc80c084d Parse the error Mangadex sends so people know what they are doing wrong 2020-09-17 12:10:12 -04:00
Jobobby04 9ee712aea4 Fix some mangadex login imports 2020-09-17 10:41:41 -04:00
Jobobby04 11fbc1faf9 Use XLog in more places 2020-09-17 09:05:19 -04:00
Jobobby04 bd6e048108 Fix search in latest + browse view 2020-09-17 00:43:38 -04:00
Jobobby04 a3be1c2c39 Fix merged manga getting backed up 2020-09-16 14:19:16 -04:00
Jobobby04 cbbb3bbf72 Trim the created tag to account for user errors 2020-09-16 13:00:39 -04:00
Jobobby04 67c99c9c42 More fixing to the sort by tags logic 2020-09-16 12:58:25 -04:00
Jobobby04 905e1b68c6 Sort by tag settings is able to sort the tags and save the changes 2020-09-16 12:25:08 -04:00
Jobobby04 f5998505a3 Cleanup 2020-09-15 23:34:14 -04:00
Jobobby04 67e1fee4c5 Fix everything about the sort by tag list 2020-09-15 23:16:49 -04:00
Jobobby04 db70a62c8f Fix sort tag preference 2020-09-15 18:21:26 -04:00
arkon a4d3fa5878 Fix downloaded only filter
(cherry picked from commit 7641bb4d0d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
2020-09-15 18:18:33 -04:00
Jobobby04 a8aef554bd Migrate button tweak 2020-09-15 18:16:29 -04:00
Jay 2583be9923 Send manga/chapter/page details when sharing a chapter page
(cherry picked from commit 004e1c98ee)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2020-09-15 18:16:06 -04:00
arkon 573768482f Tri-state library filters (closes #1814)
Based on https://github.com/inorichi/tachiyomi/pull/2127.

Co-authored-by: hXtreme <hXtreme@users.noreply.github.com>
(cherry picked from commit 687f3d48ea)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt
2020-09-15 16:29:44 -04:00
arkon 8df978a91c Clean up global chapter settings code
(cherry picked from commit da5f10a2f1)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
2020-09-15 14:52:31 -04:00
lmj0011 763335bd85 [feature] add ability to set global filter/sort/display for Manga chapters (#3622)
* - [feature] add ability to set global filter/sort/display for Manga chapters

* - move default chapter settings functionality to overflow menu
- code clean up

* - show confirmation dialog when user selects "Set as Default" option in Chapter Settings

* - hide overflow menu in LibrarySettingsSheet

* - apply default chapter settings if manga is added to Library from a Source's browsing screen

Co-authored-by: arkon <arkon@users.noreply.github.com>
(cherry picked from commit 64050e8266)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2020-09-15 14:27:42 -04:00
arkon 4e8c30b7fe Start updating manga info view header design (SY edition)
(cherry picked from commit 791a7d5a01)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/res/layout/manga_info_header.xml
2020-09-15 14:08:14 -04:00
Jobobby04 07973bff32 Revert "Disable tag sorting for release"
This reverts commit 70bef08ed6.
2020-09-15 13:29:03 -04:00
Jobobby04 c69972f83a Revert "Hide dedupe by priority for release"
This reverts commit 92ae67630c.
2020-09-15 13:28:58 -04:00
Jobobby04 d05ea94d78 Revert "Hide parental controls section for release"
This reverts commit 02b90000f0.
2020-09-15 13:28:50 -04:00
Jobobby04 332a631b6c Release 1.3.1
Validate Gradle Wrapper / Validation (push) Successful in 13s
2020-09-15 13:21:37 -04:00
Jobobby04 70bef08ed6 Disable tag sorting for release 2020-09-15 13:03:15 -04:00
Jobobby04 de05f88d5f Fix Mangadex 2 factor auth
Fix Backups with merge manga breaking(I think)
Tweak preload settings, make the max 20, defaults to 10
Add tag based sorting
2020-09-15 13:00:40 -04:00
Jay 89427ff37e Send manga/chapter/page details when sharing a chapter page
(cherry picked from commit 32aea55f424d77e78be50f288996fe04053db009)
2020-09-14 21:15:40 -04:00
599 changed files with 15720 additions and 9009 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.3.0)
- I have updated to the latest version of the app (stable is v1.4.0)
- 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
+1 -1
View File
@@ -9,7 +9,7 @@ labels: "bug"
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.3.0)
- I have updated to the latest version of the app (stable is v1.4.0)
- 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
+1 -1
View File
@@ -9,7 +9,7 @@ labels: "feature"
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.3.0)
- I have updated to the latest version of the app (stable is v1.4.0)
- 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 482 KiB

After

Width:  |  Height:  |  Size: 1.7 MiB

+28 -18
View File
@@ -1,24 +1,34 @@
<?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">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" 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;}
.st0{fill:#232B30;}
.st1{fill:#2E84BF;}
.st2{fill:#CE2828;}
</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 id="Calque_2">
<path class="st0" d="M304.4,891.3h391.2c107,0,194.5-87.5,194.5-194.5V305.6c0-107-87.5-194.5-194.5-194.5H304.4
c-107,0-194.5,87.5-194.5,194.5v391.2C109.9,803.8,197.5,891.3,304.4,891.3z"/>
</g>
<g id="Calque_1">
<path class="st1" d="M474.4,242.2c115.3-1.4,144.2,48.2,185.2,74.7c21.6,8.3,17.7,16.3,26.2,28.7c2.5,3.5,5.5,5,9.5,7.5
c30.3,18.2-10.8,39.6-28.2,40.3c7.1-4.6,13.2-5.9,17.7-12.4c-0.3,0-0.6,0-0.9,0c0-0.3,0-0.6,0-0.9c-14.6-4-13.3,8.8-29.2,6.2
c3.5-0.4,12.6-5.8,12.4-13.3c-11.9-7.2-31.6-15.2-46.9-14.2c-0.1,0.4-0.1,0.4-0.2,0.8c36.3,24.2,25.7,57.3,47.1,63.8
c-11.6-0.5-5.9,2.1,0,7.1c-14.6-3-16.1-19.2-23-33.6c-7.8-12.9-15.4-28.5-23.9-29.2c-2,9.2,2,56.4,14.7,46.6
c-10,12.3,4.4,7.2,11,4.7c-23.9,19-35.1,13.8-42.8-13.8c-6.2-13.4-14.6-34.2-19.5-46.6c-2.8-7.1-7.5-13.3-13.6-17.9
c-41.8-31.2-72.8-33.3-116.9-26.1c-11.1-21.4-27.5-40.4-38.9-61.9C435,247.7,452,242.4,474.4,242.2z"/>
<path class="st1" d="M313.4,336C395.3,494.3,482,442.7,607.1,502.8c109.3,67.9,83.8,212.8-30.1,257.7c0-28.3,0-56.6,0-84.9
c46.4-45.1,20.7-112.1-37.1-125c-68.8-15-145.6-29.5-191.7-62.5C298,450.3,286.6,386.8,313.4,336z"/>
<path class="st1" d="M424.8,690.6c0.1,25.4,1.9,53.9-0.9,77.8c-94.2-25.7-162.5-103.3-124.7-192.8c0,4.7,0,9.4,0,14.2
C307.8,650,359.3,682.6,424.8,690.6z"/>
</g>
<g id="Calque_3">
<path class="st2" d="M247,198l138.9,37.1L500,414.7l47.8-72.5c34.5,14.5,37,85.5,55.7,96.4l-23,34.5
C477,444.9,387.8,439.9,351.3,364.7C317.9,311.3,247.1,198.1,247,198z"/>
<path class="st2" d="M602.6,255.5c16.2,9.6,33.1,19.8,46.5,32.8c7.5,7.2,14.9,12.3,25.3,18.3c3.9,2.2,4.7,2.1,7.8,4.7L753,198
l-137.1,37.1L602.6,255.5z"/>
<path class="st2" d="M500.9,841l61.9-50.4l0.9-212.3c0,0-4.5-11.4-85.2-25.5c-17.1-3-29.7-7.2-29.8-7.2l-12.3-3.6l1.8,248.5
L500.9,841z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@@ -0,0 +1,6 @@
org.gradle.daemon=false
org.gradle.jvmargs=-Xmx5120m
org.gradle.workers.max=2
kotlin.incremental=false
kotlin.compiler.execution.strategy=in-process
@@ -4,38 +4,40 @@ on:
push:
branches:
- 'master'
repository_dispatch:
jobs:
ping-pong:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: '0'
- name: Set CISKIP flag to false
run: echo ::set-env name=CISKIP::'false'
- name: Set CISKIP flag if action has ci skip
if: contains(github.event.action, 'skip-ci') || contains(github.event.action, 'skip-ci') || contains(github.event.action, 'skip ci') || contains(github.event.action, 'ci skip') || contains(github.event.action, 'ci-skip')
run: echo ::set-env name=CISKIP::'true'
- name: Exho
run: echo env.CISKIP
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
preview:
name: Build app preview
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v2
- name: TAG - Bump version and push tag
uses: anothrNick/github-tag-action@1.17.2
if: github.event.action != 'pong' && env.CISKIP == 'false'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WITH_V: true
RELEASE_BRANCHES: master
DEFAULT_BUMP: patch
- name: PING - Dispatch initiating repository event
if: github.event.action != 'pong' && env.CISKIP == 'false'
run: |
curl -X POST https://api.github.com/repos/jobobby04/TachiyomiSYPreview/dispatches \
-H 'Accept: application/vnd.github.everest-preview+json' \
-u ${{ secrets.ACCESS_TOKEN }} \
--data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
- name: ACK - Acknowledge pong from remote repository
if: github.event.action == 'pong'
run: |
echo "PONG received from '${{ github.event.client_payload.repository }}'"
@@ -6,22 +6,42 @@ on:
- 'release'
jobs:
apk:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
build:
name: Build app release
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.5.0
with:
access_token: ${{ github.token }}
- name: Clone repo
uses: actions/checkout@v2
- 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: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Write google-services.json
uses: DamianReeves/write-file-action@v1.0
with:
@@ -31,8 +51,15 @@ jobs:
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: Build app
uses: eskatos/gradle-command-action@v1
with:
arguments: assembleRelease --stacktrace
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Sign Android Release
uses: r0adkll/sign-android-release@v1
with:
@@ -46,6 +73,7 @@ jobs:
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
@@ -56,6 +84,7 @@ jobs:
release_name: TachiyomiSY
draft: true
prerelease: false
- name: Upload Release APK
uses: actions/upload-release-asset@v1
env:
+52
View File
@@ -0,0 +1,52 @@
name: CI
on: [pull_request]
jobs:
check_wrapper:
name: Validate Gradle Wrapper
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
build:
name: Build app
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.5.0
with:
access_token: ${{ github.token }}
- name: Clone repo
uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app
uses: eskatos/gradle-command-action@v1
with:
arguments: assembleStandardDebug
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: TachiyomiSY-${{ github.sha }}.apk
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
@@ -1,11 +0,0 @@
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
+3 -1
View File
@@ -1,5 +1,7 @@
name: Issue closer
on: [issues]
on:
issues:
types: [opened, edited, reopened]
jobs:
autoclose:
-23
View File
@@ -1,23 +0,0 @@
name: Pull request build check
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Install NDK
run: sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;21.0.6113669"
- name: Build project
run: ./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v2
with:
name: TachiyomiSY-${{ github.sha }}.apk
path: app/build/outputs/apk/dev/debug/app-dev-debug.apk
-81
View File
@@ -1,81 +0,0 @@
dist: trusty
language: android
android:
components:
- tools
- platform-tools
- build-tools-29.0.3
- android-29
- extra-android-m2repository
- extra-google-m2repository
- extra-android-support
- extra-google-google_play_services
licenses:
- 'android-sdk-license-.+'
- 'android-sdk-preview-license-.+'
before_install:
- yes | sdkmanager "platforms;android-29" # workaround for accepting the license
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then
openssl aes-256-cbc -K $encrypted_e56be693d4fd_key -iv $encrypted_e56be693d4fd_iv -in "$PWD/.travis/secrets.tar.enc" -out secrets.tar -d;
tar xf secrets.tar;
mv debug.keystore "$HOME/.android";
fi
- mkdir "$ANDROID_HOME/licenses" || true
- echo -e "\n24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
- echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license"
install:
- echo y | sdkmanager "ndk-bundle"
before_script:
- export ANDROID_NDK_HOME=$ANDROID_HOME/ndk-bundle
script: ".travis/build.sh"
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- "$HOME/.gradle/caches/"
- "$HOME/.gradle/wrapper/"
- "$HOME/.android/build-cache"
deploy:
- provider: releases
api_key:
secure: qmS9SyMq8xPDqaY83rvAFyZcvic24lGBj3MFt22RhVJzIXAAN/vqL1R70PnNiCF7CE+R7PaDlBpwjxDMBiuh0QQNc1oX6cgepUbro4/Nt7NFFfCvKXaFdR1cSgYouhuHmy0SS0/alrcfhQ2bPwcm1/vAOiSa8Wu7hsXhCcxbFyEbXZVD11QZmiffEM0py+OeuqOFo2JxZaGRu2z04E/u5TWep1ZEuhFRCC87PGgFqABgg6jYYebQOUZADG/0G8581HTGU0mdwueYsiA35ncRzpV2V8DajEEBd5wOe5d8SyMtE+6Qs5PD9KcXAqGGe4QRmrJMX5EcLQaLZf/Qd5s9SFZVHb1aJIw/y05w4L5dlVpsjx5WuUAYAVg7Ol5UawofFo/hYkYCNmfub67wJQdHSIxPif7V6YeON6RQQMpc5GBYY9eA6ZxhrdA2m7eyoOT3jcbdaVJwC0jMGhn26hkgJfTo1LfAUs85Cs3BrK8w6Poqc/Jb+4Y0NhdGIKgO5tS3vY54cTJVVrQTq4/XmME4ZnzOX3HaOqzfyt/6M4gEQMvaeFksxwoFhocV7wfaCq9ps/Kdq2dl4KwoqRV2WqVaauqzCP4XPSlVLaJqztsw0wboupcaZepWJ2a/6j9IrKo1pEnyeHF5y+k0SUAxL0X8iKZ0uPxsgoVrlNtqXJWNGvA=
file: tachiyomi-v*.apk
file_glob: true
skip_cleanup: true
on:
tags: true
repo: inorichi/tachiyomi
- provider: script
script: ".travis/deploy.sh"
skip_cleanup: true
on:
branch: master
condition: "-z $TRAVIS_TAG"
repo: inorichi/tachiyomi
- provider: script
script: ".travis/deploy.sh"
skip_cleanup: true
on:
branch: dev
condition: "-z $TRAVIS_TAG"
repo: inorichi/tachiyomi
env:
global:
- secure: Ita1+tzo7P5IC2yqU3KgRcXt+5DTpP0103Hx/ECYi42/7rLt+TC7PMjl2yH3Z189+tGwLq0Ol1KJ2Z5Rn3q7EaQgD0+WRkH/ijtrjKoVh7dyItIBp7yowZpA0TJHQ4EZpGSxZakKbIP4di8XMxJ2+5VzEivYUt04LCUpzugemL6b6XOfUmOZykVxV2UDAlPPggklITYBXkHUa0mwJhjS1aPPeeR3PhVXomkqfuOJOKejPXXXJope9fhAnmopHA7ISfjIrTuwDVQJqNSuco+O9kQShmlu0C8pob1vFGPEDvafaDS8SZ9A6gKT1ZfgSUqVmvDbx0WLX8XugBLrQedtZv63esOa1WUyGhgFVpeJjexlszXlhyfP1gH5QbzRr+EiSaagCyjf9II2veLAtU5cFY+nj6KBdKQsazIMRHf8SAQlWASyJYMED/N9RnUFxSf1rnLGqiY2ezjycx4ieFj7vhlbTgyao1GHjjR9cwNuntdMYWhY8+Vc7Fctmzm46xOyyz9oJGdyim76Y4w4MZvQNKeZOBAjdEgX6cXBk15scoM2Vj9ENox+MKZLaKRawXg2U1ujK+bWAQkXiVvPriv05/JtYsNUft8qAsm+69vtohDsUW7Wu0bBIKDL+v0W30ty1PpyNehBB2OquUE7fp53gitOmYl7TyuxktkMY8PXKKU=
- secure: NABCfigMUVM/9TLALYBpQLp/p3rG6MbH5y34/oqCSej/oh2u0nyhFSi8veS0lFpDIcv0TZvxHJXoSA0zeZijb1fUU8jYVNT2azuPWE6Gu7sf0TfBeCvulqbgLMoaA6JuWbEbZwHcxpKHg4vLSMjNk+ZP4v2dffI6A620fxLltxxhTpsYkYYsfKG857CpQtdgN/HqcOaxyvzXFmWWyVWHala1uMcMeXZCwgnlVAqau9o0bsU092txSmHqoesHoAinidSCTCmTlEqp/1AFaYQTbxmnfNC1yLgzxWAlJcS3NWzNo3ellMvKjsiIGn3JJpAjTGcyf3yPsvhs1cY3MZbmJYVyKH5HbnkA5ms6mx0DDJ2UOs5H2dmED82m14+hu62Xb8XN8zAdq+bySNSwgsGzvr1PT74pT4BW1T+D7L1xvUe6k1enZ38GIMJbJPhBybRQazhjKPmXRB30Thxoqe5VqU8UeiXHAEps7JYAWUR1PLZvEYQb6MWurmTxs9be/OTwrUT1LDiqeJZg6XkDGgQwuR2YBaQJHJD17Piq6q1BUX8abhK6wzAAYVqyGvpmUCmQCtHZgenE6ulwcKChzBv4n97OjE21LGWnbNF5ViUhfAbGcKOVufd1chZsfbkJ7a3tHYCfLnxHUIhKvHk26f5Em8h68D0wQkPnkcVVkfh7XpI=
- secure: C93UADV5aR0zhLCLwR6tCyz+fwUYslZbhjBl3PHQp+0boIGS/Be2UQTFHp/NB9mQmhWqbwqHoAVFENZFytV04ePgOuNtMFcjAIfnzm19Am7iRAMFixD45pF/CuYYfLupElkAcSequtAzN0g4G0sQ5KR1hibaDIoz9kfA2YcUAMaZ4T5bhCr8os/xA2nOlmvPDWsRWCFBYkSpnmbsSsgYAhulA/V5bSNAWnp9LPw3CBLibW3WsVP4wuhZAkXznKwn/mHT31kfQlpMH3qNhXpsN9huUkZ/k8QWeakcHJKugung0Z2T1StK8rlI8OrJstVcwueHTa2ses4f5VbhWog/Z8HDkdll9W9RM/QqXjNDtOVBt/SPuhCp4k2rvJixFUxzvSqgSWQvQnbHwjWxIUVVyHtnb0/zc/S9ONZG14TOwB/+Lkgacb85PNszurZ2f3mH0O6slIh1mH+5d9J4+L976ot4nTPlK1OtothagVyKGOrn9HycrPk/MjftIJuElHzo7NEJd/wRPqIb5y12iZN1RSPriU+itg1uSAVP891/o3peJyuqY9WSB7dYwgDJos6dDvbr19emtdyxkQR+eAb5duyH6s4R58wh1kJ1d4zu0X6uSnF4AIc+6teKkN24rSXcqB/hrcojS49jgLy5P0/CVsUbYZPI/tx8E/IJfr8m36E=
- secure: mawwBvllvESc/mp+JHvncq1iUhiC7nyokPgXmOehffc0K3byMLs2L25HjNsU6EnXG9Lcae1cfP8S9bWLquU2C3kpAkLBUpjEbdx7K0654uvs7Rrvb5hcTRHwjzaEVmVaBFX4ROcjUhBYny/Wjj/YENCkSWpkfcMd1esFbVsO+fOLyaAPvrb6auKY7H+pUSqlEwaEnrkYeBBZIHa7KqwL4g5DHbq6K368tjmval/wBzwMB0V8V3dt/ik8RMVDtKPrik4Bu0V9UmXZUIo/a06ii/CM82ekFRh3eUb0DKkdkmYbdH6MBMoLTfQtMa6A4luXaA0oycAnTX3OGB5MWIjK39KhWRavh6ybSIt4aHKoolxzH8Zgmk7xMhFSot/laX5q5IzjZu5KU6F2SmdV0kcQugM8oAjANFySetPvY1q7nZ8pM+NO1xKS/mH0w4vChhdJFD1mw7aCoh8FdeUf0Eym2+pp5Q9uAisWMmNn5XN8/fL5q6PzAxkXmkedfrr1N61FmIL6EKx8qiWpOUNlRRTIMJ4GMhCyckCF6cNxDkBItp52c+Hmkbn+ZEInEyX6gpjYVm3xyEi0Z5kLCi/fMX2nBNczc5BuGLzzmJnITv4ovpeYn2/vPvHbaPgPC4LqDK3AjlpVadMZk/M5Egn+hWY7Mni57CmpZD+SpxUbbsItI0c=
- secure: PJPDkUg1zc57brxUvNpSh+Q3ZEaGpBqZzwDavqslkn0WmjBTLrE6/OG7TFHKNmO+P56qFl+pMEKqThxqR3+4bWEeEx8ykkixDVzxNJMmws+7A7ImJ75iQyB6giMW/4tykVMMHgIPNAdcnI8VOWn0LGHnpFWUd70yoyAGX8s6cspHCKgcuWMA3GS410KJfHpyd0B9/QS7ZyWzSETW7zSPyLPa81SBO95EhOF3TOGZYLt/mBhdtU3YGFs4k9fZ8jDDcm9XmBfqVlUhb8HiZcxJiZDdRvxODERfNnwc47uaJk6+kxGDzIW2uAxrMXXVKkG04GeMOokXoR9kW1Hl2JmoyySLKLZmB7I/XEtVWdzZw16mWi+4zmhjLhfB0phSW+/5I+0VtZZ6jO031J5FL/JqVrcq1ws/aw4QlaOdPUco/x2u4LNHyYYgOi5arD9xSyu6IRy0jCC4Xa1zuqM5adGJX+rZyVfKZ0TxOW661HTxlo8COtkB2i0WR2deZGVN75ooCAEO8DauQoUcFH1OelahmPtzVs1/6ZczuxGdp9ED7ZQq9NHEOsOdUGCj/D79Dm1hWFQsIsslnnGYWitAycNCgEwmlt2Q6fbrv2CJrmLqZ9a9r3AhzxoHn9Qx1GyuyfhZJzm/6Ff2kcOjma2kcz13KUwTxdW+2G5dDCotK3f7aiI=
- secure: FIIZfEEYfjNMKODs33Czh603CYVn6LRrzpFNIiPHYTb8iQWv9qAYhsg4FpHfOjDikokTwb5X/h8G7AX93Z0xKyyDi75ACT11oPeTNTArDdcmdDVlOYBvYHc2Ci7pMW5r8LGejB7Y3mWM8uKyA3oKvneEFutB65vO3JVZvFWrm03Lmqqe7+mA4qNqNqTbN7R7fmk5b7zt7A3DHvDu0JPTbSSUwpso/p2I5WJYjrf71I7YMQwIFLoMfplC1onVA3EFS3lZsF65zE+xVRy34AKa41iZAMbhVDyqUHEnx6L0dwEdn2Z5XLlK0ov1+qLTLlQsBE4Knre6TNkWMfktk7MKA+ch8RYxvEYLODhQkIrOkLSNWhZPhdaT+xD4fr0RCKSHo6uWRC4aofsJx8wSqb8ZL4j2zopUp9VisMOI202UEnvFDBtOkVGJSxxYbFjifIB7NCJBn788w+3k+k4IbOg537VdyoK2PMBR8/TDdjImWhWHY1i7+345ejwmzHL7ZPfb6GTNnQTWkajT77/n6Yk41twR5vvegOSTKuuO++WN/pUks4PGqtcQe9fnSfx2OcOq1ofLiG+JDorJ7z8kHSG13wHLq+QYMDayQbyJEYpDzmn/w3Ou1s2o0a7A41+cIkRzAgH9y3v4lgjp9GcMP2S74ZPA7OecWbFSexM7tL/dYxY=
- secure: DKCGc4E9PKeTX68r9pbbNg5qITsN0bApQ1m0x8xdEoi8GLRKVMYNn6ahoAxvy1YsBXC9Zlt5++gLmUV1I1JyDMyJXMr/lZrp4oarW0xWpTBmn3HzOph/K2W4i/fTGgMFieumPEbQIFOnU3JSjK6UJB8qVGEXD2OqS7A//EdrGDbAYVDL3ZTKE6JUlTNHgaKaNHhn+Dq4aBLTSYPwlLyqo+WNBVUUCKCHOq62ULF8MpX5YGaPFNxKYzircV7HpF1hCbV31dmpkeYT9xztra5V0SIBM27jAcQqGmtHH2mhx1sLu+gjhFJbbtY6cggA9EedzYYLDx/NPmgfyuOJfyVbSwTF3vhDUYfskqc1THWpwOSKO0Ry+8/xYb9crxg+FSwuI5hnfkIFk9woBvRGBhjto3/1buMNY9dSFiWtEbN6Let8e747l0wIGJCpJxSeh7vn7F1mWjixhf9GX1+V9BrUvGTd3XJDNb9cVnafYa1RTj8BLteA4HBza7Z9R3dvG4YWp16L/94UuaTzgAQfERLTZGopQth/hsaVTlYesJmJLF70lGM+W83y3YuNkSaX1zQ5FAIvp7oH0O16t7ISm6GprUFwN2Uox7AAbPZdWHxJbly+D+yCFNcqS3Bz9mV3YCLo690Sy1ePNHr+nCseVfBMo7OYyavSS/EjPWfEy65Wq04=
+22 -28
View File
@@ -4,7 +4,7 @@
# ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY
Tachiyomi is a free and open source manga reader for Android 5.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. TachiyomiAZ has decided to keep the old UI of the app, this version is meant to push forward in the ways of UI and usability, doing that it takes the new Tachiyomi(original) UI. TachiyomiSY also includes a lot of the up in coming features of the Tachiyomi Preview, as well as features from other forks like J2K, it has quite a few custom features from myself and contributors as well.
Tachiyomi is a free and open source manga reader for Android 5.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
![screenshots of app](./.github/readme-images/screens.png)
@@ -21,55 +21,49 @@ Features of Tachiyomi(original) include:
* Create backups locally to read offline or to your desired cloud service
Features of TachiyomiSY include:
* Uses the new Tachiyomi Stable UI
* 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
* Automatic webtoon detection, allowing the reader to switch to webtoon mode automatically when viewing one
* Recommendations, from AZ but heavily modified by She11Shocked to use both MAL and Anilist interchangeably
* Manga recommendations, uses MAL and Anilist, as well as Neko Similar Manga for Mangadex manga(Thanks to Az, She11Shocked, Carlos, and Goldbattle)
* Lewd filter, hide the lewd manga in your library when you want to
* Tracking filter, filter your tracked manga so you can see them or see non-tracked manga, made by She11Shocked
* Search tracking status in library, made by She11Shocked
* Backup saved searches
* New E-Hentai/ExHentai features, such as language settings and watched list settings
* Comfortable grid view
* Custom categories for sources, liked the pinned sources, but you can make your own versions and put any sources in them
* Manga info edit
* Enhanced views for internal and integrated sources
* Enhanced usability for internal and delegated sources
* Manga Cover view + share and save
* 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
* Smart background for reading modes like LTR or Vertical, changes the background based on the page color
* Force disable webtoon zoom
* Continue reading button in library
* Hentai features enable/disable, in advanced settings
* Quick clean titles
Inherited from TachiyomiAZ or TachiyomiEH and are included and possibly modified in TachiyomiSY
* Source migration, migrate all your manga from one source to another
* Custom hentai sources:
* * E-Hentai/ExHentai
* 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
* * Tsumino
* Saving searches
* Autoscroll
* Page preload customization
* Customize image cache size
* Batch import of custom sources and featured extensions
* Automatic CAPTCHA solving
* TriState Filters
* Infinite history page
* Search your history page
* Advanced source settings page, searching, enable/disable all
* Click tag for local search, long click tag for global search
* Merge multiple of the same manga from different sources
* Drag and drop library sorting
* Library search engine, includes exclude, quotes as absolute, and a bunch of other ways to search
* New E-Hentai/ExHentai features, such as language settings and watched list settings
* Enhanced views for internal and integrated sources
* Enhanced usability for internal and delegated sources
* Custom sources:
* * E-Hentai/ExHentai
* Additional features for some extensions, features include custom description, opening in app, batch add to library, and a bunch of other things based on the source:
* * 8Muses (EroMuse)
* * HBrowse
* * HentaiCafe (inside Foolside)
* * Hitomi.la
* * Mangadex
* * NHentai
* * PervEden (EN and IT)
* * Puruin
* * Tsumino
## Download
Get the app from our [releases page](https://github.com/jobobby04/tachiyomisy/releases/latest).
@@ -82,7 +76,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
<details><summary>Issues</summary>
1. **Before reporting a new issue, take a look at the [FAQ](https://github.com/inorichi/tachiyomi/wiki/FAQ), the [changelog](https://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).**
1. **Before reporting a new issue, take a look at the [FAQ](https://tachiyomi.org/help/faq/), the [changelog](https://github.com/jobobby04/tachiyomisy/releases) and the already opened [issues](https://github.com/jobobby04/tachiyomisy/issues).**
2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi)
</details>
@@ -109,7 +103,7 @@ DON'T: https://github.com/inorichi/tachiyomi/issues/75
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
* Include screenshot (if needed)
Catalogue requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
Source requests should be created at https://github.com/inorichi/tachiyomi-extensions, they do not belong in this repository.
</details>
## FAQ
+50 -53
View File
@@ -37,14 +37,15 @@ ext {
android {
compileSdkVersion AndroidConfig.compileSdk
buildToolsVersion AndroidConfig.buildTools
ndkVersion AndroidConfig.ndk
defaultConfig {
applicationId "eu.kanade.tachiyomi.sy"
minSdkVersion AndroidConfig.minSdk
targetSdkVersion AndroidConfig.targetSdk
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 8
versionName "1.3.0"
versionCode 10
versionName "1.4.0"
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@@ -138,34 +139,35 @@ androidExtensions {
dependencies {
// Source models and interfaces from Tachiyomi 1.x
implementation 'tachiyomi.sourceapi:source-api:1.1'
// AndroidX libraries
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.annotation:annotation:1.2.0-alpha01'
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
implementation 'androidx.biometric:biometric:1.1.0-alpha02'
implementation 'androidx.browser:browser:1.2.0'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha01'
implementation 'androidx.browser:browser:1.3.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0-alpha1'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.1.0'
implementation 'androidx.core:core-ktx:1.4.0-alpha01'
implementation 'androidx.core:core-ktx:1.5.0-alpha05'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
final lifecycle_version = '2.3.0-alpha07'
final lifecycle_version = '2.3.0-beta01'
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
// Job scheduling
final work_version = '2.5.0-alpha01'
implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation "androidx.work:work-runtime-ktx:2.5.0-beta02"
// UI library
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'com.google.android.material:material:1.3.0-alpha04'
standardImplementation 'com.google.firebase:firebase-core:17.5.0'
standardImplementation 'com.google.firebase:firebase-core:18.0.0'
// ReactiveX
implementation 'io.reactivex:rxandroid:1.2.1'
@@ -174,11 +176,11 @@ dependencies {
implementation 'com.github.pwittchen:reactivenetwork:0.13.0'
// Network client
final okhttp_version = '4.9.0'
final okhttp_version = '4.10.0-RC1'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttp_version"
implementation 'com.squareup.okio:okio:2.8.0'
implementation 'com.squareup.okio:okio:2.9.0'
// TLS 1.3 support for Android < 10
implementation 'org.conscrypt:conscrypt-android:2.5.1'
@@ -186,10 +188,12 @@ dependencies {
// REST
final retrofit_version = '2.9.0'
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation "com.squareup.retrofit2:adapter-rxjava:$retrofit_version"
// JSON
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.github.salomonbrys.kotson:kotson:2.5.0'
@@ -199,7 +203,7 @@ dependencies {
// Disk
implementation 'com.jakewharton:disklrucache:2.0.2'
implementation 'com.github.inorichi:unifile:e9ee588'
implementation 'com.github.inorichi:junrar-android:634c1f5'
implementation 'com.github.junrar:junrar:7.4.0'
// HTML parser
implementation 'org.jsoup:jsoup:1.13.1'
@@ -211,13 +215,13 @@ dependencies {
implementation 'com.github.gabrielemariotti.changeloglib:changelog:2.1.0'
// Database
implementation 'androidx.sqlite:sqlite:2.1.0'
implementation 'androidx.sqlite:sqlite-ktx:2.1.0'
implementation 'com.github.inorichi.storio:storio-common:8be19de@aar'
implementation 'com.github.inorichi.storio:storio-sqlite:8be19de@aar'
implementation 'io.requery:sqlite-android:3.32.2'
implementation 'io.requery:sqlite-android:3.33.0'
// Preferences
implementation 'com.github.tfcporciuncula:flow-preferences:1.3.1'
implementation 'com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.3'
// Model View Presenter
final nucleus_version = '3.0.0'
@@ -233,7 +237,7 @@ dependencies {
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
kapt "com.github.bumptech.glide:compiler:$glide_version"
implementation 'com.github.tachiyomiorg:subsampling-scale-image-view:bff2806'
implementation 'com.github.tachiyomiorg:subsampling-scale-image-view:6caf219'
// Logging
implementation 'com.jakewharton.timber:timber:4.7.1'
@@ -251,7 +255,6 @@ dependencies {
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
implementation 'com.nononsenseapps:filepicker:2.5.2'
implementation 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0'
implementation 'com.github.mthli:Slice:v1.3'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.github.carlosesco:DirectionalViewPager:a844dbca0a'
@@ -277,8 +280,7 @@ dependencies {
implementation "io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbinding_version"
// Licenses
// NOTE: REMEMBER TO UPDATE GRADLE PLUGIN
implementation 'com.mikepenz:aboutlibraries:8.3.0'
implementation "com.mikepenz:aboutlibraries:$BuildPluginsVersion.ABOUTLIB_PLUGIN"
// Tests
testImplementation 'junit:junit:4.13'
@@ -292,27 +294,25 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$BuildPluginsVersion.KOTLIN"
// 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.9'
final coroutines_version = '1.4.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
// SY for mangadex utils
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.0.1"
// Text distance (EH)
implementation 'info.debatty:java-string-similarity:1.2.1'
implementation 'info.debatty:java-string-similarity:2.0.0'
// Firebase (EH)
implementation 'com.google.firebase:firebase-analytics-ktx:17.5.0'
implementation 'com.google.firebase:firebase-crashlytics-ktx:17.2.1'
implementation 'com.google.firebase:firebase-analytics-ktx:18.0.0'
implementation 'com.google.firebase:firebase-crashlytics-ktx:17.3.0'
// Better logging (EH)
implementation 'com.elvishew:xlog:1.6.1'
implementation 'com.elvishew:xlog:1.7.1'
// Debug utils (EH)
final def debug_overlay_version = '1.1.3'
@@ -321,24 +321,13 @@ dependencies {
releaseImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
testImplementation "com.ms-square:debugoverlay-no-op:$debug_overlay_version"
// Humanize (EH) used for E-Hentai updater statistics
implementation 'com.github.mfornos:humanize-slim:1.2.2'
// RatingBar (SY)
implementation 'me.zhanghai.android.materialratingbar:library:1.3.1'
implementation 'me.zhanghai.android.materialratingbar:library:1.4.0'
// JsonReader for similar manga
implementation 'com.squareup.moshi:moshi:1.11.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
final def markwon_version = '4.5.1'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
implementation "io.noties.markwon:image:$markwon_version"
implementation "io.noties.markwon:linkify:$markwon_version"
implementation 'com.google.guava:guava:29.0-android'
}
buildscript {
@@ -352,21 +341,29 @@ buildscript {
repositories {
mavenCentral()
jcenter()
}
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api-markers
tasks.withType(AbstractKotlinCompile).all {
kotlinOptions.freeCompilerArgs += ["-Xopt-in=kotlin.Experimental"]
kotlinOptions.freeCompilerArgs += [
"-Xopt-in=kotlin.Experimental",
"-Xopt-in=kotlin.RequiresOptIn",
"-Xuse-experimental=kotlin.ExperimentalStdlibApi",
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi",
]
}
// Duplicating Hebrew string assets due to some locale code issues on different devices
task copyResources(type: Copy) {
task copyHebrewStrings(type: Copy) {
from './src/main/res/values-he'
into './src/main/res/values-iw'
include '**/*'
}
preBuild.dependsOn(formatKotlin, copyResources)
preBuild.dependsOn(formatKotlin, copyHebrewStrings)
if (!getGradle().getStartParameter().getTaskRequests().toString().contains("Debug")) {
apply plugin: 'com.google.gms.google-services'
+36
View File
@@ -30,6 +30,42 @@
<init>();
}
# Kotlin Serialization
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class eu.kanade.tachiyomi.**$$serializer { *; }
-keepclassmembers class eu.kanade.tachiyomi.** {
*** Companion;
}
-keepclasseswithmembers class eu.kanade.tachiyomi.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class exh.**$$serializer { *; }
-keepclassmembers class exh.** {
*** Companion;
}
-keepclasseswithmembers class exh.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
*** Companion;
}
-keepclasseswithmembers class xyz.nulldev.ts.api.http.serializer.** {
kotlinx.serialization.KSerializer serializer(...);
}
# Madokami extension username and password crash fix
-keepclassmembers class androidx.preference.EditTextPreference {
*** mOnBindEditTextListener;
@@ -1,38 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.07776"
android:scaleY="0.07776"
android:translateX="15.12"
android:translateY="15.12">
<path
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
android:fillType="evenOdd"
android:fillColor="#000"/>
android:pathData="M474.4,272.2c115.3,-1.4 144.2,48.2 185.2,74.7c21.6,8.3 17.7,16.3 26.2,28.7c2.5,3.5 5.5,5 9.5,7.5c30.3,18.2 -10.8,39.6 -28.2,40.3c7.1,-4.6 13.2,-5.9 17.7,-12.4c-0.3,0 -0.6,0 -0.9,0c0,-0.3 0,-0.6 0,-0.9c-14.6,-4 -13.3,8.8 -29.2,6.2c3.5,-0.4 12.6,-5.8 12.4,-13.3c-11.9,-7.2 -31.6,-15.2 -46.9,-14.2c-0.1,0.4 -0.1,0.4 -0.2,0.8c36.3,24.2 25.7,57.3 47.1,63.8c-11.6,-0.5 -5.9,2.1 0,7.1c-14.6,-3 -16.1,-19.2 -23,-33.6c-7.8,-12.9 -15.4,-28.5 -23.9,-29.2c-2,9.2 2,56.4 14.7,46.6c-10,12.3 4.4,7.2 11,4.7c-23.9,19 -35.1,13.8 -42.8,-13.8c-6.2,-13.4 -14.6,-34.2 -19.5,-46.6c-2.8,-7.1 -7.5,-13.3 -13.6,-17.9c-41.8,-31.2 -72.8,-33.3 -116.9,-26.1c-11.1,-21.4 -27.5,-40.4 -38.9,-61.9C435,277.7 452,272.4 474.4,272.2z"
android:fillColor="#2E84BF"/>
<path
android:pathData="M14.5,7L86.5,7A7,7 0,0 1,93.5 14L93.5,95A7,7 0,0 1,86.5 102L14.5,102A7,7 0,0 1,7.5 95L7.5,14A7,7 0,0 1,14.5 7z"
android:fillType="evenOdd"
android:fillColor="#455A64"/>
android:pathData="M313.4,366C395.3,524.3 482,472.7 607.1,532.8c109.3,67.9 83.8,212.8 -30.1,257.7c0,-28.3 0,-56.6 0,-84.9c46.4,-45.1 20.7,-112.1 -37.1,-125c-68.8,-15 -145.6,-29.5 -191.7,-62.5C298,480.3 286.6,416.8 313.4,366z"
android:fillColor="#2E84BF"/>
<path
android:pathData="M7.5,12.01C7.5,9.24 9.74,7 12.5,7L17.5,7L17.5,102L12.5,102C9.74,102 7.5,99.77 7.5,96.99L7.5,12.01Z"
android:fillType="evenOdd"
android:fillColor="#607D8B"/>
android:pathData="M424.8,720.6c0.1,25.4 1.9,53.9 -0.9,77.8c-94.2,-25.7 -162.5,-103.3 -124.7,-192.8c0,4.7 0,9.4 0,14.2C307.8,680 359.3,712.6 424.8,720.6z"
android:fillColor="#2E84BF"/>
<path
android:name="path_3"
android:pathData="M 54 54.5 M 28.5 54.5 C 28.5 47.74 31.188 41.249 35.969 36.469 C 40.749 31.688 47.24 29 54 29 C 60.76 29 67.251 31.688 72.031 36.469 C 76.812 41.249 79.5 47.74 79.5 54.5 C 79.5 61.26 76.812 67.751 72.031 72.531 C 67.251 77.312 60.76 80 54 80 C 47.24 80 40.749 77.312 35.969 72.531 C 31.188 67.751 28.5 61.26 28.5 54.5"
android:fillColor="#CE2828"
android:fillType="evenOdd"/>
android:pathData="M247,228l138.9,37.1L500,444.7l47.8,-72.5c34.5,14.5 37,85.5 55.7,96.4l-23,34.5C477,474.9 387.8,469.9 351.3,394.7C317.9,341.3 247.1,228.1 247,228z"
android:fillColor="#CE2828"/>
<path
android:name="path_4"
android:pathData="M 54 54.5 M 34.06 54.5 C 33.964 50.23 35.243 46.04 37.707 42.551 C 40.171 39.062 43.692 36.455 47.748 35.117 C 51.805 33.779 56.185 33.779 60.242 35.117 C 64.298 36.455 67.819 39.062 70.283 42.551 C 72.747 46.04 74.026 50.23 73.93 54.5 C 74.026 58.77 72.747 62.96 70.283 66.449 C 67.819 69.938 64.298 72.545 60.242 73.883 C 56.185 75.221 51.805 75.221 47.748 73.883 C 43.692 72.545 40.171 69.938 37.707 66.449 C 35.243 62.96 33.964 58.77 34.06 54.5"
android:fillColor="#000"
android:fillType="evenOdd"/>
android:pathData="M602.6,285.5c16.2,9.6 33.1,19.8 46.5,32.8c7.5,7.2 14.9,12.3 25.3,18.3c3.9,2.2 4.7,2.1 7.8,4.7L753,228l-137.1,37.1L602.6,285.5z"
android:fillColor="#CE2828"/>
<path
android:name="path_5"
android:pathData="M50.953,47.457C51.828,47.457 53.465,44.672 53.465,43.746C53.465,42.82 50.238,40.461 45.684,40.461C39.32,40.461 37.535,44.926 37.535,47.539C37.535,50.238 38.285,53.355 45.152,55.465C46.977,56.055 49.789,56.98 49.789,59.258C49.789,61.195 47.727,62.207 45.34,62.207C38.605,62.207 39.137,55.883 38.605,55.883C38.285,55.883 36.691,57.402 36.691,60.098C36.691,65.07 41.199,67.434 45.652,67.434C52.27,67.434 54.871,62.965 54.871,59.34C54.871,56.391 53.523,53.188 46.941,50.996C45.215,50.406 42.113,49.563 42.113,47.285C42.113,45.434 44.117,44.672 45.684,44.672C48.949,44.672 50.168,47.457 50.953,47.457ZM50.953,47.457"
android:fillColor="#F7D009"
android:fillType="evenOdd" />
<path
android:name="path_6"
android:pathData="M56.734,66.254C56.734,67.012 56.734,67.434 57.496,67.434C64.391,67.434 71.469,58.582 71.191,49.395C71.191,48.047 71.285,47.035 70.688,46.949L67.895,46.949C67.551,46.949 66.766,46.867 66.766,47.371C66.766,47.875 67.113,49.227 67.113,50.742C67.113,53.188 66.27,56.98 65.012,56.98C64.391,56.98 61.313,53.523 61.313,49.816C61.313,48.887 61.598,47.961 61.598,47.457C61.598,46.867 60.906,46.949 60.504,46.949L57.934,46.949C56.711,46.949 56.734,46.949 56.734,48.383C56.734,57.148 62.188,59.34 62.188,60.688C62.188,61.027 61.254,62.207 57.645,62.207C56.805,62.207 56.734,63.133 56.734,63.469ZM56.734,66.254"
android:fillColor="#E40F85"
android:fillType="evenOdd" />
android:pathData="M500.9,871l61.9,-50.4l0.9,-212.3c0,0 -4.5,-11.4 -85.2,-25.5c-17.1,-3 -29.7,-7.2 -29.8,-7.2l-12.3,-3.6l1.8,248.5L500.9,871z"
android:fillColor="#CE2828"/>
</group>
</vector>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/transparent"/>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/transparent"/>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+70 -35
View File
@@ -25,7 +25,7 @@
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Tachiyomi.Light"
android:usesCleartextTraffic="true">
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.main.MainActivity"
android:launchMode="singleTop"
@@ -42,7 +42,8 @@
<activity
android:name=".ui.main.DeepLinkActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@android:style/Theme.NoDisplay"
android:label="@string/process_text_action_name">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<action android:name="com.google.android.gms.actions.SEARCH_ACTION" />
@@ -53,6 +54,11 @@
<action android:name="eu.kanade.tachiyomi.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
@@ -60,7 +66,14 @@
</activity>
<activity
android:name=".ui.reader.ReaderActivity"
android:launchMode="singleTask" />
android:launchMode="singleTask">
<intent-filter>
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
</intent-filter>
<meta-data android:name="com.samsung.android.support.REMOTE_ACTION"
android:resource="@xml/s_pen_actions"/>
</activity>
<activity
android:name=".ui.security.BiometricUnlockActivity"
android:theme="@style/Theme.Splash" />
@@ -85,6 +98,9 @@
android:scheme="tachiyomi" />
</intent-filter>
</activity>
<activity
android:name=".ui.setting.track.MyAnimeListLoginActivity"
android:configChanges="uiMode|orientation|screenSize" />
<activity
android:name=".ui.setting.track.ShikimoriLoginActivity"
android:label="Shikimori">
@@ -153,6 +169,10 @@
android:exported="false" />
<!-- EH -->
<service
android:name="exh.md.similar.SimilarUpdateService"
android:exported="false" />
<service
android:name="exh.eh.EHentaiUpdateWorker"
android:permission="android.permission.BIND_JOB_SERVICE"
@@ -170,39 +190,39 @@
<!-- EH -->
<data
android:host="g.e-hentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="http" />
<data
android:host="g.e-hentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="https" />
<data
android:host="e-hentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="http" />
<data
android:host="e-hentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="https" />
<!-- EXH -->
<data
android:host="exhentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="http" />
<data
android:host="exhentai.org"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="https" />
<!-- nhentai -->
<data
android:host="nhentai.net"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="http" />
<data
android:host="nhentai.net"
android:pathPrefix="/g/"
android:pathPattern="/g/.*"
android:scheme="https" />
<!-- Perv Eden -->
@@ -228,76 +248,91 @@
<!-- Tsumino -->
<data
android:host="www.tsumino.com"
android:pathPrefix="/Book/Info/"
android:pathPattern="/Book/Info/.*"
android:scheme="http" />
<data
android:host="www.tsumino.com"
android:pathPrefix="/Book/Info/"
android:pathPattern="/Book/Info/.*"
android:scheme="https" />
<data
android:host="www.tsumino.com"
android:pathPrefix="/Read/View/"
android:pathPattern="/Read/View/.*"
android:scheme="http" />
<data
android:host="www.tsumino.com"
android:pathPrefix="/Read/View/"
android:pathPattern="/Read/View/.*"
android:scheme="https" />
<!-- Hitomi.la -->
<data
android:host="hitomi.la"
android:pathPrefix="/galleries/"
android:pathPattern="/galleries/.*"
android:scheme="http" />
<data
android:host="hitomi.la"
android:pathPrefix="/reader/"
android:pathPattern="/reader/.*"
android:scheme="http" />
<data
android:host="hitomi.la"
android:pathPrefix="/galleries/"
android:pathPattern="/galleries/.*"
android:scheme="https" />
<data
android:host="hitomi.la"
android:pathPrefix="/reader/"
android:pathPattern="/reader/.*"
android:scheme="https" />
<!-- Pururin.io -->
<data
android:host="pururin.io"
android:pathPrefix="/gallery/"
android:pathPattern="/gallery/.*"
android:scheme="http" />
<data
android:host="pururin.io"
android:pathPrefix="/gallery/"
android:pathPattern="/gallery/.*"
android:scheme="https" />
<!-- HBrowse -->
<data
android:host="www.hbrowse.com"
android:pathPrefix="/"
android:scheme="http" />
<data
android:host="www.hbrowse.com"
android:pathPrefix="/"
android:scheme="https" />
<!-- MangaDex -->
<data
android:host="mangadex.org"
android:pathPattern="\/(title|manga)\/"
android:scheme="http" />
<data
android:host="mangadex.org"
android:pathPattern="\/(title|manga)\/"
android:scheme="https" />
<data
android:scheme="https"
android:host="www.mangadex.org"
android:pathPattern="\/(title|manga)\/"
android:scheme="http" />
android:pathPattern="/manga/..*" />
<data
android:scheme="https"
android:host="mangadex.org"
android:pathPattern="/manga/..*" />
<data
android:scheme="https"
android:host="www.mangadex.cc"
android:pathPattern="/manga/..*" />
<data
android:scheme="https"
android:host="www.mangadex.cc"
android:pathPattern="/manga/..*" />
<data
android:scheme="https"
android:host="www.mangadex.org"
android:pathPattern="\/(title|manga)\/"
android:scheme="https" />
android:pathPattern="/title/..*" />
<data
android:scheme="https"
android:host="mangadex.org"
android:pathPattern="/title/..*" />
<data
android:scheme="https"
android:host="www.mangadex.cc"
android:pathPattern="/title/..*" />
<data
android:scheme="https"
android:host="www.mangadex.cc"
android:pathPattern="/title/..*" />
</intent-filter>
</activity>
<activity
+21 -11
View File
@@ -16,7 +16,6 @@ import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog
import com.elvishew.xlog.printer.AndroidPrinter
import com.elvishew.xlog.printer.Printer
import com.elvishew.xlog.printer.file.FilePrinter
import com.elvishew.xlog.printer.file.backup.NeverBackupStrategy
import com.elvishew.xlog.printer.file.clean.FileLastModifiedCleanStrategy
import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
@@ -36,6 +35,7 @@ import exh.debug.DebugToggles
import exh.log.CrashlyticsPrinter
import exh.log.EHDebugModeOverlay
import exh.log.EHLogLevel
import exh.log.EnhancedFilePrinter
import exh.syDebugVersion
import io.realm.Realm
import io.realm.RealmConfiguration
@@ -50,6 +50,8 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
import java.io.File
import java.security.NoSuchAlgorithmException
import java.security.Security
import java.text.SimpleDateFormat
import java.util.Locale
import javax.net.ssl.SSLContext
import kotlin.concurrent.thread
import kotlin.time.ExperimentalTime
@@ -113,15 +115,15 @@ open class App : Application(), LifecycleObserver {
try {
SSLContext.getInstance("TLSv1.2")
} catch (e: NoSuchAlgorithmException) {
XLog.e("Could not install Android 7 broken SSL workaround!", e)
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
}
try {
ProviderInstaller.installIfNeeded(applicationContext)
} catch (e: GooglePlayServicesRepairableException) {
XLog.e("Could not install Android 7 broken SSL workaround!", e)
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
} catch (e: GooglePlayServicesNotAvailableException) {
XLog.e("Could not install Android 7 broken SSL workaround!", e)
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
}
}
}
@@ -181,8 +183,8 @@ open class App : Application(), LifecycleObserver {
val logConfig = LogConfiguration.Builder()
.logLevel(logLevel)
.st(2)
.nb()
.disableStackTrace()
.disableBorder()
.build()
val printers = mutableListOf<Printer>(AndroidPrinter())
@@ -193,16 +195,24 @@ open class App : Application(), LifecycleObserver {
"logs"
)
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
@OptIn(ExperimentalTime::class)
printers += FilePrinter
printers += EnhancedFilePrinter
.Builder(logFolder.absolutePath)
.fileNameGenerator(
object : DateFileNameGenerator() {
override fun generateFileName(logLevel: Int, timestamp: Long): String {
return super.generateFileName(logLevel, timestamp) + "-${BuildConfig.BUILD_TYPE}.log"
return super.generateFileName(
logLevel,
timestamp
) + "-${BuildConfig.BUILD_TYPE}.log"
}
}
)
.flattener { timeMillis, level, tag, message ->
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
}
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.toLongMilliseconds()))
.backupStrategy(NeverBackupStrategy())
.build()
@@ -217,8 +227,8 @@ open class App : Application(), LifecycleObserver {
*printers.toTypedArray()
)
XLog.d("Application booting...")
XLog.nst().d(
XLog.tag("Init").d("Application booting...")
XLog.tag("Init").disableStackTrace().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" +
@@ -243,7 +253,7 @@ open class App : Application(), LifecycleObserver {
.install()
} catch (e: IllegalStateException) {
// Crashes if app is in background
XLog.e("Failed to initialize debug overlay, app in background?", e)
XLog.tag("Init").e("Failed to initialize debug overlay, app in background?", e)
}
}
}
@@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import exh.eh.EHentaiUpdateHelper
import io.noties.markwon.Markwon
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addSingleton
@@ -43,16 +43,16 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { DownloadManager(app) }
addSingletonFactory { CustomMangaManager(app) }
addSingletonFactory { TrackManager(app) }
addSingletonFactory { Gson() }
// SY -->
addSingletonFactory { EHentaiUpdateHelper(app) }
addSingletonFactory { Json { ignoreUnknownKeys = true } }
addSingletonFactory { Markwon.create(app) }
// SY -->
addSingletonFactory { CustomMangaManager(app) }
addSingletonFactory { EHentaiUpdateHelper(app) }
// SY <--
// Asynchronously init expensive components for a faster cold start
@@ -1,11 +1,18 @@
package eu.kanade.tachiyomi
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
object Migrations {
@@ -20,13 +27,13 @@ object Migrations {
*/
fun upgrade(preferences: PreferencesHelper): Boolean {
val context = preferences.context
val oldVersion = preferences.lastVersionCode().get()
// Cancel app updater job for debug builds that don't include it
if (BuildConfig.DEBUG && !BuildConfig.INCLUDE_UPDATER) {
UpdaterJob.cancelTask(context)
}
val oldVersion = preferences.lastVersionCode().get()
if (oldVersion < BuildConfig.VERSION_CODE) {
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
@@ -92,8 +99,32 @@ object Migrations {
preferences.librarySortingMode().set(LibrarySort.ALPHA)
}
}
if (oldVersion < 52) {
// Migrate library filters to tri-state versions
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
fun convertBooleanPrefToTriState(key: String): Int {
val oldPrefValue = prefs.getBoolean(key, false)
return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE.value
else ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value
}
prefs.edit {
putInt(PreferenceKeys.filterDownloaded, convertBooleanPrefToTriState("pref_filter_downloaded_key"))
remove("pref_filter_downloaded_key")
putInt(PreferenceKeys.filterUnread, convertBooleanPrefToTriState("pref_filter_unread_key"))
remove("pref_filter_unread_key")
putInt(PreferenceKeys.filterCompleted, convertBooleanPrefToTriState("pref_filter_completed_key"))
remove("pref_filter_completed_key")
}
// Force MAL log out due to login flow change
val trackManager = Injekt.get<TrackManager>()
trackManager.myAnimeList.logout()
}
return true
}
return false
}
}
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.annoations
package eu.kanade.tachiyomi.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@@ -0,0 +1,107 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.eh.EHentaiThrottleManager
import rx.Observable
import uy.kohesive.injekt.injectLazy
abstract class AbstractBackupManager(protected val context: Context) {
internal val databaseHelper: DatabaseHelper by injectLazy()
internal val sourceManager: SourceManager by injectLazy()
internal val trackManager: TrackManager by injectLazy()
protected val preferences: PreferencesHelper by injectLazy()
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String?
/**
* Returns manga
*
* @return [Manga], null if not found
*/
internal fun getMangaFromDatabase(manga: Manga): Manga? =
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
/**
* [Observable] that fetches chapter information
*
* @param source source of manga
* @param manga manga that needs updating
* @param chapters list of chapters in the backup
* @return [Observable] that contains manga
*/
internal open fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
return (
if (source is EHentai) {
source.fetchChapterList(manga, throttleManager::throttle)
} else {
source.fetchChapterList(manga)
}
).map {
syncChaptersWithSource(databaseHelper, it, manga, source)
}
.doOnNext { (first) ->
if (first.isNotEmpty()) {
chapters.forEach { it.manga_id = manga.id }
updateChapters(chapters)
}
}
}
/**
* Returns list containing manga from library
*
* @return [Manga] from library
*/
protected fun getFavoriteManga(): List<Manga> =
databaseHelper.getFavoriteMangas().executeAsBlocking()
// SY -->
/**
* Returns list containing merged manga that are possibly not in the library
*
* @return merged [Manga] that are possibly not in the library
*/
protected fun getMergedManga(): List<Manga> =
databaseHelper.getMergedMangas().executeAsBlocking()
// SY <--
/**
* Inserts manga and returns id
*
* @return id of [Manga], null if not found
*/
internal fun insertManga(manga: Manga): Long? =
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
/**
* Inserts list of chapters
*/
protected fun insertChapters(chapters: List<Chapter>) {
databaseHelper.insertChapters(chapters).executeAsBlocking()
}
/**
* Updates a list of chapters
*/
protected fun updateChapters(chapters: List<Chapter>) {
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
}
/**
* Return number of backups.
*
* @return number of backups selected by user
*/
protected fun numberOfBackups(): Int = preferences.numberOfBackups().get()
}
@@ -0,0 +1,143 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
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.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import exh.eh.EHentaiThrottleManager
import kotlinx.coroutines.Job
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val context: Context, protected val notifier: BackupNotifier) {
protected val db: DatabaseHelper by injectLazy()
protected val trackManager: TrackManager by injectLazy()
var job: Job? = null
protected lateinit var backupManager: T
protected var restoreAmount = 0
protected var restoreProgress = 0
// SY -->
protected val throttleManager = EHentaiThrottleManager()
// SY <--
/**
* Mapping of source ID to source name from backup data
*/
protected var sourceMapping: Map<Long, String> = emptyMap()
protected val errors = mutableListOf<Pair<Date, String>>()
abstract fun performRestore(uri: Uri): Boolean
fun restoreBackup(uri: Uri): Boolean {
val startTime = System.currentTimeMillis()
restoreProgress = 0
errors.clear()
if (!performRestore(uri)) {
return false
}
val endTime = System.currentTimeMillis()
val time = endTime - startTime
val logFile = writeErrorLog()
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
return true
}
/**
* [Observable] that fetches chapter information
*
* @param source source of manga
* @param manga manga that needs updating
* @return [Observable] that contains manga
*/
internal fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */)
// If there's any error, return empty update and continue.
.onErrorReturn {
val errorMessage = if (it is NoChaptersException) {
context.getString(R.string.no_chapters_error)
} else {
it.message
}
errors.add(Date() to "${manga.title} - $errorMessage")
Pair(emptyList(), emptyList())
}
}
/**
* [Observable] that refreshes tracking information
* @param manga manga that needs updating.
* @param tracks list containing tracks from restore file.
* @return [Observable] that contains updated track item
*/
internal fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
return Observable.from(tracks)
.flatMap { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged) {
service.refresh(track)
.doOnNext { db.insertTrack(it).executeAsBlocking() }
.onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}")
track
}
} else {
errors.add(Date() to "${manga.title} - ${context.getString(R.string.tracker_not_logged_in, service?.name)}")
Observable.empty()
}
}
}
/**
* Called to update dialog in [BackupConst]
*
* @param progress restore progress
* @param amount total restoreAmount of manga
* @param title title of restored manga
*/
internal fun showRestoreProgress(
progress: Int,
amount: Int,
title: String
) {
notifier.showRestoreProgress(title, progress, amount)
}
internal fun writeErrorLog(): File {
try {
if (errors.isNotEmpty()) {
val destFile = File(context.externalCacheDir, "tachiyomi_restore.txt")
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
destFile.bufferedWriter().use { out ->
errors.forEach { (date, message) ->
out.write("[${sdf.format(date)}] $message\n")
}
}
return destFile
}
} catch (e: Exception) {
// Empty
}
return File("")
}
}
@@ -0,0 +1,16 @@
package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy
abstract class AbstractBackupRestoreValidator {
protected val sourceManager: SourceManager by injectLazy()
protected val trackManager: TrackManager by injectLazy()
abstract fun validate(context: Context, uri: Uri): Results
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
}
@@ -7,4 +7,9 @@ object BackupConst {
private const val NAME = "BackupRestoreServices"
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
const val EXTRA_TYPE = "$ID.$NAME.EXTRA_TYPE"
const val BACKUP_TYPE_LEGACY = 0
const val BACKUP_TYPE_FULL = 1
}
@@ -4,11 +4,13 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning
@@ -46,17 +48,14 @@ class BackupCreateService : Service() {
* @param uri path of Uri
* @param flags determines what to backup
*/
fun start(context: Context, uri: Uri, flags: Int) {
fun start(context: Context, uri: Uri, flags: Int, type: Int) {
if (!isRunning(context)) {
val intent = Intent(context, BackupCreateService::class.java).apply {
putExtra(BackupConst.EXTRA_URI, uri)
putExtra(BackupConst.EXTRA_FLAGS, flags)
putExtra(BackupConst.EXTRA_TYPE, type)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
ContextCompat.startForegroundService(context, intent)
}
}
}
@@ -66,7 +65,6 @@ class BackupCreateService : Service() {
*/
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var backupManager: BackupManager
private lateinit var notifier: BackupNotifier
override fun onCreate() {
@@ -105,7 +103,11 @@ class BackupCreateService : Service() {
try {
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
backupManager = BackupManager(this)
val backupType = intent.getIntExtra(BackupConst.EXTRA_TYPE, BackupConst.BACKUP_TYPE_LEGACY)
val backupManager = when (backupType) {
BackupConst.BACKUP_TYPE_FULL -> FullBackupManager(this)
else -> LegacyBackupManager(this)
}
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
val unifile = UniFile.fromUri(this, backupFileUri)
@@ -7,6 +7,8 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -17,11 +19,13 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
override fun doWork(): Result {
val preferences = Injekt.get<PreferencesHelper>()
val backupManager = BackupManager(context)
val uri = preferences.backupsDirectory().get().toUri()
val flags = BackupCreateService.BACKUP_ALL
return try {
backupManager.createBackup(uri, flags, true)
FullBackupManager(context).createBackup(uri, flags, true)
if (preferences.createLegacyBackup().get()) {
LegacyBackupManager(context).createBackup(uri, flags, true)
}
Result.success()
} catch (e: Exception) {
Result.failure()
@@ -15,7 +15,7 @@ import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.TimeUnit
internal class BackupNotifier(private val context: Context) {
class BackupNotifier(private val context: Context) {
private val preferences: PreferencesHelper by injectLazy()
@@ -65,10 +65,7 @@ internal class BackupNotifier(private val context: Context) {
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.backup_created))
if (unifile.filePath != null) {
setContentText(unifile.filePath)
}
setContentText(unifile.filePath ?: unifile.name)
// Clear old actions if they exist
if (mActions.isNotEmpty()) {
@@ -4,54 +4,22 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
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.MANGAS
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.VERSION
import eu.kanade.tachiyomi.data.backup.models.DHistory
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.backup.full.FullBackupRestore
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestore
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.TrackManager
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.isServiceRunning
import exh.EXHMigrations
import exh.eh.EHentaiThrottleManager
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import rx.Observable
import timber.log.Timber
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.
*/
class BackupRestoreService : Service() {
@@ -72,16 +40,14 @@ class BackupRestoreService : Service() {
* @param context context of application
* @param uri path of Uri
*/
fun start(context: Context, uri: Uri) {
fun start(context: Context, uri: Uri, mode: Int, online: Boolean?) {
if (!isRunning(context)) {
val intent = Intent(context, BackupRestoreService::class.java).apply {
putExtra(BackupConst.EXTRA_URI, uri)
putExtra(BackupConst.EXTRA_MODE, mode)
online?.let { putExtra(BackupConst.EXTRA_TYPE, it) }
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
ContextCompat.startForegroundService(context, intent)
}
}
@@ -102,43 +68,9 @@ class BackupRestoreService : Service() {
*/
private lateinit var wakeLock: PowerManager.WakeLock
private var job: Job? = null
// SY -->
private val throttleManager = EHentaiThrottleManager()
private var skippedAmount = 0
private var totalAmount = 0
// SY <--
/**
* The progress of a backup restore
*/
private var restoreProgress = 0
/**
* Amount of manga in Json file (needed for restore)
*/
private var restoreAmount = 0
/**
* Mapping of source ID to source name from backup data
*/
private var sourceMapping: Map<Long, String> = emptyMap()
/**
* List containing errors
*/
private val errors = mutableListOf<Pair<Date, String>>()
private lateinit var backupManager: BackupManager
private var backupRestore: AbstractBackupRestore<*>? = null
private lateinit var notifier: BackupNotifier
private val db: DatabaseHelper by injectLazy()
private val trackManager: TrackManager by injectLazy()
override fun onCreate() {
super.onCreate()
@@ -159,7 +91,7 @@ class BackupRestoreService : Service() {
}
private fun destroyJob() {
job?.cancel()
backupRestore?.job?.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
@@ -180,349 +112,32 @@ class BackupRestoreService : Service() {
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
// SY -->
throttleManager.resetThrottle()
// SY <--
val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL)
val online = intent.getBooleanExtra(BackupConst.EXTRA_TYPE, true)
// Cancel any previous job if needed.
job?.cancel()
backupRestore?.job?.cancel()
backupRestore = when (mode) {
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online)
else -> LegacyBackupRestore(this, notifier)
}
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
writeErrorLog()
backupRestore?.writeErrorLog()
notifier.showRestoreError(exception.message)
stopSelf(startId)
}
job = GlobalScope.launch(handler) {
if (!restoreBackup(uri)) {
backupRestore?.job = GlobalScope.launch(handler) {
if (backupRestore?.restoreBackup(uri) == false) {
notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
}
}
job?.invokeOnCompletion {
backupRestore?.job?.invokeOnCompletion {
stopSelf(startId)
}
return START_NOT_STICKY
}
/**
* Restores data from backup file.
*
* @param uri backup file to restore
*/
private fun restoreBackup(uri: Uri): Boolean {
val startTime = System.currentTimeMillis()
val reader = JsonReader(contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser.parseReader(reader).asJsonObject
// Get parser version
val version = json.get(VERSION)?.asInt ?: 1
// Initialize manager
backupManager = BackupManager(this, version)
val mangasJson = json.get(MANGAS).asJsonArray
// SY -->
val validManga = mangasJson.filter {
var manga = backupManager.parser.fromJson<MangaImpl>(it.asJsonObject.get(MANGA))
// EXH -->
manga = EXHMigrations.migrateBackupEntry(manga)
val sourced = backupManager.sourceManager.get(manga.source) != null
if (!sourced) {
restoreAmount -= 1
}
sourced
}
totalAmount = mangasJson.size()
restoreAmount = validManga.count() + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
skippedAmount = mangasJson.size() - validManga.count()
// SY <--
restoreProgress = 0
errors.clear()
// Restore categories
json.get(CATEGORIES)?.let { restoreCategories(it) }
// SY -->
json.get(SAVEDSEARCHES)?.let { restoreSavedSearches(it) }
// SY <--
// Store source mapping for error messages
sourceMapping = BackupRestoreValidator.getSourceMapping(json)
// Restore individual manga
mangasJson.forEach {
if (job?.isActive != true) {
return false
}
restoreManga(it.asJsonObject)
}
val endTime = System.currentTimeMillis()
val time = endTime - startTime
val logFile = writeErrorLog()
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
return true
}
private fun restoreCategories(categoriesJson: JsonElement) {
db.inTransaction {
backupManager.restoreCategories(categoriesJson.asJsonArray)
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
}
// SY -->
private fun restoreSavedSearches(savedSearchesJson: JsonElement) {
backupManager.restoreSavedSearches(savedSearchesJson)
restoreProgress += 1
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 <--
private fun restoreManga(mangaJson: JsonObject) {
/* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson<MangaImpl>(mangaJson.get(MANGA))
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
mangaJson.get(CHAPTERS)
?: JsonArray()
)
val categories = backupManager.parser.fromJson<List<String>>(
mangaJson.get(CATEGORIES)
?: JsonArray()
)
val history = backupManager.parser.fromJson<List<DHistory>>(
mangaJson.get(HISTORY)
?: JsonArray()
)
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
mangaJson.get(TRACK)
?: JsonArray()
)
// EXH -->
manga = EXHMigrations.migrateBackupEntry(manga)
// <-- EXH
try {
val source = backupManager.sourceManager.get(manga.source)
if (source != null) {
restoreMangaData(manga, source, chapters, categories, history, tracks)
} else {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} - ${getString(R.string.source_not_found_name, sourceName)}")
}
} catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}")
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
}
/**
* Returns a manga restore observable
*
* @param manga manga data from json
* @param source source to get manga data from
* @param chapters chapters data from json
* @param categories categories data from json
* @param history history data from json
* @param tracks tracking data from json
*/
private fun restoreMangaData(
manga: Manga,
source: Source,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
val dbManga = backupManager.getMangaFromDatabase(manga)
db.inTransaction {
if (dbManga == null) {
// Manga not in database
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
} else { // Manga in database
// Copy information from manga already in database
backupManager.restoreMangaNoFetch(manga, dbManga)
// Fetch rest of manga information
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
}
}
}
/**
* [Observable] that fetches manga information
*
* @param manga manga that needs updating
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreMangaFetch(
source: Source,
manga: Manga,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
backupManager.restoreMangaFetchObservable(source, manga)
.onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}")
manga
}
.filter { it.id != null }
.flatMap {
chapterFetchObservable(source, it, chapters)
// Convert to the manga that contains new chapters.
.map { manga }
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks)
}
.flatMap {
trackingFetchObservable(it, tracks)
}
.subscribe()
}
private fun restoreMangaNoFetch(
source: Source,
backupManga: Manga,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
Observable.just(backupManga)
.flatMap { manga ->
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
chapterFetchObservable(source, manga, chapters)
.map { manga }
} else {
Observable.just(manga)
}
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks)
}
.flatMap { manga ->
trackingFetchObservable(manga, tracks)
}
.subscribe()
}
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
// Restore categories
backupManager.restoreCategoriesForManga(manga, categories)
// Restore history
backupManager.restoreHistoryForManga(history)
// Restore tracking
backupManager.restoreTrackForManga(manga, tracks)
}
/**
* [Observable] that fetches chapter information
*
* @param source source of manga
* @param manga manga that needs updating
* @return [Observable] that contains manga
*/
private fun chapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>): Observable<Pair<List<Chapter>, List<Chapter>>> {
return backupManager.restoreChapterFetchObservable(source, manga, chapters /* SY --> */, throttleManager /* SY <-- */)
// If there's any error, return empty update and continue.
.onErrorReturn {
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())
}
}
/**
* [Observable] that refreshes tracking information
* @param manga manga that needs updating.
* @param tracks list containing tracks from restore file.
* @return [Observable] that contains updated track item
*/
private fun trackingFetchObservable(manga: Manga, tracks: List<Track>): Observable<Track> {
return Observable.from(tracks)
.flatMap { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged) {
service.refresh(track)
.doOnNext { db.insertTrack(it).executeAsBlocking() }
.onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}")
track
}
} else {
errors.add(Date() to "${manga.title} - ${getString(R.string.tracker_not_logged_in, service?.name)}")
Observable.empty()
}
}
}
/**
* Called to update dialog in [BackupConst]
*
* @param progress restore progress
* @param amount total restoreAmount of manga
* @param title title of restored manga
*/
private fun showRestoreProgress(
progress: Int,
amount: Int,
title: String
) {
notifier.showRestoreProgress(title, progress, amount)
}
/**
* Write errors to error log
*/
private fun writeErrorLog(): File {
try {
if (errors.isNotEmpty()) {
val destFile = File(externalCacheDir, "tachiyomi_restore.txt")
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
destFile.bufferedWriter().use { out ->
errors.forEach { (date, message) ->
out.write("[${sdf.format(date)}] $message\n")
}
}
return destFile
}
} catch (e: Exception) {
// Empty
}
return File("")
}
}
@@ -0,0 +1,543 @@
package eu.kanade.tachiyomi.data.backup.full
import android.content.Context
import android.net.Uri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.full.models.Backup
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupChapter
import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.full.models.BackupFull
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
import eu.kanade.tachiyomi.data.backup.full.models.BackupTracking
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import exh.MERGED_SOURCE_ID
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata
import exh.savedsearches.JsonSavedSearch
import exh.source.getMainSource
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import okio.buffer
import okio.gzip
import okio.sink
import rx.Observable
import timber.log.Timber
import kotlin.math.max
@OptIn(ExperimentalSerializationApi::class)
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val parser = ProtoBuf
/**
* Create backup Json file from database
*
* @param uri path of Uri
* @param isJob backup called from job
*/
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
// Create root object
var backup: Backup? = null
databaseHelper.inTransaction {
val databaseManga = getFavoriteManga() /* SY --> */ + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */
backup = Backup(
backupManga(databaseManga, flags),
backupCategories(),
backupExtensionInfo(databaseManga),
backupSavedSearches()
)
}
try {
val file: UniFile = (
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.proto.gz""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
dir.createFile(BackupFull.getDefaultFilename())
} else {
UniFile.fromUri(context, uri)
}
)
?: throw Exception("Couldn't create backup file")
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) }
return file.uri.toString()
} catch (e: Exception) {
Timber.e(e)
throw e
}
}
private fun backupManga(mangas: List<Manga>, flags: Int): List<BackupManga> {
return mangas.map {
backupMangaObject(it, flags)
}
}
private fun backupExtensionInfo(mangas: List<Manga>): List<BackupSource> {
return mangas
.asSequence()
.map { it.source }
.distinct()
.map { sourceManager.getOrStub(it) }
.map { BackupSource.copyFrom(it) }
.toList()
}
/**
* Backup the categories of library
*
* @return list of [BackupCategory] to be backed up
*/
private fun backupCategories(): List<BackupCategory> {
return databaseHelper.getCategories()
.executeAsBlocking()
.map { BackupCategory.copyFrom(it) }
}
// SY -->
/**
* Backup the saved searches from sources
*
* @return list of [BackupSavedSearch] to be backed up
*/
private fun backupSavedSearches(): List<BackupSavedSearch> {
return preferences.savedSearches().get().map {
val sourceId = it.substringBefore(':').toLong()
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
BackupSavedSearch(
content.name,
content.query,
content.filters.toString(),
sourceId
)
}
}
// SY <--
/**
* Convert a manga to Json
*
* @param manga manga that gets converted
* @param options options for the backup
* @return [BackupManga] containing manga in a serializable form
*/
private fun backupMangaObject(manga: Manga, options: Int): BackupManga {
// Entry for this manga
val mangaObject = BackupManga.copyFrom(manga)
// SY -->
if (manga.source == MERGED_SOURCE_ID) {
manga.id?.let { mangaId ->
mangaObject.mergedMangaReferences = databaseHelper.getMergedMangaReferences(mangaId)
.executeAsBlocking()
.map { BackupMergedMangaReference.copyFrom(it) }
}
}
val source = sourceManager.get(manga.source)?.getMainSource()
if (source is MetadataSource<*, *>) {
manga.id?.let { mangaId ->
databaseHelper.getFlatMetadataForManga(mangaId).executeAsBlocking()?.let { flatMetadata ->
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
}
}
}
// SY <--
// Check if user wants chapter information in backup
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
if (chapters.isNotEmpty()) {
mangaObject.chapters = chapters.map { BackupChapter.copyFrom(it) }
}
}
// Check if user wants category information in backup
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
// Backup categories for this manga
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
if (categoriesForManga.isNotEmpty()) {
mangaObject.categories = categoriesForManga.mapNotNull { it.order }
}
}
// Check if user wants track information in backup
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
if (tracks.isNotEmpty()) {
mangaObject.tracking = tracks.map { BackupTracking.copyFrom(it) }
}
}
// Check if user wants history information in backup
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
if (historyForManga.isNotEmpty()) {
val history = historyForManga.mapNotNull { history ->
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
url?.let { BackupHistory(url, history.last_read) }
}
if (history.isNotEmpty()) {
mangaObject.history = history
}
}
}
return mangaObject
}
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
manga.id = dbManga.id
manga.copyFrom(dbManga)
insertManga(manga)
}
/**
* [Observable] that fetches manga information
*
* @param source source of manga
* @param manga manga that needs updating
* @return [Observable] that contains manga
*/
fun restoreMangaFetchObservable(source: Source?, manga: Manga, online: Boolean): Observable<Manga> {
return if (online && source != null /* SY --> */ && source !is MergedSource /* SY <-- */) {
source.fetchMangaDetails(manga)
.map { networkManga ->
manga.copyFrom(networkManga)
manga.favorite = manga.favorite
manga.initialized = true
manga.id = insertManga(manga)
manga
}
} else {
Observable.just(manga)
.map {
it.initialized = it.description != null
it.id = insertManga(it)
it
}
}
}
/**
* Restore the categories from Json
*
* @param backupCategories list containing categories
*/
internal fun restoreCategories(backupCategories: List<BackupCategory>) {
// Get categories from file and from db
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
// Iterate over them
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
// Used to know if the category is already in the db
var found = false
for (dbCategory in dbCategories) {
// If the category is already in the db, assign the id to the file's category
// and do nothing
if (category.name == dbCategory.name) {
category.id = dbCategory.id
found = true
break
}
}
// If the category isn't in the db, remove the id and insert a new category
// Store the inserted id in the category
if (!found) {
// Let the db assign the id
category.id = null
val result = databaseHelper.insertCategory(category).executeAsBlocking()
category.id = result.insertedId()?.toInt()
}
}
}
/**
* Restores the categories a manga is in.
*
* @param manga the manga whose categories have to be restored.
* @param categories the categories to restore.
*/
internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = mutableListOf<MangaCategory>()
categories.forEach { backupCategoryOrder ->
backupCategories.firstOrNull {
it.order == backupCategoryOrder
}?.let { backupCategory ->
dbCategories.firstOrNull { dbCategory ->
dbCategory.name == backupCategory.name
}?.let { dbCategory ->
mangaCategoriesToUpdate += MangaCategory.create(manga, dbCategory)
}
}
}
// Update database
if (mangaCategoriesToUpdate.isNotEmpty()) {
databaseHelper.deleteOldMangasCategories(listOf(manga)).executeAsBlocking()
databaseHelper.insertMangasCategories(mangaCategoriesToUpdate).executeAsBlocking()
}
}
/**
* Restore history from Json
*
* @param history list containing history to be restored
*/
internal fun restoreHistoryForManga(history: List<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = mutableListOf<History>()
for ((url, lastRead) in history) {
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
// Check if history already in database and update
if (dbHistory != null) {
dbHistory.apply {
last_read = max(lastRead, dbHistory.last_read)
}
historyToBeUpdated.add(dbHistory)
} else {
// If not in database create
databaseHelper.getChapter(url).executeAsBlocking()?.let {
val historyToAdd = History.create(it).apply {
last_read = lastRead
}
historyToBeUpdated.add(historyToAdd)
}
}
}
databaseHelper.updateHistoryLastRead(historyToBeUpdated).executeAsBlocking()
}
/**
* Restores the sync of a manga.
*
* @param manga the manga whose sync have to be restored.
* @param tracks the track list to restore.
*/
internal fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
// Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! }
// Get tracks from database
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
val trackToUpdate = mutableListOf<Track>()
tracks.forEach { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged) {
var isInDatabase = false
for (dbTrack in dbTracks) {
if (track.sync_id == dbTrack.sync_id) {
// The sync is already in the db, only update its fields
if (track.media_id != dbTrack.media_id) {
dbTrack.media_id = track.media_id
}
if (track.library_id != dbTrack.library_id) {
dbTrack.library_id = track.library_id
}
dbTrack.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
isInDatabase = true
trackToUpdate.add(dbTrack)
break
}
}
if (!isInDatabase) {
// Insert new sync. Let the db assign the id
track.id = null
trackToUpdate.add(track)
}
}
}
// Update database
if (trackToUpdate.isNotEmpty()) {
databaseHelper.insertTracks(trackToUpdate).executeAsBlocking()
}
}
/**
* Restore the chapters for manga if chapters already in database
*
* @param manga manga of chapters
* @param chapters list containing chapters that get restored
* @return boolean answering if chapter fetch is not needed
*/
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>): Boolean {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
// Return if fetch is needed
if (dbChapters.isEmpty() || dbChapters.size < chapters.size) {
return false
}
chapters.forEach { chapter ->
val pos = dbChapters.indexOfFirst { it.url == chapter.url }
if (pos != -1) {
val dbChapter = dbChapters[pos]
chapter.id = dbChapter.id
chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) {
chapter.read = dbChapter.read
chapter.last_page_read = dbChapter.last_page_read
} else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) {
chapter.last_page_read = dbChapter.last_page_read
}
if (!chapter.bookmark && dbChapter.bookmark) {
chapter.bookmark = dbChapter.bookmark
}
}
}
// Filter the chapters that couldn't be found.
chapters.filter { it.id != null }
chapters.map { it.manga_id = manga.id }
updateChapters(chapters)
return true
}
internal fun restoreChaptersForMangaOffline(manga: Manga, chapters: List<Chapter>) {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
chapters.forEach { chapter ->
val pos = dbChapters.indexOfFirst { it.url == chapter.url }
if (pos != -1) {
val dbChapter = dbChapters[pos]
chapter.id = dbChapter.id
chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) {
chapter.read = dbChapter.read
chapter.last_page_read = dbChapter.last_page_read
} else if (chapter.last_page_read == 0 && dbChapter.last_page_read != 0) {
chapter.last_page_read = dbChapter.last_page_read
}
if (!chapter.bookmark && dbChapter.bookmark) {
chapter.bookmark = dbChapter.bookmark
}
}
}
chapters.map { it.manga_id = manga.id }
updateChapters(chapters.filter { it.id != null })
insertChapters(chapters.filter { it.id == null })
}
// SY -->
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
val currentSavedSearches = preferences.savedSearches().get().map {
val sourceId = it.substringBefore(':').toLong()
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
BackupSavedSearch(
content.name,
content.query,
content.filters.toString(),
sourceId
)
}
preferences.savedSearches()
.set(
(
backupSavedSearches.filter { backupSavedSearch -> currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source } }
.map {
"${it.source}:" + Json.encodeToString(
JsonSavedSearch(
it.name,
it.query,
Json.decodeFromString(it.filterList)
)
)
} + preferences.savedSearches().get()
)
.toSet()
)
}
/**
* Restore the categories from Json
*
* @param manga the merge manga for the references
* @param backupMergedMangaReferences the list of backup manga references for the merged manga
*/
internal fun restoreMergedMangaReferencesForManga(manga: Manga, backupMergedMangaReferences: List<BackupMergedMangaReference>) {
// Get merged manga references from file and from db
val dbMergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
// Iterate over them
backupMergedMangaReferences.forEach { backupMergedMangaReference ->
// Used to know if the merged manga reference is already in the db
var found = false
for (dbMergedMangaReference in dbMergedMangaReferences) {
// If the backupMergedMangaReference is already in the db, assign the id to the file's backupMergedMangaReference
// and do nothing
if (backupMergedMangaReference.mergeUrl == dbMergedMangaReference.mergeUrl && backupMergedMangaReference.mangaUrl == dbMergedMangaReference.mangaUrl) {
found = true
break
}
}
// If the backupMergedMangaReference isn't in the db, remove the id and insert a new backupMergedMangaReference
// Store the inserted id in the backupMergedMangaReference
if (!found) {
// Let the db assign the id
val mergedManga = databaseHelper.getManga(backupMergedMangaReference.mangaUrl, backupMergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach
val mergedMangaReference = backupMergedMangaReference.getMergedMangaReference()
mergedMangaReference.mergeId = manga.id
mergedMangaReference.mangaId = mergedManga.id
databaseHelper.insertMergedManga(mergedMangaReference).executeAsBlocking()
}
}
}
internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
manga.id?.let { mangaId ->
databaseHelper.getFlatMetadataForManga(mangaId).executeAsBlocking().let {
if (it == null) {
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId)
databaseHelper.insertFlatMetadata(flatMetadata).await()
}
}
}
}
// SY <--
}
@@ -0,0 +1,257 @@
package eu.kanade.tachiyomi.data.backup.full
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupFlatMetadata
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupMergedMangaReference
import eu.kanade.tachiyomi.data.backup.full.models.BackupSavedSearch
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.all.MergedSource
import exh.EXHMigrations
import exh.MERGED_SOURCE_ID
import kotlinx.serialization.ExperimentalSerializationApi
import okio.buffer
import okio.gzip
import okio.source
import rx.Observable
import java.util.Date
@OptIn(ExperimentalSerializationApi::class)
class FullBackupRestore(context: Context, notifier: BackupNotifier, private val online: Boolean) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
override fun performRestore(uri: Uri): Boolean {
// SY -->
throttleManager.resetThrottle()
// SY <--
backupManager = FullBackupManager(context)
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
restoreAmount = backup.backupManga.size + 1 /* SY --> */ + 1 /* SY <-- */ // +1 for categories, +1 for saved searches
// Restore categories
if (backup.backupCategories.isNotEmpty()) {
restoreCategories(backup.backupCategories)
}
// SY -->
if (backup.backupSavedSearches.isNotEmpty()) {
restoreSavedSearches(backup.backupSavedSearches)
}
// SY <--
// Store source mapping for error messages
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
// Restore individual manga, sort by merged source so that merged source manga go last and merged references get the proper ids
backup.backupManga /* SY --> */.sortedBy { it.source == MERGED_SOURCE_ID } /* SY <-- */.forEach {
if (job?.isActive != true) {
return false
}
restoreManga(it, backup.backupCategories, online)
}
return true
}
private fun restoreCategories(backupCategories: List<BackupCategory>) {
db.inTransaction {
backupManager.restoreCategories(backupCategories)
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
}
// SY -->
private fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
backupManager.restoreSavedSearches(backupSavedSearches)
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.saved_searches))
}
// SY <--
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) {
var manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories
val history = backupManga.history
val tracks = backupManga.getTrackingImpl()
// SY -->
val mergedMangaReferences = backupManga.mergedMangaReferences
val flatMetadata = backupManga.flatMetadata
// SY <--
// SY -->
manga = EXHMigrations.migrateBackupEntry(manga)
// SY <--
try {
val source = backupManager.sourceManager.get(manga.source)
if (source != null || !online) {
restoreMangaData(manga, source, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
} else {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} - ${context.getString(R.string.source_not_found_name, sourceName)}")
}
} catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}")
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
}
/**
* Returns a manga restore observable
*
* @param manga manga data from json
* @param source source to get manga data from
* @param chapters chapters data from json
* @param categories categories data from json
* @param history history data from json
* @param tracks tracking data from json
*/
private fun restoreMangaData(
manga: Manga,
source: Source?,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
) {
val dbManga = backupManager.getMangaFromDatabase(manga)
db.inTransaction {
if (dbManga == null) {
// Manga not in database
restoreMangaFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
} else { // Manga in database
// Copy information from manga already in database
backupManager.restoreMangaNoFetch(manga, dbManga)
// Fetch rest of manga information
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
}
}
}
/**
* [Observable] that fetches manga information
*
* @param manga manga that needs updating
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreMangaFetch(
source: Source?,
manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
) {
backupManager.restoreMangaFetchObservable(source, manga, online)
.doOnError {
errors.add(Date() to "${manga.title} - ${it.message}")
}
.filter { it.id != null }
.flatMap {
if (online && source != null) {
// SY -->
if (source !is MergedSource) {
chapterFetchObservable(source, it, chapters)
// Convert to the manga that contains new chapters.
.map { manga }
} else {
Observable.just(manga)
}
// SY <--
} else {
backupManager.restoreChaptersForMangaOffline(it, chapters)
Observable.just(manga)
}
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
}
.flatMap {
trackingFetchObservable(it, tracks)
}
.subscribe()
}
private fun restoreMangaNoFetch(
source: Source?,
backupManga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
) {
Observable.just(backupManga)
.flatMap { manga ->
if (online && source != null) {
if (/* SY --> */ source !is MergedSource && /* SY <-- */ !backupManager.restoreChaptersForManga(manga, chapters)) {
chapterFetchObservable(source, manga, chapters)
.map { manga }
} else {
Observable.just(manga)
}
} else {
backupManager.restoreChaptersForMangaOffline(manga, chapters)
Observable.just(manga)
}
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
}
.flatMap { manga ->
trackingFetchObservable(manga, tracks)
}
.subscribe()
}
private fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>, mergedMangaReferences: List<BackupMergedMangaReference>, flatMetadata: BackupFlatMetadata?) {
// Restore categories
backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
// Restore history
backupManager.restoreHistoryForManga(history)
// Restore tracking
backupManager.restoreTrackForManga(manga, tracks)
// SY -->
// Restore merged manga references if its a merged manga
backupManager.restoreMergedMangaReferencesForManga(manga, mergedMangaReferences)
// Restore flat metadata for metadata sources
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) }
// SY <--
}
}
@@ -0,0 +1,49 @@
package eu.kanade.tachiyomi.data.backup.full
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import kotlinx.serialization.ExperimentalSerializationApi
import okio.buffer
import okio.gzip
import okio.source
@OptIn(ExperimentalSerializationApi::class)
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
/**
* Checks for critical backup file data.
*
* @throws Exception if manga cannot be found.
* @return List of missing sources or missing trackers.
*/
override fun validate(context: Context, uri: Uri): Results {
val backupManager = FullBackupManager(context)
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
if (backup.backupManga.isEmpty()) {
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
}
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
val missingSources = sources
.filter { sourceManager.get(it.key) == null }
.values
.sorted()
val trackers = backup.backupManga
.flatMap { it.tracking }
.map { it.syncId }
.distinct()
val missingTrackers = trackers
.mapNotNull { trackManager.getService(it) }
.filter { !it.isLogged }
.map { it.name }
.sorted()
return Results(missingSources, missingTrackers)
}
}
@@ -0,0 +1,14 @@
package eu.kanade.tachiyomi.data.backup.full.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>,
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
// Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var backupSources: List<BackupSource> = emptyList(),
// SY specific values
@ProtoNumber(600) var backupSavedSearches: List<BackupSavedSearch> = emptyList()
)
@@ -0,0 +1,37 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
class BackupCategory(
@ProtoNumber(1) var name: String,
@ProtoNumber(2) var order: Int = 0,
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
// Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var flags: Int = 0,
// SY specific values
@ProtoNumber(600) var mangaOrder: List<Long> = emptyList()
) {
fun getCategoryImpl(): CategoryImpl {
return CategoryImpl().apply {
name = this@BackupCategory.name
flags = this@BackupCategory.flags
order = this@BackupCategory.order
mangaOrder = this@BackupCategory.mangaOrder
}
}
companion object {
fun copyFrom(category: Category): BackupCategory {
return BackupCategory(
name = category.name,
order = category.order,
flags = category.flags,
mangaOrder = category.mangaOrder
)
}
}
}
@@ -0,0 +1,56 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupChapter(
// in 1.x some of these values have different names
// url is called key in 1.x
@ProtoNumber(1) var url: String,
@ProtoNumber(2) var name: String,
@ProtoNumber(3) var scanlator: String? = null,
@ProtoNumber(4) var read: Boolean = false,
@ProtoNumber(5) var bookmark: Boolean = false,
// lastPageRead is called progress in 1.x
@ProtoNumber(6) var lastPageRead: Int = 0,
@ProtoNumber(7) var dateFetch: Long = 0,
@ProtoNumber(8) var dateUpload: Long = 0,
// chapterNumber is called number is 1.x
@ProtoNumber(9) var chapterNumber: Float = 0F,
@ProtoNumber(10) var sourceOrder: Int = 0,
) {
fun toChapterImpl(): ChapterImpl {
return ChapterImpl().apply {
url = this@BackupChapter.url
name = this@BackupChapter.name
chapter_number = this@BackupChapter.chapterNumber
scanlator = this@BackupChapter.scanlator
read = this@BackupChapter.read
bookmark = this@BackupChapter.bookmark
last_page_read = this@BackupChapter.lastPageRead
date_fetch = this@BackupChapter.dateFetch
date_upload = this@BackupChapter.dateUpload
source_order = this@BackupChapter.sourceOrder
}
}
companion object {
fun copyFrom(chapter: Chapter): BackupChapter {
return BackupChapter(
url = chapter.url,
name = chapter.name,
chapterNumber = chapter.chapter_number,
scanlator = chapter.scanlator,
read = chapter.read,
bookmark = chapter.bookmark,
lastPageRead = chapter.last_page_read,
dateFetch = chapter.date_fetch,
dateUpload = chapter.date_upload,
sourceOrder = chapter.source_order
)
}
}
}
@@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchMetadata
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchTag
import eu.kanade.tachiyomi.data.backup.full.models.metadata.BackupSearchTitle
import exh.metadata.metadata.base.FlatMetadata
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupFlatMetadata(
@ProtoNumber(1) var searchMetadata: BackupSearchMetadata,
@ProtoNumber(2) var searchTags: List<BackupSearchTag> = emptyList(),
@ProtoNumber(3) var searchTitles: List<BackupSearchTitle> = emptyList()
) {
fun getFlatMetadata(mangaId: Long): FlatMetadata {
return FlatMetadata(
metadata = searchMetadata.getSearchMetadata(mangaId),
tags = searchTags.map { it.getSearchTag(mangaId) },
titles = searchTitles.map { it.getSearchTitle(mangaId) }
)
}
companion object {
fun copyFrom(flatMetadata: FlatMetadata): BackupFlatMetadata {
return BackupFlatMetadata(
searchMetadata = BackupSearchMetadata.copyFrom(flatMetadata.metadata),
searchTags = flatMetadata.tags.map { BackupSearchTag.copyFrom(it) },
searchTitles = flatMetadata.titles.map { BackupSearchTitle.copyFrom(it) }
)
}
}
}
@@ -0,0 +1,12 @@
package eu.kanade.tachiyomi.data.backup.full.models
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object BackupFull {
fun getDefaultFilename(): String {
val date = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault()).format(Date())
return "tachiyomi_$date.proto.gz"
}
}
@@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.data.backup.full.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long
)
@@ -0,0 +1,90 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupManga(
// in 1.x some of these values have different names
@ProtoNumber(1) var source: Long,
// url is called key in 1.x
@ProtoNumber(2) var url: String,
@ProtoNumber(3) var title: String = "",
@ProtoNumber(4) var artist: String? = null,
@ProtoNumber(5) var author: String? = null,
@ProtoNumber(6) var description: String? = null,
@ProtoNumber(7) var genre: List<String> = emptyList(),
@ProtoNumber(8) var status: Int = 0,
// thumbnailUrl is called cover in 1.x
@ProtoNumber(9) var thumbnailUrl: String? = null,
// @ProtoNumber(10) val customCover: String = "", 1.x value, not used in 0.x
// @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x
// @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x
@ProtoNumber(13) var dateAdded: Long = 0,
@ProtoNumber(14) var viewer: Int = 0,
// @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x
@ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(),
@ProtoNumber(17) var categories: List<Int> = emptyList(),
@ProtoNumber(18) var tracking: List<BackupTracking> = emptyList(),
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
@ProtoNumber(100) var favorite: Boolean = true,
@ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
// SY specific values
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
@ProtoNumber(601) var flatMetadata: BackupFlatMetadata? = null
) {
fun getMangaImpl(): MangaImpl {
return MangaImpl().apply {
url = this@BackupManga.url
title = this@BackupManga.title
artist = this@BackupManga.artist
author = this@BackupManga.author
description = this@BackupManga.description
genre = this@BackupManga.genre.joinToString()
status = this@BackupManga.status
thumbnail_url = this@BackupManga.thumbnailUrl
favorite = this@BackupManga.favorite
source = this@BackupManga.source
date_added = this@BackupManga.dateAdded
viewer = this@BackupManga.viewer
chapter_flags = this@BackupManga.chapterFlags
}
}
fun getChaptersImpl(): List<ChapterImpl> {
return chapters.map {
it.toChapterImpl()
}
}
fun getTrackingImpl(): List<TrackImpl> {
return tracking.map {
it.getTrackingImpl()
}
}
companion object {
fun copyFrom(manga: Manga): BackupManga {
return BackupManga(
url = manga.url,
title = manga.title,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.getGenres() ?: emptyList(),
status = manga.status,
thumbnailUrl = manga.thumbnail_url,
favorite = manga.favorite,
source = manga.source,
dateAdded = manga.date_added,
viewer = manga.viewer,
chapterFlags = manga.chapter_flags
)
}
}
}
@@ -0,0 +1,51 @@
package eu.kanade.tachiyomi.data.backup.full.models
import exh.merged.sql.models.MergedMangaReference
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
/*
* SY merged manga backup class
*/
@Serializable
data class BackupMergedMangaReference(
@ProtoNumber(1) var isInfoManga: Boolean,
@ProtoNumber(2) var getChapterUpdates: Boolean,
@ProtoNumber(3) var chapterSortMode: Int,
@ProtoNumber(4) var chapterPriority: Int,
@ProtoNumber(5) var downloadChapters: Boolean,
@ProtoNumber(6) var mergeUrl: String,
@ProtoNumber(7) var mangaUrl: String,
@ProtoNumber(8) var mangaSourceId: Long
) {
fun getMergedMangaReference(): MergedMangaReference {
return MergedMangaReference(
isInfoManga = isInfoManga,
getChapterUpdates = getChapterUpdates,
chapterSortMode = chapterSortMode,
chapterPriority = chapterPriority,
downloadChapters = downloadChapters,
mergeUrl = mergeUrl,
mangaUrl = mangaUrl,
mangaSourceId = mangaSourceId,
mergeId = null,
mangaId = null,
id = null
)
}
companion object {
fun copyFrom(mergedMangaReference: MergedMangaReference): BackupMergedMangaReference {
return BackupMergedMangaReference(
isInfoManga = mergedMangaReference.isInfoManga,
getChapterUpdates = mergedMangaReference.getChapterUpdates,
chapterSortMode = mergedMangaReference.chapterSortMode,
chapterPriority = mergedMangaReference.chapterPriority,
downloadChapters = mergedMangaReference.downloadChapters,
mergeUrl = mergedMangaReference.mergeUrl,
mangaUrl = mergedMangaReference.mangaUrl,
mangaSourceId = mergedMangaReference.mangaSourceId
)
}
}
}
@@ -0,0 +1,15 @@
package eu.kanade.tachiyomi.data.backup.full.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
/*
* SY saved searches class
*/
@Serializable
data class BackupSavedSearch(
@ProtoNumber(1) val name: String,
@ProtoNumber(2) val query: String = "",
@ProtoNumber(3) val filterList: String = "",
@ProtoNumber(4) val source: Long = 0
)
@@ -0,0 +1,6 @@
package eu.kanade.tachiyomi.data.backup.full.models
import kotlinx.serialization.Serializer
@Serializer(forClass = Backup::class)
object BackupSerializer
@@ -0,0 +1,20 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.source.Source
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSource(
@ProtoNumber(0) var name: String = "",
@ProtoNumber(1) var sourceId: Long
) {
companion object {
fun copyFrom(source: Source): BackupSource {
return BackupSource(
name = source.name,
sourceId = source.id
)
}
}
}
@@ -0,0 +1,66 @@
package eu.kanade.tachiyomi.data.backup.full.models
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupTracking(
// in 1.x some of these values have different types or names
// syncId is called siteId in 1,x
@ProtoNumber(1) var syncId: Int,
// LibraryId is not null in 1.x
@ProtoNumber(2) var libraryId: Long,
@ProtoNumber(3) var mediaId: Int = 0,
// trackingUrl is called mediaUrl in 1.x
@ProtoNumber(4) var trackingUrl: String = "",
@ProtoNumber(5) var title: String = "",
// lastChapterRead is called last read, and it has been changed to a float in 1.x
@ProtoNumber(6) var lastChapterRead: Float = 0F,
@ProtoNumber(7) var totalChapters: Int = 0,
@ProtoNumber(8) var score: Float = 0F,
@ProtoNumber(9) var status: Int = 0,
// startedReadingDate is called startReadTime in 1.x
@ProtoNumber(10) var startedReadingDate: Long = 0,
// finishedReadingDate is called endReadTime in 1.x
@ProtoNumber(11) var finishedReadingDate: Long = 0,
) {
fun getTrackingImpl(): TrackImpl {
return TrackImpl().apply {
sync_id = this@BackupTracking.syncId
media_id = this@BackupTracking.mediaId
library_id = this@BackupTracking.libraryId
title = this@BackupTracking.title
// convert from float to int because of 1.x types
last_chapter_read = this@BackupTracking.lastChapterRead.toInt()
total_chapters = this@BackupTracking.totalChapters
score = this@BackupTracking.score
status = this@BackupTracking.status
started_reading_date = this@BackupTracking.startedReadingDate
finished_reading_date = this@BackupTracking.finishedReadingDate
tracking_url = this@BackupTracking.trackingUrl
}
}
companion object {
fun copyFrom(track: Track): BackupTracking {
return BackupTracking(
syncId = track.sync_id,
mediaId = track.media_id,
// forced not null so its compatible with 1.x backup system
libraryId = track.library_id!!,
title = track.title,
// convert to float for 1.x
lastChapterRead = track.last_chapter_read.toFloat(),
totalChapters = track.total_chapters,
score = track.score,
status = track.status,
startedReadingDate = track.started_reading_date,
finishedReadingDate = track.finished_reading_date,
trackingUrl = track.tracking_url
)
}
}
}
@@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata
import exh.metadata.sql.models.SearchMetadata
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSearchMetadata(
@ProtoNumber(1) var uploader: String? = null,
@ProtoNumber(2) var extra: String,
@ProtoNumber(3) var indexedExtra: String? = null,
@ProtoNumber(4) var extraVersion: Int
) {
fun getSearchMetadata(mangaId: Long): SearchMetadata {
return SearchMetadata(
mangaId = mangaId,
uploader = uploader,
extra = extra,
indexedExtra = indexedExtra,
extraVersion = extraVersion
)
}
companion object {
fun copyFrom(searchMetadata: SearchMetadata): BackupSearchMetadata {
return BackupSearchMetadata(
uploader = searchMetadata.uploader,
extra = searchMetadata.extra,
indexedExtra = searchMetadata.indexedExtra,
extraVersion = searchMetadata.extraVersion
)
}
}
}
@@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata
import exh.metadata.sql.models.SearchTag
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSearchTag(
@ProtoNumber(1) var namespace: String? = null,
@ProtoNumber(2) var name: String,
@ProtoNumber(3) var type: Int
) {
fun getSearchTag(mangaId: Long): SearchTag {
return SearchTag(
id = null,
mangaId = mangaId,
namespace = namespace,
name = name,
type = type
)
}
companion object {
fun copyFrom(searchTag: SearchTag): BackupSearchTag {
return BackupSearchTag(
namespace = searchTag.namespace,
name = searchTag.name,
type = searchTag.type
)
}
}
}
@@ -0,0 +1,29 @@
package eu.kanade.tachiyomi.data.backup.full.models.metadata
import exh.metadata.sql.models.SearchTitle
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSearchTitle(
@ProtoNumber(1) var title: String,
@ProtoNumber(2) var type: Int
) {
fun getSearchTitle(mangaId: Long): SearchTitle {
return SearchTitle(
id = null,
mangaId = mangaId,
title = title,
type = type
)
}
companion object {
fun copyFrom(searchTitle: SearchTitle): BackupSearchTitle {
return BackupSearchTitle(
title = searchTitle.title,
type = searchTitle.type
)
}
}
}
@@ -1,22 +1,19 @@
package eu.kanade.tachiyomi.data.backup
package eu.kanade.tachiyomi.data.backup.legacy
import android.content.Context
import android.net.Uri
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.jsonObject
import com.github.salomonbrys.kotson.obj
import com.github.salomonbrys.kotson.registerTypeAdapter
import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter
import com.github.salomonbrys.kotson.set
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
@@ -25,24 +22,23 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HIST
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES
import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
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.HISTORY
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.TRACK
import eu.kanade.tachiyomi.data.backup.models.DHistory
import eu.kanade.tachiyomi.data.backup.serializer.CategoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.ChapterTypeAdapter
import eu.kanade.tachiyomi.data.backup.serializer.HistoryTypeAdapter
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.database.DatabaseHelper
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CATEGORIES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CHAPTERS
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CURRENT_VERSION
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.EXTENSIONS
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.HISTORY
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGA
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MERGEDMANGAREFERENCES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.SAVEDSEARCHES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.TRACK
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.HistoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MangaTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.MergedMangaReferenceTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.TrackTypeAdapter
import eu.kanade.tachiyomi.data.database.models.CategoryImpl
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
@@ -53,46 +49,31 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.EXHSavedSearch
import eu.kanade.tachiyomi.util.lang.asObservable
import exh.MERGED_SOURCE_ID
import exh.eh.EHentaiThrottleManager
import exh.merged.sql.models.MergedMangaReference
import exh.util.asObservable
import exh.savedsearches.JsonSavedSearch
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Observable
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
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 LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
internal val databaseHelper: DatabaseHelper by injectLazy()
internal val sourceManager: SourceManager by injectLazy()
internal val trackManager: TrackManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
/**
* Version of parser
*/
var version: Int = version
var parserVersion: Int = version
private set
/**
* Json Parser
*/
var parser: Gson = initParser()
/**
@@ -101,12 +82,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @param version version of parser
*/
internal fun setVersion(version: Int) {
this.version = version
this.parserVersion = version
parser = initParser()
}
private fun initParser(): Gson = when (version) {
1 -> GsonBuilder().create()
private fun initParser(): Gson = when (parserVersion) {
2 ->
GsonBuilder()
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
@@ -118,7 +98,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
.registerTypeAdapter<MergedMangaReference>(MergedMangaReferenceTypeAdapter.build())
// SY <--
.create()
else -> throw Exception("Json version unknown")
else -> throw Exception("Unknown backup version")
}
/**
@@ -127,7 +107,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @param uri path of Uri
* @param isJob backup called from job
*/
fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? {
// Create root object
val root = JsonObject()
@@ -153,8 +133,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// SY <--
databaseHelper.inTransaction {
// Get manga from database
val mangas = getFavoriteManga() /* SY --> */ + getMergedManga() /* SY <-- */
val mangas = getFavoriteManga()/* SY --> */.filterNot { it.source == MERGED_SOURCE_ID } + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */
val extensions: MutableSet<String> = mutableSetOf()
@@ -179,46 +158,40 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
backupExtensionInfo(extensionEntries, extensions)
// SY -->
root[SAVEDSEARCHES] =
Injekt.get<PreferencesHelper>().eh_savedSearches().get().joinToString(separator = "***")
Injekt.get<PreferencesHelper>().savedSearches().get().joinToString(separator = "***")
backupMergedMangaReferences(mergedMangaReferenceEntries)
// SY <--
}
try {
// When BackupCreatorJob
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
dir = dir.createDirectory("automatic")
val file: UniFile = (
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Delete older backups
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
val newFile = dir.createFile(Backup.getDefaultFilename())
?: throw Exception("Couldn't create backup file")
newFile.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
// Create new file to place backup
dir.createFile(Backup.getDefaultFilename())
} else {
UniFile.fromUri(context, uri)
}
)
?: throw Exception("Couldn't create backup file")
return newFile.uri.toString()
} else {
val file = UniFile.fromUri(context, uri)
?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
return file.uri.toString()
file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
return file.uri.toString()
} catch (e: Exception) {
Timber.e(e)
throw e
@@ -341,41 +314,18 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
* @param manga manga that needs updating
* @return [Observable] that contains manga
*/
fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
override fun restoreChapterFetchObservable(source: Source, manga: Manga, chapters: List<Chapter>, throttleManager: EHentaiThrottleManager): Observable<Pair<List<Chapter>, List<Chapter>>> {
// SY -->
if (source is MergedSource) {
return if (source is MergedSource) {
val syncedChapters = runBlocking { source.fetchChaptersAndSync(manga, false) }
return syncedChapters.onEach { pair ->
syncedChapters.onEach { pair ->
if (pair.first.isNotEmpty()) {
chapters.forEach { it.manga_id = manga.id }
insertChapters(chapters)
updateChapters(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)
}
}
super.restoreChapterFetchObservable(source, manga, chapters, throttleManager)
}
}
@@ -541,32 +491,19 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
chapters.filter { it.id != null }
chapters.map { it.manga_id = manga.id }
insertChapters(chapters)
updateChapters(chapters)
return true
}
// SY -->
internal fun restoreSavedSearches(jsonSavedSearches: JsonElement) {
val backupSavedSearches = jsonSavedSearches.asString.split("***").toSet()
val filterSerializer = FilterSerializer()
val newSavedSearches = backupSavedSearches.mapNotNull {
try {
val id = it.substringBefore(':').toLong()
val content = JsonParser.parseString(it.substringAfter(':')).obj
val source = sourceManager.getOrStub(id)
if (source !is CatalogueSource) return@mapNotNull null
val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content["filters"].array)
Pair(
id,
EXHSavedSearch(
content["name"].string,
content["query"].string,
originalFilters
)
)
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
id to content
} catch (t: RuntimeException) {
// Load failed
Timber.e(t, "Failed to load saved search!")
@@ -577,24 +514,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
val currentSources = newSavedSearches.map { it.first }.toSet()
newSavedSearches += preferences.eh_savedSearches().get().mapNotNull {
newSavedSearches += preferences.savedSearches().get().mapNotNull {
try {
val id = it.substringBefore(':').toLong()
val content = JsonParser.parseString(it.substringAfter(':')).obj
if (id !in currentSources) return@mapNotNull null
val source = sourceManager.getOrStub(id)
if (source !is CatalogueSource) return@mapNotNull null
val originalFilters = source.getFilterList()
filterSerializer.deserialize(originalFilters, content["filters"].array)
Pair(
id,
EXHSavedSearch(
content["name"].string,
content["query"].string,
originalFilters
)
)
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
id to content
} catch (t: RuntimeException) {
// Load failed
Timber.e(t, "Failed to load saved search!")
@@ -603,23 +527,16 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
}
}.toMutableList()
val otherSerialized = preferences.eh_savedSearches().get().mapNotNull {
val otherSerialized = preferences.savedSearches().get().mapNotNull {
val sourceId = it.split(":")[0].toLongOrNull() ?: return@mapNotNull null
if (sourceId in currentSources) return@mapNotNull null
it
}
/*.filter {
!it.startsWith("${newSource.id}:")
}*/
val newSerialized = newSavedSearches.map {
"${it.first}:" + jsonObject(
"name" to it.second.name,
"query" to it.second.query,
"filters" to filterSerializer.serialize(it.second.filterList)
).toString()
"${it.first}:" + Json.encodeToString(it.second)
}
preferences.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
preferences.savedSearches().set((otherSerialized + newSerialized).toSet())
}
/**
@@ -652,7 +569,15 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
// 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
var mergedManga = if (mergedMangaReference.mergeUrl != lastMergeManga?.url) databaseHelper.getManga(mergedMangaReference.mergeUrl, MERGED_SOURCE_ID).executeAsBlocking() else lastMergeManga
if (mergedManga == null) {
mergedManga = Manga.create(MERGED_SOURCE_ID).apply {
url = mergedMangaReference.mergeUrl
title = context.getString(R.string.refresh_merge)
}
mergedManga.id = databaseHelper.insertManga(mergedManga).executeAsBlocking().insertedId()
}
val manga = databaseHelper.getManga(mergedMangaReference.mangaUrl, mergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach
lastMergeManga = mergedManga
@@ -665,45 +590,4 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
}
}
// SY <--
/**
* Returns manga
*
* @return [Manga], null if not found
*/
internal fun getMangaFromDatabase(manga: Manga): Manga? =
databaseHelper.getManga(manga.url, manga.source).executeAsBlocking()
/**
* Returns list containing manga from library
*
* @return [Manga] from library
*/
internal fun getFavoriteManga(): List<Manga> =
databaseHelper.getFavoriteMangas().executeAsBlocking()
internal fun getMergedManga(): List<Manga> =
databaseHelper.getMergedMangas().executeAsBlocking()
/**
* Inserts manga and returns id
*
* @return id of [Manga], null if not found
*/
internal fun insertManga(manga: Manga): Long? =
databaseHelper.insertManga(manga).executeAsBlocking().insertedId()
/**
* Inserts list of chapters
*/
private fun insertChapters(chapters: List<Chapter>) {
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
}
/**
* Return number of backups.
*
* @return number of backups selected by user
*/
fun numberOfBackups(): Int = preferences.numberOfBackups().get()
}
@@ -0,0 +1,241 @@
package eu.kanade.tachiyomi.data.backup.legacy
import android.content.Context
import android.net.Uri
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestore
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGAS
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.source.Source
import exh.EXHMigrations
import rx.Observable
import java.util.Date
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
override fun performRestore(uri: Uri): Boolean {
// SY -->
throttleManager.resetThrottle()
// SY <--
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser.parseReader(reader).asJsonObject
val version = json.get(Backup.VERSION)?.asInt ?: 1
backupManager = LegacyBackupManager(context, version)
val mangasJson = json.get(MANGAS).asJsonArray
restoreAmount = mangasJson.size() + 3 // +1 for categories, +1 for saved searches, +1 for merged manga references
// Restore categories
json.get(Backup.CATEGORIES)?.let { restoreCategories(it) }
// SY -->
json.get(Backup.SAVEDSEARCHES)?.let { restoreSavedSearches(it) }
json.get(Backup.MERGEDMANGAREFERENCES)?.let { restoreMergedMangaReferences(it) }
// SY <--
// Store source mapping for error messages
sourceMapping = LegacyBackupRestoreValidator.getSourceMapping(json)
// Restore individual manga
mangasJson.forEach {
if (job?.isActive != true) {
return false
}
restoreManga(it.asJsonObject)
}
return true
}
private fun restoreCategories(categoriesJson: JsonElement) {
db.inTransaction {
backupManager.restoreCategories(categoriesJson.asJsonArray)
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
}
// SY -->
private fun restoreSavedSearches(savedSearchesJson: JsonElement) {
backupManager.restoreSavedSearches(savedSearchesJson)
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.saved_searches))
}
private fun restoreMergedMangaReferences(mergedMangaReferencesJson: JsonElement) {
db.inTransaction {
backupManager.restoreMergedMangaReferences(mergedMangaReferencesJson.asJsonArray)
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.merged_references))
}
// SY <--
private fun restoreManga(mangaJson: JsonObject) {
/* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson<MangaImpl>(
mangaJson.get(
Backup.MANGA
)
)
val chapters = backupManager.parser.fromJson<List<ChapterImpl>>(
mangaJson.get(Backup.CHAPTERS)
?: JsonArray()
)
val categories = backupManager.parser.fromJson<List<String>>(
mangaJson.get(Backup.CATEGORIES)
?: JsonArray()
)
val history = backupManager.parser.fromJson<List<DHistory>>(
mangaJson.get(Backup.HISTORY)
?: JsonArray()
)
val tracks = backupManager.parser.fromJson<List<TrackImpl>>(
mangaJson.get(Backup.TRACK)
?: JsonArray()
)
// EXH -->
manga = EXHMigrations.migrateBackupEntry(manga)
// <-- EXH
try {
val source = backupManager.sourceManager.get(manga.source)
if (source != null) {
restoreMangaData(manga, source, chapters, categories, history, tracks)
} else {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} - ${context.getString(R.string.source_not_found_name, sourceName)}")
}
} catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}")
}
restoreProgress += 1
showRestoreProgress(restoreProgress, restoreAmount, manga.title)
}
/**
* Returns a manga restore observable
*
* @param manga manga data from json
* @param source source to get manga data from
* @param chapters chapters data from json
* @param categories categories data from json
* @param history history data from json
* @param tracks tracking data from json
*/
private fun restoreMangaData(
manga: Manga,
source: Source,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
val dbManga = backupManager.getMangaFromDatabase(manga)
db.inTransaction {
if (dbManga == null) {
// Manga not in database
restoreMangaFetch(source, manga, chapters, categories, history, tracks)
} else { // Manga in database
// Copy information from manga already in database
backupManager.restoreMangaNoFetch(manga, dbManga)
// Fetch rest of manga information
restoreMangaNoFetch(source, manga, chapters, categories, history, tracks)
}
}
}
/**
* [Observable] that fetches manga information
*
* @param manga manga that needs updating
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreMangaFetch(
source: Source,
manga: Manga,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
backupManager.restoreMangaFetchObservable(source, manga)
.onErrorReturn {
errors.add(Date() to "${manga.title} - ${it.message}")
manga
}
.filter { it.id != null }
.flatMap {
chapterFetchObservable(source, it, chapters)
// Convert to the manga that contains new chapters.
.map { manga }
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks)
}
.flatMap {
trackingFetchObservable(it, tracks)
}
.subscribe()
}
private fun restoreMangaNoFetch(
source: Source,
backupManga: Manga,
chapters: List<Chapter>,
categories: List<String>,
history: List<DHistory>,
tracks: List<Track>
) {
Observable.just(backupManga)
.flatMap { manga ->
if (!backupManager.restoreChaptersForManga(manga, chapters)) {
chapterFetchObservable(source, manga, chapters)
.map { manga }
} else {
Observable.just(manga)
}
}
.doOnNext {
restoreExtraForManga(it, categories, history, tracks)
}
.flatMap { manga ->
trackingFetchObservable(manga, tracks)
}
.subscribe()
}
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
// Restore categories
backupManager.restoreCategoriesForManga(manga, categories)
// Restore history
backupManager.restoreHistoryForManga(history)
// Restore tracking
backupManager.restoreTrackForManga(manga, tracks)
}
}
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup
package eu.kanade.tachiyomi.data.backup.legacy
import android.content.Context
import android.net.Uri
@@ -6,23 +6,17 @@ import com.google.gson.JsonObject
import com.google.gson.JsonParser
import com.google.gson.stream.JsonReader
import eu.kanade.tachiyomi.R
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 {
private val sourceManager: SourceManager by injectLazy()
private val trackManager: TrackManager by injectLazy()
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
/**
* Checks for critical backup file data.
*
* @throws Exception if version or manga cannot be found.
* @return List of missing sources or missing trackers.
*/
fun validate(context: Context, uri: Uri): Results {
override fun validate(context: Context, uri: Uri): Results {
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser.parseReader(reader).asJsonObject
@@ -57,16 +51,16 @@ object BackupRestoreValidator {
return Results(missingSources, missingTrackers)
}
fun getSourceMapping(json: JsonObject): Map<Long, String> {
val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap()
companion object {
fun getSourceMapping(json: JsonObject): Map<Long, String> {
val extensionsMapping = json.get(Backup.EXTENSIONS) ?: return emptyMap()
return extensionsMapping.asJsonArray
.map {
val items = it.asString.split(":")
items[0].toLong() to items[1]
}
.toMap()
return extensionsMapping.asJsonArray
.map {
val items = it.asString.split(":")
items[0].toLong() to items[1]
}
.toMap()
}
}
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
}
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.models
package eu.kanade.tachiyomi.data.backup.legacy.models
import java.text.SimpleDateFormat
import java.util.Date
@@ -17,6 +17,7 @@ object Backup {
const val EXTENSIONS = "extensions"
const val HISTORY = "history"
const val VERSION = "version"
// SY -->
const val SAVEDSEARCHES = "savedsearches"
const val MERGEDMANGAREFERENCES = "mergedmangareferences"
@@ -1,3 +1,3 @@
package eu.kanade.tachiyomi.data.backup.models
package eu.kanade.tachiyomi.data.backup.legacy.models
data class DHistory(val url: String, val lastRead: Long)
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
@@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
import eu.kanade.tachiyomi.data.backup.models.DHistory
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
/**
* JSON Serializer used to write / read [DHistory] to / from json
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.backup.serializer
package eu.kanade.tachiyomi.data.backup.legacy.serializer
import com.github.salomonbrys.kotson.typeAdapter
import com.google.gson.TypeAdapter
@@ -56,10 +56,10 @@ class ChapterCache(private val context: Context) {
/** Cache class used for cache management. */
// --> EH
private var diskCache = setupDiskCache(prefs.eh_cacheSize().get().toLong())
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
init {
prefs.eh_cacheSize().asFlow()
prefs.cacheSize().asFlow()
.onEach {
// Save old cache for destruction later
val oldCache = diskCache
@@ -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.MangaQueries
import eu.kanade.tachiyomi.data.database.queries.TrackQueries
import exh.md.similar.sql.mappers.SimilarTypeMapping
import exh.md.similar.sql.models.MangaSimilar
import exh.md.similar.sql.queries.SimilarQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries
@@ -39,7 +42,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces.
*/
open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ {
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, SimilarQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME)
@@ -59,6 +62,7 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
.addTypeMapping(MangaSimilar::class.java, SimilarTypeMapping())
// SY <--
.build()
@@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable
import exh.md.similar.sql.tables.SimilarTable
import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable
@@ -24,7 +25,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/**
* Version of the database.
*/
const val DATABASE_VERSION = /* SY --> */ 4 /* SY <-- */
const val DATABASE_VERSION = /* SY --> */ 5 /* SY <-- */
}
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@@ -39,6 +40,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTagTable.createTableQuery)
execSQL(SearchTitleTable.createTableQuery)
execSQL(MergedTable.createTableQuery)
execSQL(SimilarTable.createTableQuery)
// SY <--
// DB indexes
@@ -55,6 +57,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTitleTable.createMangaIdIndexQuery)
execSQL(SearchTitleTable.createTitleIndexQuery)
execSQL(MergedTable.createIndexQuery)
execSQL(SimilarTable.createMangaIdIndexQuery)
// SY <--
}
@@ -66,11 +69,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(MangaTable.addDateAdded)
db.execSQL(MangaTable.backfillDateAdded)
}
if (oldVersion < 12) {
if (oldVersion < 4) {
db.execSQL(MergedTable.dropTableQuery)
db.execSQL(MergedTable.createTableQuery)
db.execSQL(MergedTable.createIndexQuery)
}
if (oldVersion < 5) {
db.execSQL(SimilarTable.createTableQuery)
db.execSQL(SimilarTable.createMangaIdIndexQuery)
}
}
override fun onConfigure(db: SupportSQLiteDatabase) {
@@ -58,7 +58,7 @@ class CategoryGetResolver : DefaultGetResolver<Category>() {
// SY -->
val orderString = cursor.getString(cursor.getColumnIndex(COL_MANGA_ORDER))
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() } ?: emptyList()
mangaOrder = orderString?.split("/")?.mapNotNull { it.toLongOrNull() }.orEmpty()
// SY <--
}
}
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.source.model.MangaInfo
interface Manga : SManga {
@@ -104,3 +105,16 @@ interface Manga : SManga {
}
}
}
fun Manga.toMangaInfo(): MangaInfo {
return MangaInfo(
artist = this.artist ?: "",
author = this.author ?: "",
cover = this.thumbnail_url ?: "",
description = this.description ?: "",
genres = this.getGenres() ?: emptyList(),
key = this.url,
status = this.status,
title = this.title
)
}
@@ -18,41 +18,25 @@ interface HistoryQueries : DbProvider {
*/
fun insertHistory(history: History) = db.put().`object`(history).prepare()
// SY -->
/**
* Returns history of recent manga containing last read chapter
* @param date recent date range
* @param limit the limit of manga to grab
* @param offset offset the db by
* @param search what to search in the db history
*/
fun getRecentManga(date: Date, offset: Int = 0, search: String = "") = db.get()
fun getRecentManga(date: Date, limit: Int = 25, offset: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentMangasQuery(offset, search))
.args(date.time)
.query(getRecentMangasQuery(search))
.args(date.time, limit, offset)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
/**
* Returns history of recent manga containing last read chapter in 25s
* @param date recent date range
* @offset offset the db by
*/
fun getRecentMangaLimit(date: Date, limit: Int = 0, search: String = "") = db.get()
.listOfObjects(MangaChapterHistory::class.java)
.withQuery(
RawQuery.builder()
.query(getRecentMangasLimitQuery(limit, search))
.args(date.time)
.observesTables(HistoryTable.TABLE)
.build()
)
.withGetResolver(MangaChapterHistoryGetResolver.INSTANCE)
.prepare()
// SY <--
fun getHistoryByMangaId(mangaId: Long) = db.get()
.listOfObjects(History::class.java)
.withQuery(
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
@@ -87,6 +88,11 @@ interface MangaQueries : DbProvider {
.`object`(manga)
.withPutResolver(MangaInfoPutResolver(true))
.prepare()
fun updateMangaMigrate(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaMigrationPutResolver())
.prepare()
// SY <--
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
@@ -98,6 +104,11 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaFlagsPutResolver())
.prepare()
fun updateFlags(mangas: List<Manga>) = db.put()
.objects(mangas)
.withPutResolver(MangaFlagsPutResolver(true))
.prepare()
fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver())
@@ -136,36 +136,8 @@ fun getRecentsQuery() =
* The max_last_read table contains the most recent chapters grouped by manga
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
* @return return limit is 25
*/
// SY -->
fun getRecentMangasQuery(offset: Int = 0, search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
JOIN (
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID},${Chapter.TABLE}.${Chapter.COL_ID} as ${History.COL_CHAPTER_ID}, MAX(${History.TABLE}.${History.COL_LAST_READ}) as ${History.COL_LAST_READ}
FROM ${Chapter.TABLE} JOIN ${History.TABLE}
ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
GROUP BY ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}) AS max_last_read
ON ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} = max_last_read.${Chapter.COL_MANGA_ID}
WHERE ${History.TABLE}.${History.COL_LAST_READ} > ? AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
LIMIT 25 OFFSET $offset
"""
/**
* Query to get the recently read chapters of manga from the library up to a date.
* The max_last_read table contains the most recent chapters grouped by manga
* The select statement returns all information of chapters that have the same id as the chapter in max_last_read
* and are read after the given time period
*/
fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
fun getRecentMangasQuery(search: String = "") =
"""
SELECT ${Manga.TABLE}.${Manga.COL_URL} as mangaUrl, ${Manga.TABLE}.*, ${Chapter.TABLE}.*, ${History.TABLE}.*
FROM ${Manga.TABLE}
@@ -183,9 +155,8 @@ fun getRecentMangasLimitQuery(limit: Int = 25, search: String = "") =
AND max_last_read.${History.COL_CHAPTER_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID}
AND lower(${Manga.TABLE}.${Manga.COL_TITLE}) LIKE '%$search%'
ORDER BY max_last_read.${History.COL_LAST_READ} DESC
LIMIT $limit
LIMIT ? OFFSET ?
"""
// SY <--
fun getHistoryByMangaId() =
"""
@@ -25,7 +25,8 @@ class MangaFavoritePutResolver : PutResolver<Manga>() {
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
fun mapToContentValues(manga: Manga) = ContentValues(2).apply {
put(MangaTable.COL_FAVORITE, manga.favorite)
put(MangaTable.COL_DATE_ADDED, manga.date_added)
}
}
@@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaFlagsPutResolver : PutResolver<Manga>() {
class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
@@ -19,11 +19,21 @@ class MangaFlagsPutResolver : PutResolver<Manga>() {
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToUpdateQuery(manga: Manga): UpdateQuery {
val builder = UpdateQuery.builder()
return if (updateAll) {
builder
.table(MangaTable.TABLE)
.build()
} else {
builder
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
}
}
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
@@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaMigrationPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(5).apply {
put(MangaTable.COL_FAVORITE, manga.favorite)
put(MangaTable.COL_DATE_ADDED, manga.date_added)
put(MangaTable.COL_TITLE, manga.title)
put(MangaTable.COL_CHAPTER_FLAGS, manga.chapter_flags)
put(MangaTable.COL_VIEWER, manga.viewer)
}
}
@@ -25,6 +25,7 @@ object CategoryTable {
$COL_FLAGS INTEGER NOT NULL,
$COL_MANGA_ORDER TEXT NOT NULL
)"""
// SY -->
val addMangaOrder: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_MANGA_ORDER TEXT"
@@ -130,7 +130,7 @@ class DownloadCache(
.orEmpty()
.associate { it.name to SourceDirectory(it) }
.mapNotNullKeys { entry ->
onlineSources.find { provider.getSourceDirName(it).toLowerCase() == entry.key?.toLowerCase() }?.id
onlineSources.find { provider.getSourceDirName(it).equals(entry.key, ignoreCase = true) }?.id
}
rootDir.files = sourceDirs
@@ -173,6 +173,17 @@ class DownloadManager(/* SY private */ val context: Context) {
return cache.isChapterDownloaded(chapter, manga, skipCache)
}
/**
* Returns the download from queue if the chapter is queued for download
* else it will return null which means that the chapter is not queued for download
*
* @param chapter the chapter to check.
*/
fun getChapterDownloadOrNull(chapter: Chapter): Download? {
return downloader.queue
.firstOrNull { it.chapter.id == chapter.id && it.chapter.manga_id == chapter.manga_id }
}
/**
* Returns the amount of downloaded chapters for a manga.
*
@@ -218,13 +229,13 @@ class DownloadManager(/* SY private */ val context: Context) {
* return the list of all manga folders
*/
fun getMangaFolders(source: Source): List<UniFile> {
return provider.findSourceDir(source)?.listFiles()?.toList() ?: emptyList()
return provider.findSourceDir(source)?.listFiles()?.toList().orEmpty()
}
/**
* Deletes the directories of chapters that were read or have no match
*
* @param chapters the list of chapters to delete.
* @param allChapters the list of chapters to delete.
* @param manga the manga of the chapters.
* @param source the source of the chapters.
*/
@@ -2,10 +2,12 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
/**
@@ -15,10 +17,7 @@ import uy.kohesive.injekt.injectLazy
*/
class DownloadPendingDeleter(context: Context) {
/**
* Gson instance to encode and decode chapters.
*/
private val gson by injectLazy<Gson>()
private val json: Json by injectLazy()
/**
* Preferences used to store the list of chapters to delete.
@@ -53,7 +52,7 @@ class DownloadPendingDeleter(context: Context) {
val existingEntry = preferences.getString(manga.id!!.toString(), null)
if (existingEntry != null) {
// Existing entry found on preferences, decode json and add the new chapter
val savedEntry = gson.fromJson<Entry>(existingEntry)
val savedEntry = json.decodeFromString<Entry>(existingEntry)
// Append new chapters
val newChapters = savedEntry.chapters.addUniqueById(chapters)
@@ -69,7 +68,7 @@ class DownloadPendingDeleter(context: Context) {
}
// Save current state
val json = gson.toJson(newEntry)
val json = json.encodeToString(newEntry)
preferences.edit {
putString(newEntry.manga.id.toString(), json)
}
@@ -90,8 +89,8 @@ class DownloadPendingDeleter(context: Context) {
}
lastAddedEntry = null
return entries.associate { entry ->
entry.manga.toModel() to entry.chapters.map { it.toModel() }
return entries.associate { (chapters, manga) ->
manga.toModel() to chapters.map { it.toModel() }
}
}
@@ -101,7 +100,7 @@ class DownloadPendingDeleter(context: Context) {
private fun decodeAll(): List<Entry> {
return preferences.all.values.mapNotNull { rawEntry ->
try {
(rawEntry as? String)?.let { gson.fromJson<Entry>(it) }
(rawEntry as? String)?.let { json.decodeFromString<Entry>(it) }
} catch (e: Exception) {
null
}
@@ -124,6 +123,7 @@ class DownloadPendingDeleter(context: Context) {
/**
* Class used to save an entry of chapters with their manga into preferences.
*/
@Serializable
private data class Entry(
val chapters: List<ChapterEntry>,
val manga: MangaEntry
@@ -132,6 +132,7 @@ class DownloadPendingDeleter(context: Context) {
/**
* Class used to save an entry for a chapter into preferences.
*/
@Serializable
private data class ChapterEntry(
val id: Long,
val url: String,
@@ -142,6 +143,7 @@ class DownloadPendingDeleter(context: Context) {
/**
* Class used to save an entry for a manga into preferences.
*/
@Serializable
private data class MangaEntry(
val id: Long,
val url: String,
@@ -7,9 +7,9 @@ import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkInfo.State.CONNECTED
import android.net.NetworkInfo.State.DISCONNECTED
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import com.github.pwittchen.reactivenetwork.library.Connectivity
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import com.jakewharton.rxrelay.BehaviorRelay
@@ -47,11 +47,7 @@ class DownloadService : Service() {
*/
fun start(context: Context) {
val intent = Intent(context, DownloadService::class.java)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
ContextCompat.startForegroundService(context, intent)
}
/**
@@ -2,12 +2,15 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.content.edit
import com.google.gson.Gson
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
/**
@@ -25,11 +28,7 @@ class DownloadStore(
*/
private val preferences = context.getSharedPreferences("active_downloads", Context.MODE_PRIVATE)
/**
* Gson instance to serialize/deserialize downloads.
*/
private val gson: Gson by injectLazy()
private val json: Json by injectLazy()
private val db: DatabaseHelper by injectLazy()
/**
@@ -111,7 +110,7 @@ class DownloadStore(
*/
private fun serialize(download: Download): String {
val obj = DownloadObject(download.manga.id!!, download.chapter.id!!, counter++)
return gson.toJson(obj)
return json.encodeToString(obj)
}
/**
@@ -121,7 +120,7 @@ class DownloadStore(
*/
private fun deserialize(string: String): DownloadObject? {
return try {
gson.fromJson(string, DownloadObject::class.java)
json.decodeFromString<DownloadObject>(string)
} catch (e: Exception) {
null
}
@@ -134,5 +133,6 @@ class DownloadStore(
* @param chapterId the id of the chapter.
* @param order the order of the download in the queue.
*/
@Serializable
data class DownloadObject(val mangaId: Long, val chapterId: Long, val order: Int)
}
@@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil
import exh.isEhBasedSource
import kotlinx.coroutines.async
import okhttp3.Response
import rx.Observable
@@ -389,7 +390,19 @@ class Downloader(
private fun downloadImage(page: Page, source: HttpSource, tmpDir: UniFile, filename: String): Observable<UniFile> {
page.status = Page.DOWNLOAD_IMAGE
page.progress = 0
return source.fetchImage(page)
return /* SY --> If the source is E-Hentai request a new page if null */ Observable.just(Unit)
.flatMap {
if (page.imageUrl == null && source.isEhBasedSource()) {
source.fetchImageUrl(page)
} else Observable.just(null)
}
.doOnNext { imageUrl ->
if (imageUrl != null) page.imageUrl = imageUrl
}
.flatMap {
source.fetchImage(page)
}
// SY <--
.map { response ->
val file = tmpDir.createFile("$filename.tmp")
try {
@@ -399,6 +412,9 @@ class Downloader(
} catch (e: Exception) {
response.close()
file.delete()
// SY --> E-Hentai sometimes has dead pages, so we request a new one if it fails
if (source.isEhBasedSource()) page.imageUrl = null
// SY <--
throw e
}
file
@@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.data.library
import android.content.Context
import com.github.salomonbrys.kotson.nullLong
import com.github.salomonbrys.kotson.nullString
import com.github.salomonbrys.kotson.set
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.util.Scanner
@@ -28,26 +26,23 @@ class CustomMangaManager(val context: Context) {
if (!editJson.exists() || !editJson.isFile) return
val json = try {
Gson().fromJson(
Scanner(editJson).useDelimiter("\\Z").next(),
JsonObject::class.java
Json.decodeFromString<MangaList>(
Scanner(editJson).useDelimiter("\\Z").next()
)
} catch (e: Exception) {
null
} ?: return
val mangasJson = json.get("mangas").asJsonArray ?: return
customMangaMap = mangasJson.mapNotNull { element ->
val mangaObject = element.asJsonObject ?: return@mapNotNull null
val id = mangaObject["id"]?.nullLong ?: return@mapNotNull null
val mangasJson = json.mangas ?: return
customMangaMap = mangasJson.mapNotNull { mangaJson ->
val id = mangaJson.id ?: return@mapNotNull null
val manga = MangaImpl().apply {
this.id = id
title = mangaObject["title"]?.nullString ?: ""
author = mangaObject["author"]?.nullString
artist = mangaObject["artist"]?.nullString
description = mangaObject["description"]?.nullString
genre = mangaObject["genre"]?.asJsonArray?.mapNotNull { it.nullString }
?.joinToString(", ")
title = mangaJson.title ?: ""
author = mangaJson.author
artist = mangaJson.artist
description = mangaJson.description
genre = mangaJson.genre?.joinToString(", ")
}
id to manga
}.toMap().toMutableMap()
@@ -55,9 +50,9 @@ class CustomMangaManager(val context: Context) {
fun saveMangaInfo(manga: MangaJson) {
if (manga.title == null && manga.author == null && manga.artist == null && manga.description == null && manga.genre == null) {
customMangaMap.remove(manga.id)
customMangaMap.remove(manga.id!!)
} else {
customMangaMap[manga.id] = MangaImpl().apply {
customMangaMap[manga.id!!] = MangaImpl().apply {
id = manga.id
title = manga.title ?: ""
author = manga.author
@@ -72,34 +67,35 @@ class CustomMangaManager(val context: Context) {
private fun saveCustomInfo() {
val jsonElements = customMangaMap.values.map { it.toJson() }
if (jsonElements.isNotEmpty()) {
val gson = GsonBuilder().create()
val root = JsonObject()
val mangaEntries = gson.toJsonTree(jsonElements)
root["mangas"] = mangaEntries
editJson.delete()
editJson.writeText(gson.toJson(root))
editJson.writeText(Json.encodeToString(MangaList(jsonElements)))
}
}
fun Manga.toJson(): MangaJson {
private fun Manga.toJson(): MangaJson {
return MangaJson(
id!!,
title,
author,
artist,
description,
genre?.split(", ")?.toTypedArray()
genre?.split(", ")
)
}
@Serializable
data class MangaList(
val mangas: List<MangaJson>? = null
)
@Serializable
data class MangaJson(
val id: Long,
val id: Long? = null,
val title: String? = null,
val author: String? = null,
val artist: String? = null,
val description: String? = null,
val genre: Array<String>? = null
val genre: List<String>? = null
) {
override fun equals(other: Any?): Boolean {
@@ -166,8 +166,7 @@ class LibraryUpdateNotifier(private val context: Context) {
// Per-manga notification
if (!preferences.hideNotificationContent()) {
updates.forEach {
val (manga, chapters) = it
updates.forEach { (manga, chapters) ->
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
}
}
@@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.data.library
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import androidx.core.content.ContextCompat
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
@@ -27,8 +27,11 @@ 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.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.asObservable
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat
@@ -39,10 +42,7 @@ import exh.MERGED_SOURCE_ID
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.source.getMainSource
import exh.util.nullIfBlank
import kotlinx.coroutines.runBlocking
import rx.Observable
@@ -89,10 +89,15 @@ class LibraryUpdateService(
*/
enum class Target {
CHAPTERS, // Manga chapters
COVERS, // Manga covers
TRACKING, // Tracking metadata
// SY -->
SYNC_FOLLOWS // MangaDex specific, pull mangadex manga in reading, rereading
SYNC_FOLLOWS, // MangaDex specific, pull mangadex manga in reading, rereading
PUSH_FAVORITES // MangaDex specific, push mangadex manga to mangadex
// SY <--
}
@@ -145,11 +150,7 @@ class LibraryUpdateService(
groupExtra?.let { putExtra(KEY_GROUP_EXTRA, it) }
// SY <--
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
ContextCompat.startForegroundService(context, intent)
return true
}
@@ -229,6 +230,7 @@ class LibraryUpdateService(
Target.TRACKING -> updateTrackings(mangaList)
// SY -->
Target.SYNC_FOLLOWS -> syncFollows()
Target.PUSH_FAVORITES -> pushFavorites()
// SY <--
}
}
@@ -362,7 +364,7 @@ class LibraryUpdateService(
Pair(emptyList(), emptyList())
}
// Filter out mangas without new chapters (or failed).
.filter { pair -> pair.first.isNotEmpty() }
.filter { (first) -> first.isNotEmpty() }
.doOnNext {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(manga, it.first)
@@ -405,7 +407,7 @@ class LibraryUpdateService(
)
}
}
.map { manga -> manga.first }
.map { (first) -> first }
}
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
@@ -426,7 +428,7 @@ class LibraryUpdateService(
* @return a pair of the inserted and removed chapters.
*/
fun updateManga(manga: Manga): Observable<Pair<List<Chapter>, List<Chapter>>> {
val source = sourceManager.getOrStub(manga.source)
val source = sourceManager.getOrStub(manga.source).getMainSource()
// Update manga details metadata in the background
if (preferences.autoUpdateMetadata()) {
@@ -448,21 +450,6 @@ class LibraryUpdateService(
.subscribe()
}
// SY -->
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)
@@ -470,12 +457,16 @@ class LibraryUpdateService(
// 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()
if (source 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.tag("LibraryUpdateService").e(e)
}
}
}
@@ -549,7 +540,7 @@ class LibraryUpdateService(
// 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)!!
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return Observable.empty()
return mangaDex.fetchAllFollows(true)
.asObservable()
.map { listManga ->
@@ -583,7 +574,41 @@ class LibraryUpdateService(
.doOnCompleted {
notifier.cancelProgressNotification()
}
.map { LibraryManga() }
.flatMap { Observable.empty() }
}
/**
* Method that updates the all mangas which are not tracked as "reading" on mangadex
*/
private fun pushFavorites(): Observable<LibraryManga> {
val count = AtomicInteger(0)
val listManga = db.getLibraryMangas().executeAsBlocking()
// filter all follows from Mangadex and only add reading or rereading manga to library
return Observable.from(if (trackManager.mdList.isLogged) listManga else emptyList())
.flatMap { manga ->
notifier.showProgressNotification(manga, count.andIncrement, listManga.size)
// Get this manga's trackers from the database
val dbTracks = db.getTracks(manga).executeAsBlocking()
// find the mdlist entry if its unfollowed the follow it
val tracker = TrackItem(dbTracks.firstOrNull { it.sync_id == TrackManager.MDLIST } ?: trackManager.mdList.createInitialTracker(manga), trackManager.mdList)
if (tracker.track?.status == FollowStatus.UNFOLLOWED.int) {
tracker.track.status = FollowStatus.READING.int
tracker.service.update(tracker.track)
} else Observable.just(null)
}
.doOnNext { returnedTracker ->
returnedTracker?.let {
db.insertTrack(returnedTracker)
}
}
.doOnCompleted {
notifier.cancelProgressNotification()
}
.flatMap { Observable.empty() }
}
// SY <--
@@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast
import exh.md.similar.SimilarUpdateService
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -100,6 +101,9 @@ class NotificationReceiver : BroadcastReceiver() {
markAsRead(urls, mangaId)
}
}
// SY -->
ACTION_CANCEL_SIMILAR_UPDATE -> cancelSimilarUpdate(context)
// SY <--
}
}
@@ -241,6 +245,18 @@ class NotificationReceiver : BroadcastReceiver() {
}
}
// SY -->
/**
* Method called when user wants to stop a similar manga update
*
* @param context context of application
*/
private fun cancelSimilarUpdate(context: Context) {
SimilarUpdateService.stop(context)
Handler().post { dismissNotification(context, Notifications.ID_SIMILAR_PROGRESS) }
}
// SY <--
companion object {
private const val NAME = "NotificationReceiver"
@@ -298,6 +314,11 @@ class NotificationReceiver : BroadcastReceiver() {
// Value containing chapter url.
private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
// Sy -->
// Called to cancel similar manga update.
private const val ACTION_CANCEL_SIMILAR_UPDATE = "$ID.$NAME.CANCEL_SIMILAR_UPDATE"
// SY <--
/**
* Returns a [PendingIntent] that resumes the download of a chapter
*
@@ -548,5 +569,20 @@ class NotificationReceiver : BroadcastReceiver() {
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// SY -->
/**
* Returns [PendingIntent] that starts a service which stops the similar update
*
* @param context context of application
* @return [PendingIntent]
*/
internal fun cancelSimilarUpdatePendingBroadcast(context: Context): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_CANCEL_SIMILAR_UPDATE
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// SY <--
}
}
@@ -62,6 +62,15 @@ object Notifications {
const val ID_BACKUP_COMPLETE = -502
const val ID_RESTORE_COMPLETE = -504
// SY -->
/**
* Notification channel and ids used for backup and restore.
*/
const val CHANNEL_SIMILAR = "similar_channel"
const val ID_SIMILAR_PROGRESS = -601
const val ID_SIMILAR_COMPLETE = -602
// SY <--
private val deprecatedChannels = listOf(
"downloader_channel",
"backup_restore_complete_channel"
@@ -143,6 +152,13 @@ object Notifications {
group = GROUP_BACKUP_RESTORE
setShowBadge(false)
setSound(null, null)
},
NotificationChannel(
CHANNEL_SIMILAR,
context.getString(R.string.similar),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
}
).forEach(context.notificationManager::createNotificationChannel)
@@ -13,6 +13,8 @@ object PreferenceKeys {
const val confirmExit = "pref_confirm_exit"
const val hideBottomBar = "pref_hide_bottom_bar_on_scroll"
const val rotation = "pref_rotation_type_key"
const val enableTransitions = "pref_enable_transitions_key"
@@ -109,17 +111,17 @@ object PreferenceKeys {
const val downloadedOnly = "pref_downloaded_only"
const val filterDownloaded = "pref_filter_downloaded_key"
const val filterDownloaded = "pref_filter_library_downloaded"
const val filterUnread = "pref_filter_unread_key"
const val filterUnread = "pref_filter_library_unread"
const val filterCompleted = "pref_filter_completed_key"
const val filterCompleted = "pref_filter_library_completed"
const val filterStarted = "pref_filter_started_key"
const val filterStarted = "pref_filter_library_started"
const val filterTracked = "pref_filter_tracked_key"
const val filterTracked = "pref_filter_library_tracked"
const val filterLewd = "pref_filter_lewd_key"
const val filterLewd = "pref_filter_library_lewd"
const val librarySortingMode = "library_sorting_mode"
@@ -173,22 +175,26 @@ object PreferenceKeys {
const val enableDoh = "enable_doh"
const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
const val defaultChapterFilterByDownloaded = "default_chapter_filter_by_downloaded"
const val defaultChapterFilterByBookmarked = "default_chapter_filter_by_bookmarked"
const val defaultChapterSortBySourceOrNumber = "default_chapter_sort_by_source_or_number" // and upload date
const val defaultChapterSortByAscendingOrDescending = "default_chapter_sort_by_ascending_or_descending"
const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
const val incognitoMode = "incognito_mode"
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
fun trackToken(syncId: Int) = "track_token_$syncId"
const val eh_lock_hash = "lock_hash"
const val eh_lock_salt = "lock_salt"
const val eh_lock_length = "lock_length"
const val eh_lock_finger = "lock_finger"
const val eh_lock_manually = "eh_lock_manually"
const val eh_showSyncIntro = "eh_show_sync_intro"
const val eh_readOnlySync = "eh_sync_read_only"
@@ -209,8 +215,6 @@ object PreferenceKeys {
const val eh_enableExHentai = "enable_exhentai"
const val eh_ts_aspNetCookie = "eh_ts_aspNetCookie"
const val eh_showSettingsUploadWarning = "eh_showSettingsUploadWarning2"
const val eh_expandFilters = "eh_expand_filters"
@@ -265,10 +269,10 @@ object PreferenceKeys {
const val latest_tab_position = "latest_tab_position"
const val latest_tab_language_code = "latest_tab_language_code"
const val sources_tab_categories = "sources_tab_categories"
const val sources_tab_categories_filter = "sources_tab_categories_filter"
const val sources_tab_source_categories = "sources_tab_source_categories"
const val sourcesSort = "sources_sort"
@@ -289,10 +293,14 @@ object PreferenceKeys {
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 mangadexSimilarEnabled = "pref_related_show_tab_key"
const val mangadexSimilarUpdateInterval = "related_update_interval"
const val mangadexSimilarOnlyOverWifi = "pref_simular_only_over_wifi_key"
const val preferredMangaDexId = "preferred_mangaDex_id"
const val dataSaver = "data_saver"
@@ -316,4 +324,10 @@ object PreferenceKeys {
const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders"
const val biometricTimeRanges = "biometric_time_ranges"
const val sortTagsForLibrary = "sort_tags_for_library"
const val createLegacyBackup = "create_legacy_backup"
const val dontDeleteFromCategories = "dont_delete_from_categories"
}
@@ -29,8 +29,10 @@ object PreferenceValues {
enum class DisplayMode {
COMPACT_GRID,
COMFORTABLE_GRID,
// SY -->
NO_TITLE_GRID,
// SY <--
LIST,
}
@@ -2,15 +2,18 @@ package eu.kanade.tachiyomi.data.preference
import android.content.Context
import android.os.Environment
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
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.anilist.Anilist
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
@@ -58,6 +61,8 @@ class PreferencesHelper(val context: Context) {
fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false)
fun hideBottomBar() = flowPrefs.getBoolean(Keys.hideBottomBar, true)
fun useBiometricLock() = flowPrefs.getBoolean(Keys.useBiometricLock, false)
fun lockAppAfter() = flowPrefs.getInt(Keys.lockAppAfter, 0)
@@ -72,7 +77,7 @@ class PreferencesHelper(val context: Context) {
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false)
fun clear() = prefs.edit().clear().apply()
fun clear() = prefs.edit { clear() }
fun themeMode() = flowPrefs.getEnum(Keys.themeMode, Values.ThemeMode.system)
@@ -159,10 +164,10 @@ class PreferencesHelper(val context: Context) {
fun trackPassword(sync: TrackService) = prefs.getString(Keys.trackPassword(sync.id), "")
fun setTrackCredentials(sync: TrackService, username: String, password: String) {
prefs.edit()
.putString(Keys.trackUsername(sync.id), username)
.putString(Keys.trackPassword(sync.id), password)
.apply()
prefs.edit {
putString(Keys.trackUsername(sync.id), username)
putString(Keys.trackPassword(sync.id), password)
}
}
fun trackToken(sync: TrackService) = flowPrefs.getString(Keys.trackToken(sync.id), "")
@@ -208,18 +213,17 @@ class PreferencesHelper(val context: Context) {
fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true)
// J2K converted from boolean to integer
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, 0)
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, 0)
fun filterUnread() = flowPrefs.getInt(Keys.filterUnread, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, 0)
fun filterCompleted() = flowPrefs.getInt(Keys.filterCompleted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, 0)
fun filterStarted() = flowPrefs.getInt(Keys.filterStarted, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, 0)
fun filterTracked() = flowPrefs.getInt(Keys.filterTracked, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, 0)
fun filterLewd() = flowPrefs.getInt(Keys.filterLewd, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
fun librarySortingMode() = flowPrefs.getInt(Keys.librarySortingMode, 0)
@@ -257,7 +261,36 @@ class PreferencesHelper(val context: Context) {
fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false)
// --> AZ J2K CHERRYPICKING
fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "")
fun filterChapterByRead() = prefs.getInt(Keys.defaultChapterFilterByRead, Manga.SHOW_ALL)
fun filterChapterByDownloaded() = prefs.getInt(Keys.defaultChapterFilterByDownloaded, Manga.SHOW_ALL)
fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.SORTING_SOURCE)
fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.DISPLAY_NAME)
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC)
fun incognitoMode() = flowPrefs.getBoolean(Keys.incognitoMode, false)
fun createLegacyBackup() = flowPrefs.getBoolean(Keys.createLegacyBackup, true)
fun setChapterSettingsDefault(manga: Manga) {
prefs.edit {
putInt(Keys.defaultChapterFilterByRead, manga.readFilter)
putInt(Keys.defaultChapterFilterByDownloaded, manga.downloadedFilter)
putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter)
putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting)
putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode)
putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) Manga.SORT_DESC else Manga.SORT_ASC)
}
}
// SY -->
fun defaultMangaOrder() = flowPrefs.getString("default_manga_order", "")
@@ -269,19 +302,7 @@ class PreferencesHelper(val context: Context) {
fun skipPreMigration() = flowPrefs.getBoolean(Keys.skipPreMigration, false)
fun upgradeFilters() {
val filterDl = flowPrefs.getBoolean(Keys.filterDownloaded, false).get()
val filterUn = flowPrefs.getBoolean(Keys.filterUnread, false).get()
val filterCm = flowPrefs.getBoolean(Keys.filterCompleted, false).get()
filterDownloaded().set(if (filterDl) 1 else 0)
filterUnread().set(if (filterUn) 1 else 0)
filterCompleted().set(if (filterCm) 1 else 0)
}
// <--
// --> EH
fun eh_isHentaiEnabled() = flowPrefs.getBoolean(Keys.eh_is_hentai_enabled, true)
fun isHentaiEnabled() = flowPrefs.getBoolean(Keys.eh_is_hentai_enabled, true)
fun enableExhentai() = flowPrefs.getBoolean(Keys.eh_enableExHentai, false)
@@ -291,89 +312,81 @@ class PreferencesHelper(val context: Context) {
fun useJapaneseTitle() = flowPrefs.getBoolean("use_jp_title", false)
fun eh_useOriginalImages() = flowPrefs.getBoolean(Keys.eh_useOrigImages, false)
fun exhUseOriginalImages() = flowPrefs.getBoolean(Keys.eh_useOrigImages, false)
fun ehTagFilterValue() = flowPrefs.getInt(Keys.eh_tag_filtering_value, 0)
fun ehTagWatchingValue() = flowPrefs.getInt(Keys.eh_tag_watching_value, 0)
fun ehSearchSize() = flowPrefs.getString("ex_search_size", "rc_0")
fun thumbnailRows() = flowPrefs.getString("ex_thumb_rows", "tr_2")
fun hasPerformedURLMigration() = flowPrefs.getBoolean("performed_url_migration", false)
// EH Cookies
fun memberIdVal() = flowPrefs.getString("eh_ipb_member_id", "")
fun passHashVal() = flowPrefs.getString("eh_ipb_pass_hash", "")
fun igneousVal() = flowPrefs.getString("eh_igneous", "")
fun eh_ehSettingsProfile() = flowPrefs.getInt(Keys.eh_ehSettingsProfile, -1)
fun eh_exhSettingsProfile() = flowPrefs.getInt(Keys.eh_exhSettingsProfile, -1)
fun eh_settingsKey() = flowPrefs.getString(Keys.eh_settingsKey, "")
fun eh_sessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
fun eh_hathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
fun ehSettingsProfile() = flowPrefs.getInt(Keys.eh_ehSettingsProfile, -1)
fun exhSettingsProfile() = flowPrefs.getInt(Keys.eh_exhSettingsProfile, -1)
fun exhSettingsKey() = flowPrefs.getString(Keys.eh_settingsKey, "")
fun exhSessionCookie() = flowPrefs.getString(Keys.eh_sessionCookie, "")
fun exhHathPerksCookies() = flowPrefs.getString(Keys.eh_hathPerksCookie, "")
fun eh_showSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
fun exhShowSyncIntro() = flowPrefs.getBoolean(Keys.eh_showSyncIntro, true)
fun eh_readOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
fun exhReadOnlySync() = flowPrefs.getBoolean(Keys.eh_readOnlySync, false)
fun eh_lenientSync() = flowPrefs.getBoolean(Keys.eh_lenientSync, false)
fun exhLenientSync() = flowPrefs.getBoolean(Keys.eh_lenientSync, false)
fun eh_ts_aspNetCookie() = flowPrefs.getString(Keys.eh_ts_aspNetCookie, "")
fun exhShowSettingsUploadWarning() = flowPrefs.getBoolean(Keys.eh_showSettingsUploadWarning, true)
fun eh_showSettingsUploadWarning() = flowPrefs.getBoolean(Keys.eh_showSettingsUploadWarning, true)
fun expandFilters() = flowPrefs.getBoolean(Keys.eh_expandFilters, false)
fun eh_expandFilters() = flowPrefs.getBoolean(Keys.eh_expandFilters, false)
fun readerThreads() = flowPrefs.getInt(Keys.eh_readerThreads, 2)
fun eh_readerThreads() = flowPrefs.getInt(Keys.eh_readerThreads, 2)
fun readerInstantRetry() = flowPrefs.getBoolean(Keys.eh_readerInstantRetry, true)
fun eh_readerInstantRetry() = flowPrefs.getBoolean(Keys.eh_readerInstantRetry, true)
fun autoscrollInterval() = flowPrefs.getFloat(Keys.eh_utilAutoscrollInterval, 3f)
fun eh_utilAutoscrollInterval() = flowPrefs.getFloat(Keys.eh_utilAutoscrollInterval, 3f)
fun cacheSize() = flowPrefs.getString(Keys.eh_cacheSize, "75")
fun eh_cacheSize() = flowPrefs.getString(Keys.eh_cacheSize, "75")
fun preserveReadingPosition() = flowPrefs.getBoolean(Keys.eh_preserveReadingPosition, false)
fun eh_preserveReadingPosition() = flowPrefs.getBoolean(Keys.eh_preserveReadingPosition, false)
fun autoSolveCaptcha() = flowPrefs.getBoolean(Keys.eh_autoSolveCaptchas, false)
fun eh_autoSolveCaptchas() = flowPrefs.getBoolean(Keys.eh_autoSolveCaptchas, false)
fun delegateSources() = flowPrefs.getBoolean(Keys.eh_delegateSources, true)
fun eh_delegateSources() = flowPrefs.getBoolean(Keys.eh_delegateSources, true)
fun ehLastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
fun eh_lastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
fun savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
fun eh_savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
fun logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
fun eh_logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
fun enableSourceBlacklist() = flowPrefs.getBoolean(Keys.eh_enableSourceBlacklist, true)
fun eh_enableSourceBlacklist() = flowPrefs.getBoolean(Keys.eh_enableSourceBlacklist, true)
fun exhAutoUpdateFrequency() = flowPrefs.getInt(Keys.eh_autoUpdateFrequency, 1)
fun eh_autoUpdateFrequency() = flowPrefs.getInt(Keys.eh_autoUpdateFrequency, 1)
fun exhAutoUpdateRequirements() = flowPrefs.getStringSet(Keys.eh_autoUpdateRestrictions, emptySet())
fun eh_autoUpdateRequirements() = prefs.getStringSet(Keys.eh_autoUpdateRestrictions, emptySet())
fun exhAutoUpdateStats() = flowPrefs.getString(Keys.eh_autoUpdateStats, "")
fun eh_autoUpdateStats() = flowPrefs.getString(Keys.eh_autoUpdateStats, "")
fun aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
fun preloadSize() = flowPrefs.getInt(Keys.eh_preload_size, 10)
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
fun useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
fun exhWatchedListDefaultState() = flowPrefs.getBoolean(Keys.eh_watched_list_default_state, false)
fun eh_watchedListDefaultState() = flowPrefs.getBoolean(Keys.eh_watched_list_default_state, false)
fun exhSettingsLanguages() = flowPrefs.getString(Keys.eh_settings_languages, "false*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false")
fun eh_settingsLanguages() = flowPrefs.getString(Keys.eh_settings_languages, "false*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false\nfalse*false*false")
fun eh_EnabledCategories() = flowPrefs.getString(Keys.eh_enabled_categories, "false,false,false,false,false,false,false,false,false,false")
fun exhEnabledCategories() = flowPrefs.getString(Keys.eh_enabled_categories, "false,false,false,false,false,false,false,false,false,false")
fun latestTabSources() = flowPrefs.getStringSet(Keys.latest_tab_sources, mutableSetOf())
fun latestTabInFront() = flowPrefs.getBoolean(Keys.latest_tab_position, false)
fun latestTabDisplayLanguageCode() = flowPrefs.getBoolean(Keys.latest_tab_language_code, false)
fun sourcesTabCategories() = flowPrefs.getStringSet(Keys.sources_tab_categories, mutableSetOf())
fun sourcesTabCategoriesFilter() = flowPrefs.getBoolean(Keys.sources_tab_categories_filter, false)
fun sourcesTabSourcesInCategories() = flowPrefs.getStringSet(Keys.sources_tab_source_categories, mutableSetOf())
fun sourceSorting() = flowPrefs.getInt(Keys.sourcesSort, 0)
@@ -392,14 +405,20 @@ class PreferencesHelper(val context: Context) {
fun groupLibraryUpdateType() = flowPrefs.getEnum(Keys.groupLibraryUpdateType, Values.GroupLibraryMode.GLOBAL)
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, false)
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, true)
fun mangaDexForceLatestCovers() = flowPrefs.getBoolean(Keys.mangaDexForceLatestCovers, false)
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
fun mangadexSimilarEnabled() = flowPrefs.getBoolean(Keys.mangadexSimilarEnabled, false)
fun shownMangaDexSimilarAskDialog() = flowPrefs.getBoolean("shown_similar_ask_dialog", false)
fun mangadexSimilarOnlyOverWifi() = flowPrefs.getBoolean(Keys.mangadexSimilarOnlyOverWifi, true)
fun mangadexSimilarUpdateInterval() = flowPrefs.getInt(Keys.mangadexSimilarUpdateInterval, 2)
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
fun ignoreJpeg() = flowPrefs.getBoolean(Keys.ignoreJpeg, false)
@@ -421,4 +440,8 @@ class PreferencesHelper(val context: Context) {
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false)
fun biometricTimeRanges() = flowPrefs.getStringSet(Keys.biometricTimeRanges, mutableSetOf())
fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf())
fun dontDeleteFromCategories() = flowPrefs.getStringSet(Keys.dontDeleteFromCategories, emptySet())
}
@@ -2,11 +2,13 @@ package eu.kanade.tachiyomi.data.track.anilist
import android.content.Context
import android.graphics.Color
import com.google.gson.Gson
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import rx.Completable
import rx.Observable
import uy.kohesive.injekt.injectLazy
@@ -33,7 +35,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
override val name = "AniList"
private val gson: Gson by injectLazy()
private val json: Json by injectLazy()
private val interceptor by lazy { AnilistInterceptor(this, getPassword()) }
@@ -197,12 +199,12 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
fun saveOAuth(oAuth: OAuth?) {
preferences.trackToken(this).set(gson.toJson(oAuth))
preferences.trackToken(this).set(json.encodeToString(oAuth))
}
fun loadOAuth(): OAuth? {
return try {
gson.fromJson(preferences.trackToken(this).get(), OAuth::class.java)
json.decodeFromString<OAuth>(preferences.trackToken(this).get())
} catch (e: Exception) {
null
}
@@ -2,26 +2,34 @@ package eu.kanade.tachiyomi.data.track.anilist
import android.net.Uri
import androidx.core.net.toUri
import com.github.salomonbrys.kotson.array
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.jsonObject
import com.github.salomonbrys.kotson.nullInt
import com.github.salomonbrys.kotson.nullString
import com.github.salomonbrys.kotson.obj
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.asObservableSuccess
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val json: Json by injectLazy()
private val jsonMime = "application/json; charset=utf-8".toMediaTypeOrNull()
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
@@ -35,15 +43,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}
|""".trimMargin()
val variables = jsonObject(
"mangaId" to track.media_id,
"progress" to track.last_chapter_read,
"status" to track.toAnilistStatus()
)
val payload = jsonObject(
"query" to query,
"variables" to variables
)
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("mangaId", track.media_id)
put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus())
}
}
val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder()
.url(apiUrl)
@@ -57,8 +64,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = JsonParser.parseString(responseBody).obj
track.library_id = response["data"]["SaveMediaListEntry"]["id"].asLong
val response = json.decodeFromString<JsonObject>(responseBody)
track.library_id = response["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
track
}
}
@@ -74,16 +81,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}
|""".trimMargin()
val variables = jsonObject(
"listId" to track.library_id,
"progress" to track.last_chapter_read,
"status" to track.toAnilistStatus(),
"score" to track.score.toInt()
)
val payload = jsonObject(
"query" to query,
"variables" to variables
)
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("listId", track.library_id)
put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus())
put("score", track.score.toInt())
}
}
val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder()
.url(apiUrl)
@@ -122,13 +128,12 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}
|""".trimMargin()
val variables = jsonObject(
"query" to search
)
val payload = jsonObject(
"query" to query,
"variables" to variables
)
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("query", search)
}
}
val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder()
.url(apiUrl)
@@ -141,11 +146,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = JsonParser.parseString(responseBody).obj
val data = response["data"]!!.obj
val page = data["Page"].obj
val media = page["media"].array
val entries = media.map { jsonToALManga(it.obj) }
val response = json.decodeFromString<JsonObject>(responseBody)
val data = response["data"]!!.jsonObject
val page = data["Page"]!!.jsonObject
val media = page["media"]!!.jsonArray
val entries = media.map { jsonToALManga(it.jsonObject) }
entries.map { it.toTrack() }
}
}
@@ -182,14 +187,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}
|""".trimMargin()
val variables = jsonObject(
"id" to userid,
"manga_id" to track.media_id
)
val payload = jsonObject(
"query" to query,
"variables" to variables
)
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("id", userid)
put("manga_id", track.media_id)
}
}
val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder()
.url(apiUrl)
@@ -202,11 +206,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = JsonParser.parseString(responseBody).obj
val data = response["data"]!!.obj
val page = data["Page"].obj
val media = page["mediaList"].array
val entries = media.map { jsonToALUserManga(it.obj) }
val response = json.decodeFromString<JsonObject>(responseBody)
val data = response["data"]!!.jsonObject
val page = data["Page"]!!.jsonObject
val media = page["mediaList"]!!.jsonArray
val entries = media.map { jsonToALUserManga(it.jsonObject) }
entries.firstOrNull()?.toTrack()
}
}
@@ -232,9 +236,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|}
|}
|""".trimMargin()
val payload = jsonObject(
"query" to query
)
val payload = buildJsonObject {
put("query", query)
}
val body = payload.toString().toRequestBody(jsonMime)
val request = Request.Builder()
.url(apiUrl)
@@ -247,10 +251,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
val response = JsonParser.parseString(responseBody).obj
val data = response["data"]!!.obj
val viewer = data["Viewer"].obj
Pair(viewer["id"].asInt, viewer["mediaListOptions"]["scoreFormat"].asString)
val response = json.decodeFromString<JsonObject>(responseBody)
val data = response["data"]!!.jsonObject
val viewer = data["Viewer"]!!.jsonObject
Pair(viewer["id"]!!.jsonPrimitive.int, viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content)
}
}
@@ -258,12 +262,12 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
val date = try {
val date = Calendar.getInstance()
date.set(
struct["startDate"]["year"].nullInt ?: 0,
struct["startDate"]!!.jsonObject["year"]!!.jsonPrimitive.intOrNull ?: 0,
(
struct["startDate"]["month"].nullInt
struct["startDate"]!!.jsonObject["month"]!!.jsonPrimitive.intOrNull
?: 0
) - 1,
struct["startDate"]["day"].nullInt ?: 0
struct["startDate"]!!.jsonObject["day"]!!.jsonPrimitive.intOrNull ?: 0
)
date.timeInMillis
} catch (_: Exception) {
@@ -271,19 +275,25 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
return ALManga(
struct["id"].asInt,
struct["title"]["romaji"].asString,
struct["coverImage"]["large"].asString,
struct["description"].nullString.orEmpty(),
struct["type"].asString,
struct["status"].nullString.orEmpty(),
struct["id"]!!.jsonPrimitive.int,
struct["title"]!!.jsonObject["romaji"]!!.jsonPrimitive.content,
struct["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
struct["description"]!!.jsonPrimitive.contentOrNull,
struct["type"]!!.jsonPrimitive.content,
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
date,
struct["chapters"].nullInt ?: 0
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0
)
}
private fun jsonToALUserManga(struct: JsonObject): ALUserManga {
return ALUserManga(struct["id"].asLong, struct["status"].asString, struct["scoreRaw"].asInt, struct["progress"].asInt, jsonToALManga(struct["media"].obj))
return ALUserManga(
struct["id"]!!.jsonPrimitive.long,
struct["status"]!!.jsonPrimitive.content,
struct["scoreRaw"]!!.jsonPrimitive.int,
struct["progress"]!!.jsonPrimitive.int,
jsonToALManga(struct["media"]!!.jsonObject)
)
}
companion object {

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