Compare commits

...

295 Commits

Author SHA1 Message Date
Jobobby04 0aebe1da43 Release 1.6.2 2021-04-28 14:24:05 -04:00
arkon f45fdca168 Remove app update check on Android 5.x
(cherry picked from commit 13324dd1a1)
2021-04-28 14:01:22 -04:00
Jozef Hollý fc5eb4cccc Weblate translations (#4947)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Albedo <Illiator27@gmail.com>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Blue cat <bluecat300@gmail.com>
Co-authored-by: Csíkos Martin Nándor <csikos.martin17@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Losms <krishna.chand67@yahoo.com>
Co-authored-by: Luka Paun <croluxgame@gmail.com>
Co-authored-by: Lusuho <jevpsychox@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nestor A. Sanchez <help.toastcode@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Q farfayoux <aym.belrhiti@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Techeira Damián <damian.techeira@mercadolibre.com>
Co-authored-by: Thu Htoo San <kokhantyangon@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: monolifed <monolifed@protonmail.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/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/hu/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/my/
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_BR/
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/
Translation: Tachiyomi/Tachiyomi 0.x

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Albedo <Illiator27@gmail.com>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Blue cat <bluecat300@gmail.com>
Co-authored-by: Csíkos Martin Nándor <csikos.martin17@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Losms <krishna.chand67@yahoo.com>
Co-authored-by: Luka Paun <croluxgame@gmail.com>
Co-authored-by: Lusuho <jevpsychox@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nestor A. Sanchez <help.toastcode@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Q farfayoux <aym.belrhiti@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Techeira Damián <damian.techeira@mercadolibre.com>
Co-authored-by: Thu Htoo San <kokhantyangon@gmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit ae9bf06b46)
2021-04-27 23:36:57 -04:00
Eugene 8ac309c4ae Theme AMOLEDblue (#280) 2021-04-27 17:57:51 -04:00
Jobobby04 f170446c5f Lint 2021-04-26 18:02:29 -04:00
arkon 643bec9bbb Update issue-closer-action
(cherry picked from commit 5236834911)
2021-04-26 18:02:11 -04:00
Ivan Iskandar 134be3893e Fix download error icon color tint (#4959)
* Fix download error color tint

* Use progress indicator as download icon border

* Resolve feedback

* Use extension function to set tinted drawable

(cherry picked from commit bf80dd622c)
2021-04-26 18:01:55 -04:00
Andreas 5855822edd Cleanup dual page split (#4956)
* Cleanup Dual Page Split

* Move where images is processed

* Change parameter name to imageStream

* Use available instead of Int.MAX_VALUE

* Update JavaDoc

(cherry picked from commit 662b71436e)
2021-04-26 18:01:47 -04:00
arkon 3343b766a2 Minor cleanup to updating download status in Updates
(cherry picked from commit f608cb55eb)
2021-04-26 18:01:39 -04:00
arkon 329d24c7db Don't automatically go to HALF_EXPANDED state for color filter tab (closes #4913)
(cherry picked from commit 6ba82da029)
2021-04-26 18:01:28 -04:00
arkon bdfbc641d9 Reset Incognito Mode on app relaunch (closes #4928)
(cherry picked from commit f407e30b6e)
2021-04-26 18:01:20 -04:00
Ivan Iskandar 6e570d7fad Make the download progress status smoother (#4958)
* Make the download progress status smoother

* Download status icon cleanup

(cherry picked from commit 4e7b8c98f9)
2021-04-26 18:01:11 -04:00
arkon b5d696ebe2 Use popup menus for reader shortcuts instead of toggling through
(cherry picked from commit 5f9574541f)
2021-04-26 18:01:02 -04:00
arkon 5299ae4856 Maybe better handle MAL token expiration
(cherry picked from commit 08a6db7d6e)
2021-04-26 18:00:54 -04:00
arkon a9038831da Downgrade back to stable OkHttp
Maybe fixes some crashes.

(cherry picked from commit b485e1d657)
2021-04-26 18:00:45 -04:00
arkon f1a8132307 Remove "Locked" orientation, replace with explicit orientations
Portrait/Landscape allow sensor, Locked Portrait/Landscape don't.

(cherry picked from commit e8d8621f06)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-04-26 18:00:36 -04:00
arkon 76185338bf Make manga and chapter folder name searching case insensitive
(cherry picked from commit 4cefbce7c3)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt
2021-04-26 17:58:11 -04:00
arkon bda4aae83d Sanitize source download folder name (fixes #4945)
(cherry picked from commit fa31369f99)
2021-04-26 17:57:05 -04:00
Ivan Iskandar 80bf908133 MainActivity: Show bottom nav when the tab page is changed (#4914)
* MainActivity: Show bottom nav when the tab page is changed

* Revert "MainActivity: Show bottom nav when the tab page is changed"

This reverts commit 27fd73db

* MainActivity: Show bottom nav when the app bar is fully expanded

(cherry picked from commit d0bf93ebb7)
2021-04-26 17:56:37 -04:00
arkon 91b49f8a0c Consider sort direction when downloading next n chapters (fixes #4916)
(cherry picked from commit 41a747c7e7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2021-04-26 17:56:28 -04:00
arkon 80a5a54e60 Consider sort direction when resuming (fixes #4909)
(cherry picked from commit 8882cd4787)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2021-04-26 17:50:48 -04:00
arkon 3104f3a8b5 Add link to official Facebook page
(cherry picked from commit 68bea8a196)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
2021-04-26 17:26:46 -04:00
Jobobby04 4fa2c968a9 Fix cleanup orphaned downloads removing valid downloads 2021-04-26 17:24:54 -04:00
Eugene be1e7f28ef SY Rus 1.7.0 (#270)
* Rus 1.7.0

* Fix2

* Fix3

* Fix4

* Fix5
2021-04-19 15:24:58 -04:00
Jobobby04 4118b13e5b Release 1.6.1 2021-04-19 15:19:13 -04:00
Jozef Hollý 7e0f2950c1 Weblate translations (#4812)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Hautzii <am.03012002@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: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Manuel Tassi <manueltassi91@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: OfficialBispo <diogobispo10@gmail.com>
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: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Zulkifli <zulhaha1@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/
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/eo/
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/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/
Translation: Tachiyomi/Tachiyomi 0.x

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: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Hautzii <am.03012002@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Manuel Tassi <manueltassi91@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: OfficialBispo <diogobispo10@gmail.com>
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: Reza Almanda <rezaalmanda27@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Zulkifli <zulhaha1@gmail.com>
Co-authored-by: f0roots <f0rootss@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 554f890ae3)
2021-04-19 14:59:03 -04:00
arkon a8c4da9e2b Theme BiometricUnlockActivity to avoid flashing light theme
(cherry picked from commit dd1743698f)
2021-04-19 14:58:52 -04:00
arkon 72d315b6ed Include extension loading errors in error logs
(cherry picked from commit b092e98ac9)
2021-04-19 14:58:44 -04:00
arkon b886f0a55a Fix activity leak
(cherry picked from commit 9ee6262aed)
2021-04-19 14:58:36 -04:00
Fernando Maldonado 63fa1ee75e Fix status bar icon colors in webview activity (#4903)
* Fixed status bar icon colors in webview activity

* Changed theme to Theme.Base

* Changed app theme to Theme.Base

* Update themes.xml

Co-authored-by: arkon <arkon@users.noreply.github.com>
(cherry picked from commit 24a2d86f41)
2021-04-19 14:58:21 -04:00
Andreas 7d1ad7efb6 [SKIP CI] Update FUNDING.yml (#4907)
(cherry picked from commit b5c5c66336)
2021-04-19 14:57:13 -04:00
arkon 56400febd1 Update LeakCanary
(cherry picked from commit a598ac3993)

# Conflicts:
#	app/build.gradle.kts
2021-04-19 14:56:48 -04:00
arkon aa56698dac Clean up controller viewbinding creation
Based on https://github.com/Jays2Kings/tachiyomiJ2K/blob/master/app/src/main/java/eu/kanade/tachiyomi/ui/base/controller/BaseController.kt

(cherry picked from commit cab919d74c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2021-04-19 14:56:08 -04:00
Ivan Iskandar d37b24adb1 Fix source SearchView stuck open until query submitted (#4897)
closes #4850

(cherry picked from commit 60a929b92c)
2021-04-19 14:42:18 -04:00
arkon d3778ac6e1 Clean up ChapterCache (remove Gson, Rx usage)
(cherry picked from commit 356b7c346a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
2021-04-19 14:42:04 -04:00
Ivan Iskandar e43777bba7 Themes cleanup (#4894)
(cherry picked from commit ad57fde1c5)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt
#	app/src/main/res/values/themes.xml
2021-04-19 14:35:28 -04:00
arkon 0b3209284a Update KotlinX dependencies
(cherry picked from commit 17f7dea21b)
2021-04-19 14:18:47 -04:00
arkon a1be070e99 Minor cleanup
(cherry picked from commit b40af7c3c6)

# Conflicts:
#	README.md
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
2021-04-19 14:18:39 -04:00
arkon eda47cd546 Move reading mode toast to default bottom position
Toasts don't block user interaction, so it's probably fine.

(cherry picked from commit 9065362fde)
2021-04-19 14:17:37 -04:00
arkon 54d8748c58 Adjust ActionToolbar positioning
Have I ever mentioned that I hate insets?

(cherry picked from commit ad9bad3d17)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
2021-04-19 14:16:22 -04:00
arkon 77c17f2556 Avoid duplicate actions in update notifications
(cherry picked from commit dfd858034f)
2021-04-19 14:15:55 -04:00
arkon d81e4158cb Add clipboard error string
I pulled a Jay and forgot to stage something.

(cherry picked from commit 58ad8fa8c0)
2021-04-19 14:15:43 -04:00
arkon 77061067ee Avoid crash when users copying to clipboard fails because they have apps that are listening to their clipboards but also denied permissions
See https://commonsware.com/blog/2013/08/08/developer-psa-please-fix-your-clipboard-handling.html

(cherry picked from commit 38610d8a24)
2021-04-19 14:15:33 -04:00
arkon 707af702c1 Avoid rare crash in WebViewActivity
(cherry picked from commit 27cec697bf)
2021-04-19 14:15:24 -04:00
arkon 1b0b98b140 [SKIP CI] Add string for EOL update check message
(cherry picked from commit 024f9a8c76)
2021-04-19 14:15:14 -04:00
arkon 2220b6a91d Follow chapter sort setting for start/resume FAB (closes #1716)
(cherry picked from commit f7cc36f2f0)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2021-04-19 14:14:59 -04:00
arkon 22b8f51fa3 Double tap Updates to go to Download Queue (closes #4884)
(cherry picked from commit ef5148ebb4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2021-04-19 14:07:22 -04:00
arkon 08f0e515d5 Use DSL for creating chapter description spanned string
(cherry picked from commit 6dbc0a6fd5)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt
2021-04-19 14:06:51 -04:00
arkon 28b4281683 Follow chapter sort setting when downloading next n chapters (closes #4725)
(cherry picked from commit fba3f9d501)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2021-04-19 14:05:12 -04:00
arkon 676f716fcb Update issue templates
(cherry picked from commit d9f8137362)

# Conflicts:
#	.github/ISSUE_TEMPLATE.md
#	.github/ISSUE_TEMPLATE/bug_report.md
#	.github/ISSUE_TEMPLATE/feature_request.md
2021-04-19 14:02:07 -04:00
arkon 665784a241 Adjust MoreController bottom padding for navbar
(cherry picked from commit 28416489b2)
2021-04-19 14:01:11 -04:00
arkon 0d16609f95 Long press reader settings icon to open color filter tab
Partially addresses #4867

(cherry picked from commit 54a23ddd1f)
2021-04-19 14:01:02 -04:00
arkon b46500c837 Add checkmark beside selected popup menu item
Based on what's in J2K. Also renamed to MaterialSpinnerView to match what's there.

Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
(cherry picked from commit 3287ca9cf2)
2021-04-19 14:00:55 -04:00
arkon fb5872ef51 Case insensitive source directory search
(cherry picked from commit a59e134862)

# Conflicts:
#	app/build.gradle.kts
2021-04-19 14:00:44 -04:00
arkon bc28e2d617 Adjust ActionToolbar positioning
(cherry picked from commit 1f8c5b0120)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
2021-04-19 14:00:10 -04:00
arkon de0c55117d Minor cleanup
(cherry picked from commit c7f839ea4a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt
2021-04-19 13:59:33 -04:00
arkon 936997b52e Remove toolbar snapping
(cherry picked from commit d981245723)
2021-04-19 13:58:43 -04:00
Ivan Iskandar 885c251fb4 Add navigation bar scrim (#4845)
* Revert "Add navigation bar scrim (closes #4836)"

This reverts commit 2a69d1b0

* Add navigation bar scrim

(cherry picked from commit 1f729f1cb3)
2021-04-19 13:58:32 -04:00
Jobobby04 b8e907cea2 Block merged manga from being able to be migrated 2021-04-15 15:28:28 -04:00
Jobobby04 f4c6b2e09c Fix crash in migration if there are no viable chapter numbers 2021-04-15 15:03:44 -04:00
Jobobby04 13f4bfa7bc Remove source migration action 2021-04-15 14:57:49 -04:00
Jobobby04 780c1e68a6 Rename debug function 2021-04-14 19:01:41 -04:00
Jobobby04 f10944521c Fix build 2021-04-14 18:45:48 -04:00
arkon af5ebeca56 Avoid crash when unknown reading mode is used
(cherry picked from commit b4577d6676)
2021-04-14 18:38:23 -04:00
arkon 01ad3dc92b Handle reader toolbar subtitle getting cut off when text is too big (closes #4843)
(cherry picked from commit 544adb9940)

# Conflicts:
#	app/src/main/res/layout/reader_activity.xml
2021-04-14 18:38:09 -04:00
Jays2Kings 5c1423be86 Migrating manga now also saves more chapter info
Bookmarks, Last Read, Date fetched, and has been read fully (this was already tracked but previously it only took the highest chapters

Closes #151

(cherry picked from commit ee4f3e6586910c8c4d62859c867c2a40e41eef67)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessAdapter.kt
2021-04-14 18:20:24 -04:00
Jays2Kings 382c23e0fd Fix webtoon mode not calling OnPageSelected in some cases (in upstream too)
This fix isn't 100% tested, but like 80%.

@arkon if you're reading this, this issue is happening up stream too. I can make a issue for it in the repo but haven't checked if it happens there:

Steps:
Get Cubari source, search "cubari:imgur/3iOqiIy" change to continuous vertical, crop borders. Then back out and open the chapter again. onPageSelected isn't called because recycler position is -1. Regardless of the 4 pages you should be on

also fyi just a slight scroll fixes this issue but still

(cherry picked from commit 88fd6e5c9897d4a528f93dd02cfa2a4c644a799d)
(cherry picked from commit 5f0493f1e5)
2021-04-14 18:18:03 -04:00
arkon 189b15fee6 Edge-to-edge in licenses activity
(cherry picked from commit c749e50bec)
2021-04-14 18:17:55 -04:00
arkon d3d937fe17 Use accent color for edge effect
(cherry picked from commit a4e5e3ece5)
2021-04-14 18:17:48 -04:00
Jobobby04 5af0e7e847 Nullable source name for manga type 2021-04-14 18:16:53 -04:00
Jobobby04 142bdd14b7 Add debug function to fix reader toast crash 2021-04-14 18:16:23 -04:00
Jobobby04 0483097fc3 Cleanup 2021-04-13 18:53:10 -04:00
Jobobby04 a3c26c63d4 LoginSource changes 2021-04-13 18:52:57 -04:00
arkon fbe10151f4 Add navigation bar scrim (closes #4836)
(cherry picked from commit 2a69d1b051)
2021-04-13 18:51:57 -04:00
arkon 92fc5ea4a0 Allow weaker unlock methods in Android 6 - 10 (fixes #4833)
(cherry picked from commit 126e1e2d9d)
2021-04-13 18:51:49 -04:00
arkon d2b620f485 Include debug info in dumped crash logs
(cherry picked from commit 0586e1d3ad)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
2021-04-13 18:51:40 -04:00
arkon 78aa57579d Allow dismissing download progress notification when paused (closes #4832)
(cherry picked from commit 07cb1c237e)
2021-04-13 18:49:54 -04:00
arkon b0a2d8908f Disallow forced dark mode, such as MIUI's
(cherry picked from commit f4f1efe5fa)
2021-04-13 18:49:45 -04:00
arkon de36cd0626 Fix toolbar elevation in History and Updates
(cherry picked from commit 37fdf4d434)
2021-04-13 18:49:34 -04:00
arkon b322ecd34a Fully expand source filter sheet on show (closes #4455)
(cherry picked from commit 99b46096a4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt
2021-04-13 18:49:23 -04:00
arkon 540e234562 Use same non-sticky heading style as Browse for Updates/History (closes #4822)
(cherry picked from commit 12e90ae35e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt
2021-04-13 18:31:18 -04:00
arkon f6be2c7a2a Start download when tapping update notification (closes #4825)
(cherry picked from commit 023311a874)
2021-04-12 15:03:49 -04:00
arkon c5df8725de Fix ActionToolbar bottom offset
(cherry picked from commit 155a4dd463)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
2021-04-12 15:03:40 -04:00
arkon 738e2f7cf1 Offset appbar using margin instead (maybe fixes #4819)
(cherry picked from commit 15bed1ac4c)
2021-04-12 15:03:10 -04:00
Tooster 0ac56750c8 Fix LibraryUpdateServiceTest so ./gradlew ... doesn't crash (#4821)
(cherry picked from commit 27f55f8098)
2021-04-12 15:03:01 -04:00
Jobobby04 3fb1b4affa Update readme screens 2021-04-12 14:45:13 -04:00
Jobobby04 2602c49756 Hide dedupe by priority 2021-04-12 14:45:13 -04:00
Eugene d23b3c82ba Rus locale (#262)
* Rus lacale

* pref_left_handed_vertical_seekbar_summary

* Fix 1
2021-04-11 23:47:47 -04:00
Jobobby04 6fa67c9a5f Release 1.6.0 2021-04-11 21:51:55 -04:00
Jobobby04 7a85d6b163 Update dependancies 2021-04-11 21:43:44 -04:00
Jobobby04 5a909f48b6 Fix some logs 2021-04-11 21:43:05 -04:00
Jobobby04 4d22db919d Disable mangadex tracking 2021-04-11 20:59:34 -04:00
Jobobby04 8a9f2cce10 More inset fixes 2021-04-11 20:56:50 -04:00
Jobobby04 ede0892cda Cleanup and fixes 2021-04-11 20:43:34 -04:00
Jobobby04 5df0eb7ed1 Convert EHentai Login controller to a activity 2021-04-11 20:43:21 -04:00
Jobobby04 67cb42ff30 Some inset fixes 2021-04-11 20:32:44 -04:00
Jobobby04 e65ea94a08 Comment out Mangadex intents 2021-04-11 20:31:12 -04:00
arkon fdac8a0380 Lint fixes/ignore some errors
(cherry picked from commit a3f1b72126)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-04-11 18:48:14 -04:00
arkon 1c56624d13 Make library update/backup error log action clearer for non-technical users
(cherry picked from commit a82e5f5452)
2021-04-11 18:47:32 -04:00
arkon 7c05c59501 Add locales: jv, lt, ne
(cherry picked from commit e10cb0e632)
2021-04-11 18:47:23 -04:00
arkon af77a58dcb Update DoH translations
(cherry picked from commit c7e07a6df0)
2021-04-11 18:47:16 -04:00
Jozef Hollý 5ee87ce8fc Weblate translations (#4647)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Arlangue <virgilemp@outlook.fr>
Co-authored-by: August Wale <bleachlithium@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: Edgar Mejía <edgar13155@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Hajba Károly <karoly.hajba98@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: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Kiroki Benjamin <heptahex999@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: LOKE__01 <luckylakshman378@gmail.com>
Co-authored-by: Luis Andrés Bajaña F <labfernandez2014@gmail.com>
Co-authored-by: Lusuho <jevpsychox@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Matyáš Caras <hernik27@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nikola Perović <nikolaperovicccc@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: P6N7L <nichitapospai@gmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pijus Bend <pijus.bend@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Q farfayoux <aym.belrhiti@gmail.com>
Co-authored-by: Riztard Lanthorn <riyanluqman@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Schrödinger's cat <schrodingers-kate@protonmail.com>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tantia <ilovechocobi@yahoo.com>
Co-authored-by: TheLastMelody <swordofthefallen@hotmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Yardi van Nimwegen <yardivn@live.nl>
Co-authored-by: antocs <roxasthethund@yahoo.it>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 殺Mustafa <mustafasheref8@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/cs/
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/eo/
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/hu/
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/jv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/kn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/my/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ne/
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/sr/
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/Tachiyomi 0.x

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: Arlangue <virgilemp@outlook.fr>
Co-authored-by: August Wale <bleachlithium@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: Edgar Mejía <edgar13155@gmail.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Hajba Károly <karoly.hajba98@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Kiroki Benjamin <heptahex999@gmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: LOKE__01 <luckylakshman378@gmail.com>
Co-authored-by: Luis Andrés Bajaña F <labfernandez2014@gmail.com>
Co-authored-by: Lusuho <jevpsychox@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Matyáš Caras <hernik27@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nikola Perović <nikolaperovicccc@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: P6N7L <nichitapospai@gmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pijus Bend <pijus.bend@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Q farfayoux <aym.belrhiti@gmail.com>
Co-authored-by: Riztard Lanthorn <riyanluqman@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Schrödinger's cat <schrodingers-kate@protonmail.com>
Co-authored-by: Shashank Pujari <shashankppujari@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tantia <ilovechocobi@yahoo.com>
Co-authored-by: TheLastMelody <swordofthefallen@hotmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Yardi van Nimwegen <yardivn@live.nl>
Co-authored-by: antocs <roxasthethund@yahoo.it>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 殺Mustafa <mustafasheref8@gmail.com>
(cherry picked from commit 2e0c778090)
2021-04-11 18:47:09 -04:00
arkon 348ef2cf0f Log "Invalid download location" issues to error log
(cherry picked from commit d421401626)
2021-04-11 18:46:45 -04:00
arkon 828944950b Add Google DoH provider
(cherry picked from commit b2d4e5ab84)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
2021-04-11 18:46:37 -04:00
Ivan Iskandar 1c67e82325 BrowseSourceController: Fix navigation bar insets not properly applied (#4810)
(cherry picked from commit 84e023607c)
2021-04-11 18:44:02 -04:00
Ken Swenson a45e273e2c Move deletion actions to the IO thread (#4808)
(cherry picked from commit f145fd0dec)
2021-04-11 18:43:55 -04:00
arkon 45cf4adb5b Update some dependencies; downgrade core-ktx
Fixes ActionMode being underneath statusbar

(cherry picked from commit 42a9f911d8)
2021-04-11 18:43:46 -04:00
arkon eb823cb208 Revert manga title folder for saved pages (closes #4803)
People also didn't like it making their galleries more complicate to navigate.

(cherry picked from commit 9567d55312)
2021-04-11 18:43:37 -04:00
arkon 056358fb9d Update to Gradle 7
(cherry picked from commit 531cd99247)
2021-04-11 18:43:27 -04:00
Ivan Iskandar 9e40625c08 Draw edge-to-edge (#4802)
(cherry picked from commit f3660d88dd)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryView.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2021-04-11 18:43:19 -04:00
arkon 9684e34241 [SKIP CI] Add lock workflow
(cherry picked from commit 3accb9a08b)
2021-04-11 18:41:47 -04:00
arkon 84fdd097e0 Update some internal dependencies
They no longer rely on jcenter

(cherry picked from commit 63ce7371bb)
2021-04-11 18:41:36 -04:00
Riztard Lanthorn a3c44fc5ad Search in library include manga description (#4787)
Co-Authored-By: jobobby04 <jobobby04@gmail.com>

Co-authored-by: jobobby04 <jobobby04@gmail.com>
(cherry picked from commit 01c3498dbf)
2021-04-11 18:41:28 -04:00
Taco 196e437da5 Update NDK, more KTX usage (#4792)
* Update NDK

* Utilize more KTX extensions

(cherry picked from commit b3471234ad)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
2021-04-11 18:41:20 -04:00
arkon 5e8b5ef6cf Add clarification for category exclusion (closes #4777)
(cherry picked from commit b2d697131c)
2021-04-11 18:40:31 -04:00
arkon 1ba07466ef Minor cleanup
(cherry picked from commit ef49fc91d8)
2021-04-11 18:40:23 -04:00
arkon eb88c9c94b Flip crop borders and orientation toggles
(cherry picked from commit 6222b47a4f)

# Conflicts:
#	app/src/main/res/layout/reader_activity.xml
2021-04-11 18:40:12 -04:00
arkon d6cab9f9a5 Update Kotlin and Kotlinter
(cherry picked from commit f58e3c390a)
2021-04-11 18:39:06 -04:00
arkon bcc120056c Make reader spinner colors a bit more consistent
(cherry picked from commit 7504621a24)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
2021-04-11 18:38:58 -04:00
arkon 0e8aec7929 Align filter spinners (closes #2995)
(cherry picked from commit 88e49a9b8b)
2021-04-11 18:37:13 -04:00
Jobobby04 2d4e589db8 Search browse if tag clicked in manga from Index controller 2021-04-11 18:36:41 -04:00
Jobobby04 3eecf5cb20 Disable Mangadex delegation 2021-04-11 18:17:06 -04:00
Jobobby04 6b08889c15 Use a Enum for MigrationStatus 2021-04-06 13:39:25 -04:00
Jobobby04 3bf070d88a Use material dialogs for exclude from automatic deletion 2021-04-04 21:40:26 -04:00
arkon 6d9753f361 Revert using fetch date for updates list
Spamming the list post-migration is currently a more common usecase than sources without chapter dates. We'll need to figure out a better way of handling both scenarios.

(cherry picked from commit 5b23f29d06)
2021-04-04 19:12:44 -04:00
arkon f6b9867ce8 Fix global update category exclusion
(cherry picked from commit c1bdebee78)
2021-04-04 19:12:36 -04:00
Riztard Lanthorn 03366ae7e5 add sort by date fetched in library (#4773)
* add sort by date fetched in library

* chapter fetch date to 8

(cherry picked from commit ddd4cc10ff)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySort.kt
2021-04-04 19:12:25 -04:00
arkon a70a6cbe49 Allow excluding categories from auto-download
Closes #1412

Supersedes #4121

(cherry picked from commit 0ca62a4acc)
2021-04-04 19:10:51 -04:00
arkon b5a109440f Allow excluding categories from library update
Closes #3467, #4661, #1839

Supersedes #4474

(cherry picked from commit 4f1275ac01)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
2021-04-04 19:10:42 -04:00
arkon f6acf9325a Use Material Dialogs for auto-download categories preference
To allow for negative selections in the future.

(cherry picked from commit b2fee7035f)
2021-04-04 19:09:25 -04:00
arkon b0c0b12499 Use Material Dialogs for global update categories preference
To allow for negative selections in the future.

(cherry picked from commit e15d7cb548)
2021-04-04 19:09:16 -04:00
arkon 30ed1f11ee Fix label overflow for reader spinner preferences
(cherry picked from commit 3257cbe21f)
2021-04-04 19:09:07 -04:00
arkon 3077dc24ec Move BiometricUtil to correct package
(cherry picked from commit 1237af1ff3)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt
2021-04-04 19:08:57 -04:00
arkon c2e882cb5b Allow weaker unlock methods (closes #4265)
(cherry picked from commit 68600b337e)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt
2021-04-04 19:08:22 -04:00
arkon 835351f206 Use app name for page download folder and use manga title subfolders (closes #4684)
(cherry picked from commit dac2072eaa)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
2021-04-04 19:07:44 -04:00
arkon cee8335518 Make extension load error logs less verbose
(cherry picked from commit 1b921f9845)
2021-04-04 19:07:01 -04:00
arkon 3aa5a36fdd Minor cleanup
(cherry picked from commit a3992d9fbe)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2021-04-04 19:05:54 -04:00
Tooster 74795bcc5e Replace reading mode snackbar with toast (#4752)
(cherry picked from commit efd2a0cb7b)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-04-04 19:00:54 -04:00
Andreas 38a46825e2 Remove weird cropping from icon when showing missing chapter warning (#4769)
(cherry picked from commit fba428257b)
2021-04-04 18:56:56 -04:00
arkon 7073e9b9e5 Don't repeatedly vibrate/make sounds on download progress
(cherry picked from commit ff36901007)
2021-04-04 18:56:48 -04:00
arkon 620887f90b Add QuadStateCheckBox view
(cherry picked from commit 940d8389b5)
2021-04-04 18:56:39 -04:00
arkon e38a0d47ac Better handle webtoon SSIV crop border change
(cherry picked from commit 7aa379a857)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
2021-04-04 18:56:31 -04:00
arkon eb9de3e6f1 Add tooltips for previous/next chapter buttons
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/d0738f5b00dfc2f6225cf7a20758b61dcb720168

(cherry picked from commit 1657f04d55)
2021-04-04 18:54:49 -04:00
Jobobby04 37d9a51706 Lint 2021-04-04 18:54:18 -04:00
Jobobby04 acb9bafa0a Only enable search when mode is Catalogue 2021-04-04 18:54:07 -04:00
Jobobby04 7c4e89cbc5 Fix crashing when loading a chapter from manual search in migration 2021-03-31 16:40:38 -04:00
Jobobby04 5842765eda Update crashlytics and fast adapter 2021-03-31 14:32:07 -04:00
Jobobby04 0925bd6a37 Use a buffered reader instead of a scanner for custom manga info 2021-03-31 14:31:39 -04:00
Jobobby04 2ddf5f5037 Fix some search bugs when using the latest/browse menu 2021-03-31 14:31:08 -04:00
Jobobby04 367d95c825 Logging fixes and lint 2021-03-31 14:29:27 -04:00
Jobobby04 6951314744 Fix migration getting stopped when opening views under it 2021-03-31 01:23:51 -04:00
Jobobby04 d294db3e4e Continues -> Continuous 2021-03-30 20:00:55 -04:00
arkon b2cf1266ba Recreate webtoon SSIV when crop borders setting changes (fixes #4734)
(cherry picked from commit 407e798fdb)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt
2021-03-30 19:24:46 -04:00
arkon fb01b547de Add icon for crop border shortcut off state
(cherry picked from commit 4054f2a6a0)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/res/layout/reader_activity.xml
2021-03-30 19:24:46 -04:00
arkon d3482ef734 Allow translating DNS over HTTPS (closes #4747)
(cherry picked from commit 468cdf603c)
2021-03-30 19:24:45 -04:00
Jobobby04 d622c659eb Fix toggle crop borders button a bit 2021-03-30 19:24:45 -04:00
arkon d1c497aa60 Fix nav overlay always showing on start (fixes #4736)
(cherry picked from commit 988ec6a224)
2021-03-30 19:24:44 -04:00
Andreas 29a882eebb Remove insert page when dual page split get turned off (#4739)
(cherry picked from commit bdbdf211e2)
2021-03-30 19:24:44 -04:00
Johannes Joens 90ffb8cdf6 add support for Repos with Numbers in their name (#255)
* add support for Repos with Numbers in their name

* Update strings_sy.xml

changed invalid_repo_name to better reflect its meaning
2021-03-30 19:23:52 -04:00
Jays2Kings dc760c0596 Backing up custom data for manga
Using 800s from J2k in BackupManga for this(except for status)

(cherry picked from commit c21b91bc026213993a67089ef4bc76c68ade4445)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/CustomMangaManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
#	app/src/main/res/values/strings.xml
2021-03-28 19:46:11 -04:00
arkon 7be8062a2e Fix binding of intarray preferences (maybe fixes #4728)
(cherry picked from commit 0437703cbf)
2021-03-28 19:10:36 -04:00
arkon de9ce8f949 Use regular crop icon
(cherry picked from commit 71aa592111)
2021-03-28 19:10:27 -04:00
arkon 3c3f5cf35d Add crop borders shortcut
(cherry picked from commit d501c02f8b)

# Conflicts:
#	app/src/main/res/layout/reader_activity.xml
2021-03-28 19:10:15 -04:00
arkon 7407e22b4e Remove ALPHA from dual page split label
(cherry picked from commit 9daf0e78b8)
2021-03-28 19:05:12 -04:00
arkon 3a18e76089 Clean up SpinnerPreference a bit
(cherry picked from commit dfa07a5f35)
2021-03-28 19:05:02 -04:00
arkon fa67ff165e Show nav overlay on invert tap change
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/db4eca90e957d70a102aa631708a156c29418bd3

(cherry picked from commit 437c995d12)
2021-03-28 19:04:55 -04:00
mutsumi b9d2591e2a Fix Some Bangumi Track Bug (#4726)
(cherry picked from commit cc6ae9d1a8)
2021-03-28 19:04:46 -04:00
arkon 404a6a621a Prevent manga title from jumping (fixes #4709)
(cherry picked from commit c58e4f4dee)
2021-03-28 19:04:38 -04:00
arkon aa376dc3a5 Show number of manga per source in migrate menu (#4703)
(cherry picked from commit c87b0e77de)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/MigrationSourcesPresenter.kt
2021-03-28 19:04:30 -04:00
arkon 4ee110e225 Dismiss action toolbar after download action in updates (closes #4729)
(cherry picked from commit 355d5af8ae)
2021-03-28 19:01:28 -04:00
arkon 26d52f5ad7 Fix fullscreen not applying on opening reader (fixes #4723)
(cherry picked from commit 3d99a8ebdb)
2021-03-28 19:01:19 -04:00
arkon 8b37c27a73 Cleanup reader spinner layouts
(cherry picked from commit c4b975b777)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
#	app/src/main/res/layout/reader_webtoon_settings.xml
2021-03-28 19:01:08 -04:00
Antoine Gaudreau Simard 6e9043c633 Add onPause\onResume persistence to searchView. Fixes issue #3627 (#4494)
* Add onPause\onResume persistence to searchView. Fixes issue #3627

* New controller subclass with built-in SearchView support

* Implement new SearchableNucleusController in SourceController

* Add query to BasePresenter (for one field it is not worth create a subclass in my opinion), convert BrowseSourceController to inherit from SearchableNucleusController

* move to flows to fix an issue in GlobalSearch where it would trigger the search multiple times

* Continue conversion to SearchableNucleusController

* Convert LibraryController, convert to flows, Known ISSUE with empty string being posted after setting the query upon creation of UI

* Fix issues with the post being tide to the SearchView queue which is not processed until shown. Add COLLAPSING state capture which should wrap this up.

* refactoring & enforce @StringRes for queryHint

(cherry picked from commit 2911fe7a1a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt
2021-03-28 18:33:41 -04:00
arkon 2988524fd8 Clean up reader sheet spinner preferences
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/fe2543b9d5da176b1dbb95058d1bfc54400fd47a

Co-Authored-By: Jays2Kings
(cherry picked from commit 14c114756d)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
#	app/src/main/res/layout/reader_pager_settings.xml
#	app/src/main/res/layout/reader_webtoon_settings.xml
2021-03-28 18:01:39 -04:00
arkon 95c828bed6 Reduce height of sheet when on color filter tab
(cherry picked from commit e7a8107279)
2021-03-28 17:53:35 -04:00
arkon 8721d8c9ec Add tooltips to bottom reader menu items
(cherry picked from commit bff73b1b40)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-03-28 17:53:25 -04:00
arkon 5b9d2175e2 Reorganize reader sheet contents a bit
(cherry picked from commit c255f57d95)

# Conflicts:
#	app/src/main/res/layout/reader_general_settings.xml
2021-03-28 17:49:40 -04:00
arkon 75f0ab2f40 Split general and reading mode sheet settings
(cherry picked from commit 64c47bbaed)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderGeneralSettings.kt
#	app/src/main/res/layout/reader_general_settings.xml
#	app/src/main/res/layout/reader_pager_settings.xml
#	app/src/main/res/layout/reader_webtoon_settings.xml
2021-03-28 17:47:31 -04:00
arkon 709f76d53d Merge reader settings and color filter sheets
Heavily influenced by https://github.com/Jays2Kings/tachiyomiJ2K/commit/fe2543b9d5da176b1dbb95058d1bfc54400fd47a#diff-8f47d7b7b53769ac18c28fe9978140c6bef44709879567acab2c6ef3270cd3a8

(cherry picked from commit e0b7698d40)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderSettingsSheet.kt
#	app/src/main/res/layout/reader_activity.xml
#	app/src/main/res/layout/reader_settings_sheet.xml
2021-03-28 17:19:57 -04:00
arkon ac654340d8 Maybe make opening file picker for choosing backup file more reliable
(cherry picked from commit a01792ac9a)
2021-03-28 16:51:40 -04:00
arkon 438f64a358 Use more common MIME type for protobuf
(cherry picked from commit 3ba078f64c)
2021-03-28 16:51:29 -04:00
arkon 41aec8bc96 Show unread entries first when sorting by unread (closes #4711)
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/b212f8233e2d3ceffaddc5fcd1ef884e137dae2a

(cherry picked from commit a16240f123)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
2021-03-28 16:51:17 -04:00
arkon 97342723bf Update plugins
(cherry picked from commit e5a120e778)
2021-03-28 16:50:41 -04:00
Jays2Kings a1cb3afe77 Added Start/Finished Date Support to AniList
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/1e3de8a67f239a3126178b95f2b028fbba1e7633

Co-Authored-By: Jays2Kings
(cherry picked from commit 2ba60e9114)
2021-03-28 16:50:33 -04:00
CrepeTF 1165c57ffa Apply vertical seekbar hide logic to ReaderSettingsSheet
(cherry picked from commit 4e8006f329cc87438de9202cf0ac1d0d8ceb203f)
2021-03-22 21:10:23 -04:00
CrepeTF 565f005692 Vertical seekbar options hidden when force horizontal is enabled
(cherry picked from commit 4105d8de5618b4becd724a00070a2c5c43ba9c3c)
2021-03-22 21:07:34 -04:00
Ken Swenson 3a148c73ac Fix migration due to variable shadowing (#4689)
(cherry picked from commit 472ce5a5e4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt
2021-03-22 20:31:10 -04:00
Jobobby04 12962b3486 Minor cleanup 2021-03-22 20:25:46 -04:00
Jobobby04 75da7dcbdd Update dependancies 2021-03-22 20:11:43 -04:00
Jobobby04 f02e3ae28f More blocking fixes 2021-03-22 20:11:15 -04:00
arkon c6369ed73f Handle null Anilist start dates (fixes #4685)
(cherry picked from commit 99ba84c810)
2021-03-21 00:07:02 -04:00
arkon fae2bd7ab7 Minor code cleanup
(cherry picked from commit 78285bdf37)
2021-03-21 00:06:54 -04:00
Andreas 03912407d5 Add navigation layout overlay (#4683)
* Add navigation layout overlay

* Minor clean up

Destroy animator when done not on start
Move and change pref title
Add summary

(cherry picked from commit 5a7f2684b3)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
#	app/src/main/res/layout/reader_activity.xml
#	app/src/main/res/values/colors.xml
2021-03-21 00:06:46 -04:00
arkon 879b41e97d Fix chapters list getting updated from wrong thread (fixes #4505)
(cherry picked from commit d912a42249)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2021-03-21 00:04:17 -04:00
arkon 6c3a957733 Fix Bangumi search null image errors
(cherry picked from commit 6d8c4fb8b1)
2021-03-21 00:02:37 -04:00
arkon 3d7c00c057 Make tapping available extension row prompt install
(cherry picked from commit a63cecbfcb)
2021-03-21 00:02:25 -04:00
arkon 6e1adf6e04 Fix offline restore ignoring manga from not installed sources (fixes #4679)
(cherry picked from commit 4a5bceb4e4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
2021-03-21 00:02:16 -04:00
arkon 23091cf50a Update AGP
(cherry picked from commit 86541445b7)
2021-03-21 00:01:11 -04:00
Ken Swenson 78d49b0742 Implement migration for source search (#4657)
(cherry picked from commit b6e6f490e9)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt
2021-03-20 15:40:14 -04:00
scb261 30250e350f Limit query for recent chapters to 500 (#4678)
(cherry picked from commit 2145e878a4)
2021-03-20 14:57:30 -04:00
CrepeTF d9b3b7b266 Add option to force disable vertical seekbar
(cherry picked from commit b5df33bf14d4eea8421d2e1e6b488b79e6daa9f5)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
#	app/src/main/java/exh/log/Logging.kt
2021-03-20 14:56:51 -04:00
Jobobby04 5558790e15 Fix Sync Favorites 2021-03-19 15:16:42 -04:00
Jobobby04 a1a9b4b812 Lint 2021-03-18 21:55:25 -04:00
Jobobby04 aac2fcb7d4 Catch more mangadex exceptions 2021-03-18 19:48:06 -04:00
KokaKiwi 69ddd04256 [SKIP CI] Update README.md (#4667)
Fix link to Code of Conduct.

(cherry picked from commit 355f6db255)
2021-03-18 17:16:01 -04:00
Jobobby04 7624abbebd Close mangadex login response body 2021-03-18 17:11:28 -04:00
Jobobby04 67310ada53 Move depercated logging to the bottom of the file 2021-03-18 17:06:19 -04:00
Jobobby04 aa73670d50 Fix MDList not getting max chapter/marked as completed 2021-03-18 17:05:18 -04:00
Soitora 2bde782211 [SKIP CI] Add Code of Conduct (#4665)
* Add Code of Conduct

* Update badge section

* Add Code of Conduct link to README

* Change to relative links

(cherry picked from commit bc7632bf02)

# Conflicts:
#	README.md
2021-03-18 15:42:07 -04:00
arkon 7b01f0c608 Add icons for reading mode toggle
(cherry picked from commit 609d8c9685)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
#	app/src/main/res/layout/reader_activity.xml
2021-03-18 15:40:12 -04:00
arkon 781f4e393e Less janky enum iteration
(cherry picked from commit 2f08515455)
2021-03-18 15:36:26 -04:00
scb261 93c92b674d Use fetch date instead of upload date when querying recent chapters (#4645)
(cherry picked from commit 7f450e185d)
2021-03-18 15:36:17 -04:00
arkon 368f565942 Remove __cfduid cookie check
As per email:

Cloudflare is deprecating the __cfduid cookie and the cf-request-id headers. The __cfduid cookie will be removed on 10 May 2021 and the cf-request-id headers will be removed on 1 July. We expect that most customers will not have to take action as a result of this removal. [...] Starting on 10 May 2021, we will stop adding a “Set-Cookie” header on all HTTP responses. The last __cfduid cookies will expire 30 days after that.

(cherry picked from commit 747879b4ec)
2021-03-18 15:36:08 -04:00
Riztard Lanthorn 01c298bbc1 Library update freq: add 4 & 8 hours (#4557)
(cherry picked from commit 4193870fa6)
2021-03-18 15:36:01 -04:00
arkon 1399042efb Flip order of previous chapter reader transition text (closes #4608)
(cherry picked from commit cdc5de3f1b)
2021-03-18 15:35:50 -04:00
arkon b2bfccdeae Round snackbar corners
(cherry picked from commit bc34d4fa88)
2021-03-18 15:35:41 -04:00
arkon 0d46e00b31 Adjust reader navigation button ripples
(cherry picked from commit 6fd4af8736)
2021-03-18 15:35:33 -04:00
arkon 9aca115977 Refactor LibraryUpdateService a bit for future changes
(cherry picked from commit b5c2934270)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
2021-03-18 15:35:21 -04:00
arkon e31e71ad44 Remove online protobuf backup restore option
(cherry picked from commit 94f5117941)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt
2021-03-18 15:20:30 -04:00
arkon df950219f5 Use Material dialogs for preferences
Partially addresses #2907

(cherry picked from commit 112e233498)
2021-03-18 15:11:13 -04:00
arkon 23e4b661bc Tweak dialog corner radius
(cherry picked from commit 18b1326f3a)
2021-03-18 15:11:02 -04:00
arkon 7164f686d4 Add reading mode toggle
(cherry picked from commit 1e58b05ead)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/res/layout/reader_activity.xml
2021-03-18 15:10:53 -04:00
arkon 3122f783a9 Move reader setting related classes
(cherry picked from commit 938919bd9b)
2021-03-18 14:55:52 -04:00
arkon 6be8e2de3c Move clear history from advanced settings to history screen menu (closes #4613)
(cherry picked from commit b6b78994d8)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
2021-03-18 14:55:42 -04:00
arkon c092127404 Add "my" locale
(cherry picked from commit fddd8ce305)
2021-03-18 14:54:03 -04:00
Jozef Hollý e7dd5f3c25 Weblate translations (#4461)
Co-authored-by: Adaś <adam.prosniak@gmail.com>
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: Andreas E <andreas.everos@gmail.com>
Co-authored-by: Aung Myint Myat Oo <solidifyarmor@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Bail Adnan Farid <fks7dev@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Cream π <f.t.nayeem014@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: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: Eugene <eugcheung94@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Habibur Rahman <habiburr016@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Iuri Jikidze <ijiki16@freeuni.edu.ge>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jimly Asshiddiqy <j_mly@ymail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Murat Topuz <mrt_tpz@outlook.com>
Co-authored-by: Murilo Simionato Arnemann <murilo2110@hotmail.com>
Co-authored-by: Nick Koroghlishvili <n.koroglishvili5@gmail.com>
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: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Ryota Hasegawa <unkchn123456@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Yasin Chamsoy <tristeroni@gmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 赤城 悠 <hapipon815@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bg/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
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/fa/
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/ka/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ko/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/my/
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/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/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/Tachiyomi 0.x

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Adaś <adam.prosniak@gmail.com>
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: Andreas E <andreas.everos@gmail.com>
Co-authored-by: Aung Myint Myat Oo <solidifyarmor@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Bail Adnan Farid <fks7dev@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Cream π <f.t.nayeem014@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: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: Eugene <eugcheung94@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Habibur Rahman <habiburr016@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Iuri Jikidze <ijiki16@freeuni.edu.ge>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jimly Asshiddiqy <j_mly@ymail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Murat Topuz <mrt_tpz@outlook.com>
Co-authored-by: Murilo Simionato Arnemann <murilo2110@hotmail.com>
Co-authored-by: Nick Koroghlishvili <n.koroglishvili5@gmail.com>
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: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Ryota Hasegawa <unkchn123456@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Yasin Chamsoy <tristeroni@gmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 赤城 悠 <hapipon815@gmail.com>
(cherry picked from commit ccff337975)
2021-03-18 14:53:54 -04:00
arkon 142fc0e4a6 Disable sensor when using force orientation (closes #4618)
(cherry picked from commit fde6b7af4f)
2021-03-18 14:53:41 -04:00
arkon 300e04e8f6 Allow scrolling within reader color filter sheet (fixes #4612)
(cherry picked from commit 0657db7dcb)
2021-03-18 14:53:30 -04:00
Soitora 07f684ac9e Update URL for Local Manga guide (#4641)
(cherry picked from commit d1c2eaf6d5)
2021-03-18 14:52:54 -04:00
arkon 6840382df2 Dependency updates
(cherry picked from commit 91bb6b9016)
2021-03-18 14:52:29 -04:00
Jobobby04 c7b6216d24 Fix recursive call 2021-03-13 11:59:14 -05:00
Jobobby04 a989426d95 Sync Follows sync status choice 2021-03-12 18:22:21 -05:00
Jobobby04 d255ee805b Mdlist only set the status to reading if Unfollowed 2021-03-12 10:02:47 -05:00
Jobobby04 21240cad06 Cleanup 2021-03-11 22:39:46 -05:00
Jobobby04 5b8b10a96b Fix full backup restore locking up 2021-03-11 22:39:18 -05:00
Jobobby04 c600d45e84 Maybe fix EHentai dupes in browse issue 2021-03-11 22:39:17 -05:00
Jobobby04 e9fd6ab470 Revert "Experimental Backup Restore fix"
This reverts commit 3d507600cb.
2021-03-11 19:44:36 -05:00
Jobobby04 3d507600cb Experimental Backup Restore fix 2021-03-11 19:10:34 -05:00
Jobobby04 84abe044a3 Remove Hentai Cafe Delegation 2021-03-11 19:10:33 -05:00
Jobobby04 04200bb590 Cleanup 2021-03-11 19:10:33 -05:00
Jobobby04 42d49b7cba Log tracking errors 2021-03-11 19:10:32 -05:00
Jobobby04 5dace4fd74 Fix Mangadex Login, Fix Mangadex tracking, Set Mangadex track status to Reading on tracked 2021-03-11 19:10:32 -05:00
Jobobby04 ccdae6bb9a Deprecate throwable logging function, produces bad log 2021-03-11 19:10:31 -05:00
Jobobby04 984956ce95 Fix vertical reader scrollbar buttons cutoff 2021-03-11 19:10:31 -05:00
Jobobby04 0fd9b2a8f6 Fix migration not running sometimes 2021-03-11 19:10:31 -05:00
OncePunchedMan 39f4949189 Tweak "Hot Pink" theme (#239)
* fix backdrop

* remove unused line
2021-03-11 14:32:05 -05:00
OncePunchedMan f7d52e0372 Added "Hot Pink" theme (#238)
* first test

* added hot pink theme

* moved string to correct place
2021-03-10 19:28:07 -05:00
Riztard Lanthorn 6cad8411fe swap webview and filter (#235) 2021-03-08 22:44:31 -05:00
arkon f35abccfd9 Revert to core-ktx:1.5.0-beta01
Fixes bottom reader menu from being hidden behind navbar on Android 5.0.

(cherry picked from commit 90351c6e9e)
2021-03-07 23:21:37 -05:00
Jobobby04 f3573d16b4 Fix #154.6, downloading a merge in the library will properly download 2021-03-07 23:17:43 -05:00
Jobobby04 e6f288e2c9 Fix some cherry pick issues 2021-03-07 22:33:38 -05:00
arkon 833bd6e655 Automatically reopen issues when valid
(cherry picked from commit dd4740e54f)
2021-03-07 13:40:17 -05:00
inorichi 4a30c68cfc Fix a decoder crash with RAR files
(cherry picked from commit 48e7cbd76c)
2021-03-07 13:40:02 -05:00
arkon 346bd5f57a Hide subtitle in migration list of sources if no language set (i.e. uninstalled source)
(cherry picked from commit ae42f59102)
2021-03-07 13:39:41 -05:00
arkon c2e3b4d35a AndroidX dependency updates
(cherry picked from commit aa5861d3ca)
2021-03-07 13:38:41 -05:00
Andreas 1fdb03f7db Dual page split allow to have different setting for Paged and Webtoon (#4527)
(cherry picked from commit 7a64bf55cb)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
2021-03-07 13:38:17 -05:00
Jobobby04 da3681e602 Dont throw a exception if the request fails for mdlist tracking 2021-03-07 13:04:53 -05:00
Jobobby04 d64a8907eb Many small changes
- Remove unused gridlayout dependency
- Add RECIEVE_BOOT permission for EH updater
- Some suspending db IO calls
2021-03-07 02:47:48 -05:00
Jobobby04 7e91ae02f1 Upgrade logging, now maps timber to XLog, new logging functions 2021-03-07 00:23:23 -05:00
Jobobby04 9457b832fc Surface errors when saving/sharing covers 2021-03-04 19:10:08 -05:00
Jobobby04 d0561705fe Clear db now has a option to keep read manga 2021-03-04 19:10:07 -05:00
Jobobby04 3601968342 Cleanup data saver 2021-03-04 19:10:07 -05:00
Jobobby04 fa2cde79ba Add errors to browse + latest 2021-03-04 19:10:06 -05:00
Jobobby04 1827fe0ce1 Fix merged manga library download button trying to download from localsource 2021-03-04 19:10:06 -05:00
Jobobby04 3447e0c237 Custom status fix for clean titles 2021-03-04 19:10:05 -05:00
Jobobby04 4f9ae9cc75 Fix clear db not effecting merged manga 2021-03-04 19:10:05 -05:00
Jobobby04 cd1c6cbc89 Add manga with pages read to backup 2021-03-04 19:10:04 -05:00
Jobobby04 66cd4c9b40 Move custom theme strings to strings_sy 2021-03-04 19:10:04 -05:00
CrepeTF 2e1cf49d99 Reader PR (with vertical sidebar) (#216)
* Reader PR

* Dealt with conflicts + updates

* Adeed missing import
2021-03-04 19:08:40 -05:00
curche 0c150694e7 Change Reader settings layout (#231)
* Change Reader settings layout

This commit changes the way the Reader settings are displayed. The
fork specific settings for the reader have been moved to the bottom
instead of being sandwiched between settings from the main app.
Makes it look a better organised now

* restore Cts Vertical to before in Reader settings

the current layout of the Reader settings is thus
  - Reader/Defaults/Meta
  - Display
  - Reading
  - Paged
  - Webtoon
  - Continuous vertical
  - Navigation
  - Fork Settings

Changes made based on review at PR https://github.com/jobobby04/TachiyomiSY/pull/231
2021-02-28 13:33:29 -05:00
scb261 a4c10394b6 Split transition animation setting for webtoon and pager (#230)
* Split transition animation setting for webtoon and pager

* Move variables

* Rename config variables back
2021-02-26 14:51:51 -05:00
curche f78836dac4 Change Similar manga settings layout (#228)
* convert Credit string to strings_sy element

* remove redundant similar screen title in Similar Manga settings
2021-02-25 14:52:44 -05:00
Eugene c88de1ab1b Russian localization (#215)
* Russian localization

* Delete "translatable"

* almost not translated

* more fix

* fix 2

* add and fix 3

* fix 4

* Move Themes and rolback strings main
2021-02-24 18:07:55 -05:00
Jobobby04 9694c8310c Make sure some toasts are used in the main thread 2021-02-24 17:26:08 -05:00
inorichi 1b09eecfce Fix a decoder crash
(cherry picked from commit d4c9ab793f)
2021-02-24 17:16:12 -05:00
inorichi 853e8faec5 Support CMYK and YCCK JPEGs and fix bad PNG cropping
(cherry picked from commit 48d2849d97)
2021-02-24 17:16:05 -05:00
Andreas cfd2d43f1c Let users invert dual page split (#4470)
* Let users invert dual page split

* Use Activity lifecycleScope and cleanup invert logic

(cherry picked from commit 776610d0e6)
2021-02-24 17:15:44 -05:00
Andreas 1d3542b648 Add Right and Left to reader settings (#4489)
* Add Right and Left to settings

* Fix whoopsie and minor tweak to how the array is fetched

(cherry picked from commit 3a790f3d66)
2021-02-24 17:15:33 -05:00
arkon 6dc7b9de92 Add Twitter link to About section
(cherry picked from commit 7382042288)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
2021-02-24 17:15:23 -05:00
arkon 48a63e26f3 Add orientation toggle to bottom reader menu (not really)
(cherry picked from commit 33992d80bf)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt
#	app/src/main/res/layout/reader_activity.xml
2021-02-24 17:12:14 -05:00
arkon 33b1c93949 Reword bookmark strings to clarify it's for a chapter, not a page
(cherry picked from commit a92b0e567b)
2021-02-24 17:02:44 -05:00
arkon 7a115d8080 Adjust reader seekbar design
- Revert back to old prev/next chapter icons
- Make views taller for easier actions
- Use more consistent spacing
- Add ripples to prev/next chapter buttons

(cherry picked from commit 829a65e515)
2021-02-24 17:02:12 -05:00
arkon 9be7c5e6e1 Initial adoption of bottom reader menus from TachiyomiSY
Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
Co-authored-by: CrepeTF <CrepeTF@users.noreply.github.com>
(cherry picked from commit 89837e4ced)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/res/layout/reader_activity.xml
#	app/src/main/res/menu/reader.xml
2021-02-24 17:02:03 -05:00
arkon e38d1dfdc4 [SKIP CI] Add instructions on how to get crash logs in issue templates
(cherry picked from commit 03ad48c055)
2021-02-24 16:45:17 -05:00
arkon f1f993bf38 Rename drawable with more consistent naming
(cherry picked from commit ace1db21d1)
2021-02-24 16:44:46 -05:00
arkon 2845d8cc98 Allow clicking the toolbar to go to the manga
Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
(cherry picked from commit 8bb69c455b)
2021-02-24 16:44:20 -05:00
Jobobby04 0185d5f7d6 Fixes for browse + latest page 2021-02-24 16:18:13 -05:00
Jobobby04 079ca1d0b3 Small cleanup 2021-02-24 16:16:47 -05:00
Jobobby04 5a67d8169d Edit manga status + edit local manga fixes 2021-02-24 16:15:19 -05:00
Jobobby04 f1cb4c38a2 Fix Hentai Cafe and 8Muses with the new split extension 2021-02-15 19:35:50 -05:00
Jobobby04 50a5ec45b3 Do a bit of optimization and cleanup, remove old EH startup code 2021-02-14 21:24:26 -05:00
Jobobby04 f76216c038 Revert Jdk 11 update 2021-02-12 20:06:07 -05:00
arkon d55692dc0d [SKIP CI] Update to issue-closer-action@v2.0
(cherry picked from commit f4dd150b70)
2021-02-12 19:50:42 -05:00
arkon ded8f15913 Switch back to new image decoder for preview builds
(cherry picked from commit 2b35d22e25)
2021-02-12 19:50:31 -05:00
Jobobby04 845dbbfa1e Revert "Hide dedupe by priority"
This reverts commit 1a12caa487.
2021-02-12 19:49:51 -05:00
381 changed files with 10052 additions and 5622 deletions
-1
View File
@@ -1,2 +1 @@
github: inorichi
ko_fi: inorichi
+10 -2
View File
@@ -2,9 +2,15 @@
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0)
- I have updated all extensions
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
@@ -24,3 +30,5 @@ I acknowledge that:
## Other details
Additional details and attachments.
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
+10 -2
View File
@@ -9,9 +9,15 @@ labels: "bug"
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0)
- I have updated all extensions
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- I have tried the troubleshooting guide: https://tachiyomi.org/help/guides/troubleshooting-problems/
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
@@ -34,3 +40,5 @@ This happened instead.
## Other details
Additional details and attachments.
If you're experiencing crashes, share the crash logs from More → Settings → Advanced → Dump crash logs.
+7 -2
View File
@@ -9,9 +9,14 @@ labels: "feature"
I acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0)
- I have updated all extensions
- I have updated:
- To the latest version of the app (stable is v1.6.2)
- All extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 489 KiB

@@ -32,10 +32,10 @@ jobs:
- name: Clone repo
uses: actions/checkout@v2
- name: Set up JDK 11
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 11
java-version: 1.8
- name: Copy CI gradle.properties
run: |
+27 -28
View File
@@ -7,31 +7,30 @@ jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Autoclose when created in wrong repo
uses: arkon/issue-closer-action@v1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
type: title
regex: ".*THIS ISSUE IS IN THE WRONG REPO.*"
message: "@${issue.user.login} this issue was automatically closed because it was not opened in the correct repo, as the template mentioned."
- name: Autoclose when no short description provided
uses: arkon/issue-closer-action@v1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
type: title
regex: ".*<Write short description here>*"
message: "@${issue.user.login} this issue was automatically closed because you did not fill out the description in the title."
- name: Autoclose when body acknowledgement section not removed
uses: arkon/issue-closer-action@v1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
type: body
regex: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*"
message: "@${issue.user.login} this issue was automatically closed because the acknowledgment section was not removed."
- name: Autoclose when body requested information not filled out
uses: arkon/issue-closer-action@v1.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
type: body
regex: ".*\\* (Tachiyomi version|Android version|Device): \\?.*"
message: "@${issue.user.login} this issue was automatically closed because the requested information was not filled out."
- name: Autoclose issues
uses: arkon/issue-closer-action@v3.1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
rules: |
[
{
"type": "title",
"regex": ".*THIS ISSUE IS IN THE WRONG REPO.*",
"message": "It was not opened in the correct repo, as the template mentioned."
},
{
"type": "title",
"regex": ".*<Write short description here>*",
"message": "The description in the title was not filled out."
},
{
"type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed."
},
{
"type": "body",
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
"message": "Requested information in the template was not filled out."
}
]
+19
View File
@@ -0,0 +1,19 @@
name: Lock threads
on:
# Daily
schedule:
- cron: '0 * * * *'
# Manual trigger
workflow_dispatch:
inputs:
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '2'
pr-lock-inactive-days: '2'
+76
View File
@@ -0,0 +1,76 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at the Tachiyomi [Discord server](https://discord.gg/tachiyomi). All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
+8 -3
View File
@@ -1,6 +1,6 @@
| Preview Builds | Release Builds | Tachiyomi Support Server |
|-------|----------|----------|
| [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/latest) | [![Discord](https://img.shields.io/discord/349436576037732353.svg)](https://discord.gg/tachiyomi) |
| [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/latest) | [![Discord](https://img.shields.io/discord/349436576037732353.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/tachiyomi) |
# ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY
@@ -11,7 +11,7 @@ Tachiyomi is a free and open source manga reader for Android 5.0 and above. This
## Features
Features of Tachiyomi(original) include:
* Online reading from sources such as MangaDex, MangaSee, Mangakakalot, [and more](https://github.com/tachiyomiorg/tachiyomi-extensions)
* Online reading from [a variety of sources](https://github.com/tachiyomiorg/tachiyomi-extensions)
* Local reading of downloaded manga
* A configurable reader with multiple viewers, reading directions and other settings.
* [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
@@ -109,7 +109,12 @@ Source requests should be created at https://github.com/tachiyomiorg/tachiyomi-e
<details><summary>Contributing</summary>
See [CONTRIBUTING.md](https://github.com/tachiyomiorg/tachiyomi/blob/master/CONTRIBUTING.md).
See [CONTRIBUTING.md](./CONTRIBUTING.md).
</details>
<details><summary>Code of Conduct</summary>
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
</details>
## FAQ
+29 -31
View File
@@ -34,8 +34,8 @@ android {
minSdkVersion(AndroidConfig.minSdk)
targetSdkVersion(AndroidConfig.targetSdk)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
versionCode = 13
versionName = "1.5.0"
versionCode = 16
versionName = "1.6.2"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -95,6 +95,7 @@ android {
exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE")
exclude("META-INF/*.kotlin_module")
// Compatibility for two RxJava versions (EXH)
exclude("META-INF/rxjava.properties")
@@ -126,20 +127,20 @@ dependencies {
implementation("tachiyomi.sourceapi:source-api:1.1")
// AndroidX libraries
implementation("androidx.annotation:annotation:1.2.0-beta01")
implementation("androidx.appcompat:appcompat:1.3.0-beta01")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha02")
implementation("androidx.annotation:annotation:1.3.0-alpha01")
implementation("androidx.appcompat:appcompat:1.3.0-rc01")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha03")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.0-alpha2")
implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta01")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
implementation("androidx.core:core-ktx:1.5.0-beta01")
implementation("androidx.core:core-ktx:1.3.2")
implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.2.0-beta01")
implementation("androidx.recyclerview:recyclerview:1.2.0")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
val lifecycleVersion = "2.3.0-rc01"
val lifecycleVersion = "2.3.0"
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
@@ -150,7 +151,7 @@ dependencies {
// UI library
implementation("com.google.android.material:material:1.3.0")
"standardImplementation"("com.google.firebase:firebase-core:18.0.2")
"standardImplementation"("com.google.firebase:firebase-core:18.0.3")
// ReactiveX
implementation("io.reactivex:rxandroid:1.2.1")
@@ -159,7 +160,7 @@ dependencies {
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
// Network client
val okhttpVersion = "4.10.0-RC1"
val okhttpVersion = "4.9.1"
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
@@ -169,7 +170,7 @@ dependencies {
implementation("org.conscrypt:conscrypt-android:2.5.1")
// JSON
val kotlinSerializationVersion = "1.0.1"
val kotlinSerializationVersion = "1.1.0"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
implementation("com.google.code.gson:gson:2.8.6")
@@ -180,7 +181,7 @@ dependencies {
// Disk
implementation("com.jakewharton:disklrucache:2.0.2")
implementation("com.github.inorichi:unifile:e9ee588")
implementation("com.github.tachiyomiorg:unifile:17bec43")
implementation("com.github.junrar:junrar:7.4.0")
// HTML parser
@@ -193,7 +194,7 @@ dependencies {
implementation("io.requery:sqlite-android:3.33.0")
// Preferences
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.3")
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.4")
// Model View Presenter
val nucleusVersion = "3.0.0"
@@ -204,14 +205,12 @@ dependencies {
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
// Image library
val glideVersion = "4.11.0"
val glideVersion = "4.12.0"
implementation("com.github.bumptech.glide:glide:$glideVersion")
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
kapt("com.github.bumptech.glide:compiler:$glideVersion")
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:6caf219")
// TODO: switch to new decoder for stable releases
// implementation("com.github.tachiyomiorg:subsampling-scale-image-view:ca26317")
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:547d9c0")
// Logging
implementation("com.jakewharton.timber:timber:4.7.1")
@@ -229,7 +228,8 @@ dependencies {
implementation("eu.davidea:flexible-adapter-ui:1.0.0")
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("com.github.tachiyomiorg:DirectionalViewPager:7d0617d")
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0")
implementation("dev.chrisbanes.insetter:insetter:0.5.0")
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices
val materialDialogsVersion = "3.1.1"
@@ -242,7 +242,7 @@ dependencies {
implementation("com.bluelinelabs:conductor-support:2.1.5") {
exclude(group = "com.android.support")
}
implementation("com.github.tachiyomiorg:conductor-support-preference:1.1.1")
implementation("com.github.tachiyomiorg:conductor-support-preference:2.0.1")
// FlowBinding
val flowbindingVersion = "0.12.0"
@@ -256,7 +256,7 @@ dependencies {
implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
// Tests
testImplementation("junit:junit:4.13.1")
testImplementation("junit:junit:4.13.2")
testImplementation("org.assertj:assertj-core:3.16.1")
testImplementation("org.mockito:mockito-core:1.10.19")
@@ -267,12 +267,12 @@ dependencies {
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
val coroutinesVersion = "1.4.2"
val coroutinesVersion = "1.4.3"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
// For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6")
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
// SY -->
// [EXH] Android 7 SSL Workaround
@@ -285,11 +285,11 @@ dependencies {
implementation ("info.debatty:java-string-similarity:2.0.0")
// Firebase (EH)
implementation("com.google.firebase:firebase-analytics-ktx:18.0.0")
implementation("com.google.firebase:firebase-crashlytics-ktx:17.3.0")
implementation("com.google.firebase:firebase-analytics-ktx:18.0.3")
implementation("com.google.firebase:firebase-crashlytics-ktx:17.4.1")
// Better logging (EH)
implementation("com.elvishew:xlog:1.7.1")
implementation("com.elvishew:xlog:1.9.0")
// Debug utils (EH)
val debugOverlayVersion = "1.1.3"
@@ -302,12 +302,10 @@ dependencies {
implementation ("me.zhanghai.android.materialratingbar:library:1.4.0")
// JsonReader for similar manga
implementation("com.squareup.moshi:moshi:1.11.0")
implementation("com.squareup.moshi:moshi:1.12.0")
implementation("androidx.gridlayout:gridlayout:1.0.0")
implementation("com.mikepenz:fastadapter:5.3.4")
// SY -->
implementation("com.mikepenz:fastadapter:5.4.0")
// SY <--
}
tasks {
+11 -6
View File
@@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- For managing extensions -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@@ -32,7 +33,7 @@
android:largeHeap="true"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Tachiyomi.Light"
android:theme="@style/Theme.Base"
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".ui.main.MainActivity"
@@ -84,7 +85,7 @@
</activity>
<activity
android:name=".ui.security.BiometricUnlockActivity"
android:theme="@style/Theme.Splash" />
android:theme="@style/Theme.Base" />
<activity
android:name=".ui.webview.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize" />
@@ -149,6 +150,10 @@
android:name=".extension.util.ExtensionInstallActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name="exh.ui.login.EhLoginActivity"
android:label="EHentaiLogin" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
@@ -195,7 +200,7 @@
<activity
android:name="exh.ui.intercept.InterceptActivity"
android:label="@string/app_name"
android:theme="@style/Theme.EHActivity">
android:theme="@style/Theme.Base">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -315,7 +320,7 @@
android:scheme="https" />
<!-- MangaDex -->
<data
<!--<data
android:scheme="https"
android:host="www.mangadex.org"
android:pathPrefix="/manga/" />
@@ -364,12 +369,12 @@
<data
android:scheme="https"
android:host="www.mangadex.cc"
android:pathPrefix="/chapter/" />
android:pathPrefix="/chapter/" />-->
</intent-filter>
</activity>
<activity
android:name="exh.ui.captcha.BrowserActionActivity"
android:theme="@style/Theme.EHActivity" />
android:theme="@style/Theme.Base" />
</application>
</manifest>
+24 -51
View File
@@ -22,7 +22,6 @@ import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.ms_square.debugoverlay.DebugOverlay
@@ -36,11 +35,11 @@ import exh.log.CrashlyticsPrinter
import exh.log.EHDebugModeOverlay
import exh.log.EHLogLevel
import exh.log.EnhancedFilePrinter
import exh.log.XLogTree
import exh.log.xLogD
import exh.log.xLogE
import exh.syDebugVersion
import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.conscrypt.Conscrypt
import timber.log.Timber
import uy.kohesive.injekt.Injekt
@@ -51,7 +50,6 @@ 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
import kotlin.time.days
@@ -59,12 +57,11 @@ open class App : Application(), LifecycleObserver {
private val preferences: PreferencesHelper by injectLazy()
private lateinit var firebaseAnalytics: FirebaseAnalytics
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
// if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
setupExhLogging() // EXH logging
Timber.plant(XLogTree()) // SY Redirect Timber to XLog
if (!BuildConfig.DEBUG) addAnalytics()
workaroundAndroid7BrokenSSL()
@@ -78,7 +75,6 @@ open class App : Application(), LifecycleObserver {
setupNotificationChannels()
Realm.init(this)
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
setupDebugOverlay()
}
@@ -86,6 +82,9 @@ open class App : Application(), LifecycleObserver {
LocaleHelper.updateConfiguration(this, resources.configuration)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Reset Incognito Mode on relaunch
preferences.incognitoMode().set(false)
}
override fun attachBaseContext(base: Context) {
@@ -105,23 +104,22 @@ open class App : Application(), LifecycleObserver {
try {
SSLContext.getInstance("TLSv1.2")
} catch (e: NoSuchAlgorithmException) {
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
xLogE("Could not install Android 7 broken SSL workaround!", e)
}
try {
ProviderInstaller.installIfNeeded(applicationContext)
} catch (e: GooglePlayServicesRepairableException) {
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
xLogE("Could not install Android 7 broken SSL workaround!", e)
} catch (e: GooglePlayServicesNotAvailableException) {
XLog.tag("Init").e("Could not install Android 7 broken SSL workaround!", e)
xLogE("Could not install Android 7 broken SSL workaround!", e)
}
}
}
private fun addAnalytics() {
firebaseAnalytics = Firebase.analytics
if (syDebugVersion != "0") {
firebaseAnalytics.setUserProperty("preview_version", syDebugVersion)
Firebase.analytics.setUserProperty("preview_version", syDebugVersion)
}
}
@@ -137,36 +135,13 @@ open class App : Application(), LifecycleObserver {
Notifications.createChannels(this)
}
// EXH
private fun deleteOldMetadataRealm() {
val config = RealmConfiguration.Builder()
.name("gallery-metadata.realm")
.schemaVersion(3)
.deleteRealmIfMigrationNeeded()
.build()
Realm.deleteRealm(config)
// Delete old paper db files
listOf(
File(filesDir, "gallery-ex"),
File(filesDir, "gallery-perveden"),
File(filesDir, "gallery-nhentai")
).forEach {
if (it.exists()) {
thread {
it.deleteRecursively()
}
}
}
}
// EXH
private fun setupExhLogging() {
EHLogLevel.init(this)
val logLevel = when {
EHLogLevel.shouldLog(EHLogLevel.EXTRA) -> LogLevel.ALL
BuildConfig.DEBUG -> LogLevel.DEBUG
EHLogLevel.shouldLog(EHLogLevel.EXTREME) -> LogLevel.ALL
EHLogLevel.shouldLog(EHLogLevel.EXTRA) || BuildConfig.DEBUG -> LogLevel.DEBUG
else -> LogLevel.WARN
}
@@ -188,9 +163,8 @@ open class App : Application(), LifecycleObserver {
@OptIn(ExperimentalTime::class)
printers += EnhancedFilePrinter
.Builder(logFolder.absolutePath)
.fileNameGenerator(
object : DateFileNameGenerator() {
.Builder(logFolder.absolutePath) {
fileNameGenerator = object : DateFileNameGenerator() {
override fun generateFileName(logLevel: Int, timestamp: Long): String {
return super.generateFileName(
logLevel,
@@ -198,13 +172,12 @@ open class App : Application(), LifecycleObserver {
) + "-${BuildConfig.BUILD_TYPE}.log"
}
}
)
.flattener { timeMillis, level, tag, message ->
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
flattener { timeMillis, level, tag, message ->
"${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(level)}/$tag: $message"
}
cleanStrategy = FileLastModifiedCleanStrategy(7.days.toLongMilliseconds())
backupStrategy = NeverBackupStrategy()
}
.cleanStrategy(FileLastModifiedCleanStrategy(7.days.toLongMilliseconds()))
.backupStrategy(NeverBackupStrategy())
.build()
// Install Crashlytics in prod
if (!BuildConfig.DEBUG) {
@@ -216,8 +189,8 @@ open class App : Application(), LifecycleObserver {
*printers.toTypedArray()
)
XLog.tag("Init").d("Application booting...")
XLog.tag("Init").disableStackTrace().d(
xLogD("Application booting...")
xLogD(
"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" +
@@ -242,7 +215,7 @@ open class App : Application(), LifecycleObserver {
.install()
} catch (e: IllegalStateException) {
// Crashes if app is in background
XLog.tag("Init").e("Failed to initialize debug overlay, app in background?", e)
xLogE("Failed to initialize debug overlay, app in background?", e)
}
}
}
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi
import android.os.Build
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
@@ -9,6 +10,7 @@ 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.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
@@ -129,6 +131,26 @@ object Migrations {
context.toast(R.string.myanimelist_relogin)
}
}
if (oldVersion < 57) {
// Migrate DNS over HTTPS setting
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
if (wasDohEnabled) {
prefs.edit {
putInt(PreferenceKeys.dohProvider, PREF_DOH_CLOUDFLARE)
remove("enable_doh")
}
}
}
if (oldVersion < 59) {
// Reset rotation to Free after replacing Lock
preferences.rotation().set(1)
// Disable update check for Android 5.x users
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
UpdaterJob.cancelTask(context)
}
}
return true
}
@@ -6,6 +6,7 @@ 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.toMangaInfo
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
@@ -23,6 +24,10 @@ abstract class AbstractBackupManager(protected val context: Context) {
internal val trackManager: TrackManager by injectLazy()
protected val preferences: PreferencesHelper by injectLazy()
// SY -->
protected val customMangaManager: CustomMangaManager by injectLazy()
// SY <--
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String?
/**
@@ -7,6 +7,7 @@ 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.library.CustomMangaManager
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException
@@ -24,6 +25,10 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
protected val db: DatabaseHelper by injectLazy()
protected val trackManager: TrackManager by injectLazy()
// SY -->
protected val customMangaManager: CustomMangaManager by injectLazy()
// SY <--
var job: Job? = null
protected lateinit var backupManager: T
@@ -30,7 +30,12 @@ class BackupCreateService : Service() {
internal const val BACKUP_HISTORY_MASK = 0x4
internal const val BACKUP_TRACK = 0x8
internal const val BACKUP_TRACK_MASK = 0x8
internal const val BACKUP_ALL = 0xF
// SY -->
internal const val BACKUP_CUSTOM_INFO = 0x10
internal const val BACKUP_CUSTOM_INFO_MASK = 0x10
internal const val BACKUP_ALL = 0x1F
// SY <--
/**
* Returns the status of the service.
@@ -24,6 +24,7 @@ class BackupNotifier(private val context: Context) {
setSmallIcon(R.drawable.ic_tachi)
setAutoCancel(false)
setOngoing(true)
setOnlyAlertOnce(true)
}
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) {
@@ -41,7 +42,6 @@ class BackupNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.creating_backup))
setProgress(0, 0, true)
setOnlyAlertOnce(true)
}
builder.show(Notifications.ID_BACKUP_PROGRESS)
@@ -141,7 +141,7 @@ class BackupNotifier(private val context: Context) {
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_open_log),
context.getString(R.string.action_show_errors),
NotificationReceiver.openErrorLogPendingActivity(context, uri)
)
}
@@ -43,12 +43,11 @@ class BackupRestoreService : Service() {
* @param context context of application
* @param uri path of Uri
*/
fun start(context: Context, uri: Uri, mode: Int, online: Boolean?) {
fun start(context: Context, uri: Uri, mode: Int) {
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) }
}
ContextCompat.startForegroundService(context, intent)
}
@@ -119,13 +118,12 @@ 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
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.
backupRestore?.job?.cancel()
backupRestore = when (mode) {
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online)
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier)
else -> LegacyBackupRestore(this, notifier)
}
@@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATE
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_CUSTOM_INFO
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CUSTOM_INFO_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
@@ -29,11 +31,8 @@ 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.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.util.lang.launchIO
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.savedsearches.JsonSavedSearch
@@ -164,7 +163,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
*/
private fun backupMangaObject(manga: Manga, options: Int): BackupManga {
// Entry for this manga
val mangaObject = BackupManga.copyFrom(manga)
val mangaObject = BackupManga.copyFrom(manga /* SY --> */, if (options and BACKUP_CUSTOM_INFO_MASK == BACKUP_CUSTOM_INFO) customMangaManager else null /* SY <-- */)
// SY -->
if (manga.source == MERGED_SOURCE_ID) {
@@ -237,24 +236,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
/**
* Fetches manga information
*
* @param source source of manga
* @param manga manga that needs updating
* @return Updated manga info.
*/
suspend fun restoreMangaFetch(source: Source?, manga: Manga, online: Boolean): Manga {
return if (online && source != null /* SY --> */ && source !is MergedSource /* SY <-- */) {
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.also {
it.copyFrom(networkManga.toSManga())
it.favorite = manga.favorite
it.initialized = true
it.id = insertManga(manga)
}
} else {
manga.also {
it.initialized = it.description != null
it.id = insertManga(it)
}
fun restoreManga(manga: Manga): Manga {
return manga.also {
it.initialized = it.description != null
it.id = insertManga(it)
}
}
@@ -363,29 +351,26 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
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
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)
}
}
if (!isInDatabase) {
// Insert new sync. Let the db assign the id
track.id = null
trackToUpdate.add(track)
}
}
// Update database
@@ -394,47 +379,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
}
}
/**
* 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 dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter != null) {
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
}
}
chapter.manga_id = manga.id
}
// Filter the chapters that couldn't be found.
updateChapters(chapters.filter { it.id != null })
return true
}
internal fun restoreChaptersForMangaOffline(manga: Manga, chapters: List<Chapter>) {
internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
chapters.forEach { chapter ->
@@ -527,8 +472,9 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
}
}
internal suspend fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
manga.id?.let { mangaId ->
internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
val mangaId = manga.id ?: return
launchIO {
databaseHelper.getFlatMetadataForManga(mangaId).executeOnIO().let {
if (it == null) {
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId)
@@ -15,8 +15,7 @@ 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 eu.kanade.tachiyomi.data.library.CustomMangaManager
import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID
import okio.buffer
@@ -24,7 +23,7 @@ import okio.gzip
import okio.source
import java.util.Date
class FullBackupRestore(context: Context, notifier: BackupNotifier, private val online: Boolean) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
override suspend fun performRestore(uri: Uri): Boolean {
// SY -->
@@ -57,9 +56,11 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
return false
}
restoreManga(it, backup.backupCategories, online)
restoreManga(it, backup.backupCategories)
}
// TODO: optionally trigger online library + tracker update
return true
}
@@ -81,8 +82,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
}
// SY <--
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) {
var manga = backupManga.getMangaImpl()
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories
val history = backupManga.history
@@ -90,22 +91,17 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
// SY -->
val mergedMangaReferences = backupManga.mergedMangaReferences
val flatMetadata = backupManga.flatMetadata
val customManga = backupManga.getCustomMangaInfo()
// SY <--
// SY -->
manga = EXHMigrations.migrateBackupEntry(manga)
EXHMigrations.migrateBackupEntry(manga)
// SY <--
val source = backupManager.sourceManager.get(manga.source)
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
try {
if (source != null || !online) {
restoreMangaData(manga, source, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
} else {
errors.add(Date() to "${manga.title} [$sourceName]: ${context.getString(R.string.source_not_found_name, sourceName)}")
}
restoreMangaData(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
}
@@ -117,35 +113,35 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
* 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 suspend fun restoreMangaData(
private fun restoreMangaData(
manga: Manga,
source: Source?,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
customManga: CustomMangaManager.MangaJson?,
// SY -->
) {
val dbManga = backupManager.getMangaFromDatabase(manga)
db.inTransaction {
val dbManga = backupManager.getMangaFromDatabase(manga)
if (dbManga == null) {
// Manga not in database
restoreMangaFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online)
} else { // Manga in database
restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} 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)
restoreMangaNoFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
}
}
}
@@ -157,66 +153,60 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private suspend fun restoreMangaFetch(
source: Source?,
private fun restoreMangaFetch(
manga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
customManga: CustomMangaManager.MangaJson?,
// SY <--
) {
try {
val fetchedManga = backupManager.restoreMangaFetch(source, manga, online)
val fetchedManga = backupManager.restoreManga(manga)
fetchedManga.id ?: return
backupManager.restoreChaptersForManga(fetchedManga, chapters)
if (online && source != null) {
// SY -->
if (source !is MergedSource) {
updateChapters(source, fetchedManga, chapters)
}
// SY <--
} else {
backupManager.restoreChaptersForMangaOffline(fetchedManga, chapters)
}
restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
updateTracking(fetchedManga, tracks)
restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories /* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}")
}
}
private suspend fun restoreMangaNoFetch(
source: Source?,
private fun restoreMangaNoFetch(
backupManga: Manga,
chapters: List<Chapter>,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
online: Boolean
customManga: CustomMangaManager.MangaJson?,
// SY <--
) {
if (online && source != null) {
if (/* SY --> */ source !is MergedSource && /* SY <-- */ !backupManager.restoreChaptersForManga(backupManga, chapters)) {
updateChapters(source, backupManga, chapters)
}
} else {
backupManager.restoreChaptersForMangaOffline(backupManga, chapters)
}
backupManager.restoreChaptersForManga(backupManga, chapters)
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata)
updateTracking(backupManga, tracks)
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
}
private suspend fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>, mergedMangaReferences: List<BackupMergedMangaReference>, flatMetadata: BackupFlatMetadata?) {
private fun restoreExtraForManga(
manga: Manga,
categories: List<Int>,
history: List<BackupHistory>,
tracks: List<Track>,
backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?,
customManga: CustomMangaManager.MangaJson?,
// SY <--
) {
// Restore categories
backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
@@ -232,6 +222,10 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
// Restore flat metadata for metadata sources
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) }
// Restore Custom Info
customManga?.id = manga.id!!
customManga?.let { customMangaManager.saveMangaInfo(it) }
// SY <--
}
}
@@ -4,6 +4,7 @@ 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 eu.kanade.tachiyomi.data.library.CustomMangaManager
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@@ -36,7 +37,15 @@ data class BackupManga(
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
// SY specific values
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(),
@ProtoNumber(601) var flatMetadata: BackupFlatMetadata? = null
@ProtoNumber(601) var flatMetadata: BackupFlatMetadata? = null,
@ProtoNumber(602) var customStatus: Int = 0,
// J2K specific values
@ProtoNumber(800) var customTitle: String? = null,
@ProtoNumber(801) var customArtist: String? = null,
@ProtoNumber(802) var customAuthor: String? = null,
@ProtoNumber(803) var customDescription: String? = null,
@ProtoNumber(803) var customGenre: List<String>? = null
) {
fun getMangaImpl(): MangaImpl {
return MangaImpl().apply {
@@ -62,6 +71,29 @@ data class BackupManga(
}
}
// SY -->
fun getCustomMangaInfo(): CustomMangaManager.MangaJson? {
if (customTitle != null ||
customArtist != null ||
customAuthor != null ||
customDescription != null ||
customGenre != null ||
customStatus != 0
) {
return CustomMangaManager.MangaJson(
id = 0L,
title = customTitle,
author = customAuthor,
artist = customArtist,
description = customDescription,
genre = customGenre,
status = customStatus.takeUnless { it == 0 }
)
}
return null
}
// SY <--
fun getTrackingImpl(): List<TrackImpl> {
return tracking.map {
it.getTrackingImpl()
@@ -69,22 +101,35 @@ data class BackupManga(
}
companion object {
fun copyFrom(manga: Manga): BackupManga {
fun copyFrom(manga: Manga /* SY --> */, customMangaManager: CustomMangaManager?/* SY <-- */): 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,
// SY -->
title = manga.originalTitle,
artist = manga.originalArtist,
author = manga.originalAuthor,
description = manga.originalDescription,
genre = manga.getOriginalGenres() ?: emptyList(),
status = manga.originalStatus,
// SY <--
thumbnailUrl = manga.thumbnail_url,
favorite = manga.favorite,
source = manga.source,
dateAdded = manga.date_added,
viewer = manga.viewer,
chapterFlags = manga.chapter_flags
)
// SY -->
).also { backupManga ->
customMangaManager?.getManga(manga)?.let {
backupManga.customTitle = it.title
backupManga.customArtist = it.artist
backupManga.customAuthor = it.author
backupManga.customDescription = it.description
backupManga.customGenre = it.getGenres()
backupManga.customStatus = it.status
}
}
// SY <--
}
}
}
@@ -91,7 +91,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
// SY <--
private suspend fun restoreManga(mangaJson: JsonObject) {
/* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson<MangaImpl>(
val manga = backupManager.parser.fromJson<MangaImpl>(
mangaJson.get(
Backup.MANGA
)
@@ -114,7 +114,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
)
// EXH -->
manga = EXHMigrations.migrateBackupEntry(manga)
EXHMigrations.migrateBackupEntry(manga)
// <-- EXH
val source = backupManager.sourceManager.get(manga.source)
@@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.data.cache
import android.content.Context
import android.text.format.Formatter
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -15,10 +13,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Response
import okio.buffer
import okio.sink
import rx.Observable
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.io.IOException
@@ -48,14 +48,12 @@ class ChapterCache(private val context: Context) {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
/** Google Json class used for parsing JSON files. */
private val gson: Gson by injectLazy()
private val json: Json by injectLazy()
// --> EH
private val prefs: PreferencesHelper by injectLazy()
// <-- EH
/** Cache class used for cache management. */
// --> EH
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
init {
@@ -73,7 +71,7 @@ class ChapterCache(private val context: Context) {
/**
* Returns directory of cache.
*/
val cacheDir: File
private val cacheDir: File
get() = diskCache.directory
/**
@@ -100,43 +98,19 @@ class ChapterCache(private val context: Context) {
}
// <-- EH
/**
* Remove file from cache.
*
* @param file name of file "md5.0".
* @return status of deletion for the file.
*/
fun removeFileFromCache(file: String): Boolean {
// Make sure we don't delete the journal file (keeps track of cache).
if (file == "journal" || file.startsWith("journal.")) {
return false
}
return try {
// Remove the extension from the file to get the key of the cache
val key = file.substringBeforeLast(".")
// Remove file from cache.
diskCache.remove(key)
} catch (e: Exception) {
false
}
}
/**
* Get page list from cache.
*
* @param chapter the chapter.
* @return an observable of the list of pages.
* @return the list of pages.
*/
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> {
return Observable.fromCallable {
// Get the key for the chapter.
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
fun getPageListFromCache(chapter: Chapter): List<Page> {
// Get the key for the chapter.
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
// Convert JSON string to list of objects. Throws an exception if snapshot is null
diskCache.get(key).use {
gson.fromJson<List<Page>>(it.getString(0))
}
// Convert JSON string to list of objects. Throws an exception if snapshot is null
return diskCache.get(key).use {
json.decodeFromString(it.getString(0))
}
}
@@ -148,7 +122,7 @@ class ChapterCache(private val context: Context) {
*/
fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
// Convert list of pages to json string.
val cachedValue = gson.toJson(pages)
val cachedValue = json.encodeToString(pages)
// Initialize the editor (edits the values for an entry).
var editor: DiskLruCache.Editor? = null
@@ -228,6 +202,38 @@ class ChapterCache(private val context: Context) {
}
}
fun clear(): Int {
var deletedFiles = 0
cacheDir.listFiles()?.forEach {
if (removeFileFromCache(it.name)) {
deletedFiles++
}
}
return deletedFiles
}
/**
* Remove file from cache.
*
* @param file name of file "md5.0".
* @return status of deletion for the file.
*/
private fun removeFileFromCache(file: String): Boolean {
// Make sure we don't delete the journal file (keeps track of cache).
if (file == "journal" || file.startsWith("journal.")) {
return false
}
return try {
// Remove the extension from the file to get the key of the cache
val key = file.substringBeforeLast(".")
// Remove file from cache.
diskCache.remove(key)
} catch (e: Exception) {
false
}
}
private fun getKey(chapter: Chapter): String {
return "${chapter.manga_id}${chapter.url}"
}
@@ -59,8 +59,8 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
COL_DESCRIPTION to obj.originalDescription,
COL_GENRE to obj.originalGenre,
COL_TITLE to obj.originalTitle,
COL_STATUS to obj.originalStatus,
// SY <--
COL_STATUS to obj.status,
COL_THUMBNAIL_URL to obj.thumbnail_url,
COL_FAVORITE to obj.favorite,
COL_LAST_UPDATE to obj.last_update,
@@ -40,9 +40,11 @@ open class MangaImpl : Manga {
override var genre: String?
get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre
set(value) { ogGenre = value }
// SY <--
override var status: Int = 0
override var status: Int
get() = if (favorite) customMangaManager.getManga(this)?.status?.takeUnless { it == 0 } ?: ogStatus else ogStatus
set(value) { ogStatus = value }
// SY <--
override var thumbnail_url: String? = null
@@ -71,6 +73,8 @@ open class MangaImpl : Manga {
private set
var ogGenre: String? = null
private set
var ogStatus: Int = 0
private set
// SY <--
override fun equals(other: Any?): Boolean {
@@ -13,6 +13,7 @@ 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.MangaThumbnailPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
@@ -102,6 +103,11 @@ interface MangaQueries : DbProvider {
.`object`(manga)
.withPutResolver(MangaMigrationPutResolver())
.prepare()
fun updateMangaThumbnail(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaThumbnailPutResolver())
.prepare()
// SY <--
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
@@ -151,12 +157,38 @@ interface MangaQueries : DbProvider {
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE})")
.where(
"""
${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (
SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE} WHERE ${MergedTable.COL_MANGA_ID} != ${MergedTable.COL_MERGE_ID}
)
""".trimIndent()
)
.whereArgs(0)
.build()
)
.prepare()
// SY -->
fun deleteMangasNotInLibraryAndNotRead() = db.delete()
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.where(
"""
${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_ID} NOT IN (
SELECT ${MergedTable.COL_MANGA_ID} FROM ${MergedTable.TABLE} WHERE ${MergedTable.COL_MANGA_ID} != ${MergedTable.COL_MERGE_ID}
) AND ${MangaTable.COL_ID} NOT IN (
SELECT ${ChapterTable.COL_MANGA_ID} FROM ${ChapterTable.TABLE} WHERE ${ChapterTable.COL_READ} = 1 OR ${ChapterTable.COL_LAST_PAGE_READ} != 0
)
""".trimIndent()
)
.whereArgs(0)
.build()
)
.prepare()
// SY <--
fun deleteMangas() = db.delete()
.byQuery(
DeleteQuery.builder()
@@ -195,6 +227,16 @@ interface MangaQueries : DbProvider {
)
.prepare()
fun getChapterFetchDateManga() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getChapterFetchDateMangaQuery())
.observesTables(MangaTable.TABLE)
.build()
)
.prepare()
// SY -->
fun getMangaWithMetadata() = db.get()
.listOfObjects(Manga::class.java)
@@ -69,7 +69,7 @@ fun getReadMangaNotInLibraryQuery() =
SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 0 AND ${Manga.COL_ID} IN(
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} FROM ${Chapter.TABLE} WHERE ${Chapter.COL_READ} = 1
SELECT ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} FROM ${Chapter.TABLE} WHERE ${Chapter.COL_READ} = 1 OR ${Chapter.COL_LAST_PAGE_READ} != 0
)
"""
@@ -221,6 +221,16 @@ fun getLatestChapterMangaQuery() =
ORDER by max DESC
"""
fun getChapterFetchDateMangaQuery() =
"""
SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_FETCH}) AS max
FROM ${Manga.TABLE}
JOIN ${Chapter.TABLE}
ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID}
GROUP BY ${Manga.TABLE}.${Manga.COL_ID}
ORDER by max DESC
"""
/**
* Query to get the categories for a manga.
*/
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
@@ -9,6 +8,7 @@ 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
import exh.util.nullIfZero
class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
@@ -31,15 +31,20 @@ class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() {
MangaTable.COL_GENRE to manga.originalGenre,
MangaTable.COL_AUTHOR to manga.originalAuthor,
MangaTable.COL_ARTIST to manga.originalArtist,
MangaTable.COL_DESCRIPTION to manga.originalDescription
MangaTable.COL_DESCRIPTION to manga.originalDescription,
MangaTable.COL_STATUS to manga.originalStatus
)
fun resetToContentValues(manga: Manga) = ContentValues(1).apply {
val splitter = "▒ ▒∩▒"
put(MangaTable.COL_TITLE, manga.title.split(splitter).last())
put(MangaTable.COL_GENRE, manga.genre?.split(splitter)?.lastOrNull())
put(MangaTable.COL_AUTHOR, manga.author?.split(splitter)?.lastOrNull())
put(MangaTable.COL_ARTIST, manga.artist?.split(splitter)?.lastOrNull())
put(MangaTable.COL_DESCRIPTION, manga.description?.split(splitter)?.lastOrNull())
private fun resetToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_TITLE to manga.title.split(splitter).last(),
MangaTable.COL_GENRE to manga.genre?.split(splitter)?.lastOrNull(),
MangaTable.COL_AUTHOR to manga.author?.split(splitter)?.lastOrNull(),
MangaTable.COL_ARTIST to manga.artist?.split(splitter)?.lastOrNull(),
MangaTable.COL_DESCRIPTION to manga.description?.split(splitter)?.lastOrNull(),
MangaTable.COL_STATUS to manga.status.nullIfZero()?.toString()?.split(splitter)?.lastOrNull()
)
companion object {
const val splitter = "▒ ▒∩▒"
}
}
@@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.data.database.resolvers
import androidx.core.content.contentValuesOf
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
// SY
class MangaThumbnailPutResolver : 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) = contentValuesOf(
MangaTable.COL_THUMBNAIL_URL to manga.thumbnail_url
)
}
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.lang.launchIO
import rx.Observable
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
@@ -23,7 +24,7 @@ import uy.kohesive.injekt.injectLazy
*
* @param context the application context.
*/
class DownloadManager(/* SY private */ val context: Context) {
class DownloadManager(private val context: Context) {
private val sourceManager: SourceManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
@@ -211,16 +212,16 @@ class DownloadManager(/* SY private */ val context: Context) {
*/
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
val filteredChapters = getChaptersToDelete(chapters)
launchIO {
removeFromDownloadQueue(filteredChapters)
removeFromDownloadQueue(filteredChapters)
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
chapterDirs.forEach { it.delete() }
cache.removeChapters(filteredChapters, manga)
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
chapterDirs.firstOrNull()?.parentFile?.delete()
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
chapterDirs.forEach { it.delete() }
cache.removeChapters(filteredChapters, manga)
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
chapterDirs.firstOrNull()?.parentFile?.delete()
}
}
return filteredChapters
}
@@ -262,7 +263,7 @@ class DownloadManager(/* SY private */ val context: Context) {
if (removeNonFavorite && !manga.favorite) {
val mangaFolder = provider.getMangaDir(manga, source)
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0)
cleaned += 1 + mangaFolder.listFiles().orEmpty().size
mangaFolder.delete()
cache.removeManga(manga)
return cleaned
@@ -283,8 +284,7 @@ class DownloadManager(/* SY private */ val context: Context) {
if (cache.getDownloadCount(manga) == 0) {
val mangaFolder = provider.getMangaDir(manga, source)
val size = mangaFolder.listFiles()?.size ?: 0
if (size == 0) {
if (!mangaFolder.listFiles().isNullOrEmpty()) {
mangaFolder.delete()
cache.removeManga(manga)
} else {
@@ -302,9 +302,11 @@ class DownloadManager(/* SY private */ val context: Context) {
* @param source the source of the manga.
*/
fun deleteManga(manga: Manga, source: Source) {
downloader.queue.remove(manga)
provider.findMangaDir(manga, source)?.delete()
cache.removeManga(manga)
launchIO {
downloader.queue.remove(manga)
provider.findMangaDir(manga, source)?.delete()
cache.removeManga(manga)
}
}
/**
@@ -27,6 +27,8 @@ internal class DownloadNotifier(private val context: Context) {
private val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setAutoCancel(false)
setOnlyAlertOnce(true)
}
}
@@ -81,10 +83,8 @@ internal class DownloadNotifier(private val context: Context) {
*/
fun onProgressChange(download: Download) {
with(progressNotificationBuilder) {
// Check if first call.
if (!isDownloading) {
setSmallIcon(android.R.drawable.stat_sys_download)
setAutoCancel(false)
clearActions()
// Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
@@ -114,6 +114,7 @@ internal class DownloadNotifier(private val context: Context) {
}
setProgress(download.pages!!.size, download.downloadedImages, false)
setOngoing(true)
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
}
@@ -127,8 +128,8 @@ internal class DownloadNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.chapter_paused))
setContentText(context.getString(R.string.download_notifier_download_paused))
setSmallIcon(R.drawable.ic_pause_24dp)
setAutoCancel(false)
setProgress(0, 0, false)
setOngoing(false)
clearActions()
// Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
@@ -217,7 +218,6 @@ internal class DownloadNotifier(private val context: Context) {
setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
setSmallIcon(android.R.drawable.stat_sys_warning)
clearActions()
setAutoCancel(false)
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
setProgress(0, 0, false)
@@ -53,8 +53,8 @@ class DownloadProvider(private val context: Context) {
return downloadsDir
.createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga))
} catch (e: NullPointerException) {
Timber.w(e)
} catch (e: Throwable) {
Timber.e(e, "Invalid download directory")
throw Exception(context.getString(R.string.invalid_download_dir))
}
}
@@ -65,7 +65,7 @@ class DownloadProvider(private val context: Context) {
* @param source the source to query.
*/
fun findSourceDir(source: Source): UniFile? {
return downloadsDir.findFile(getSourceDirName(source))
return downloadsDir.findFile(getSourceDirName(source), true)
}
/**
@@ -76,7 +76,7 @@ class DownloadProvider(private val context: Context) {
*/
fun findMangaDir(manga: Manga, source: Source): UniFile? {
val sourceDir = findSourceDir(source)
return sourceDir?.findFile(getMangaDirName(manga))
return sourceDir?.findFile(getMangaDirName(manga), true)
}
/**
@@ -89,7 +89,7 @@ class DownloadProvider(private val context: Context) {
fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source)
return getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") }
.mapNotNull { mangaDir?.findFile(it, true) ?: mangaDir?.findFile("$it.cbz", true) }
.firstOrNull()
}
@@ -123,14 +123,12 @@ class DownloadProvider(private val context: Context) {
source: Source
): List<UniFile> {
val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return mangaDir.listFiles()!!.asList().filter {
(
chapters.find { chp ->
getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
}
} == null
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
return mangaDir.listFiles().orEmpty().asList().filter {
chapters.find { chp ->
getValidChapterDirNames(chp).any { dir ->
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
}
} == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
}
}
// SY <--
@@ -141,7 +139,7 @@ class DownloadProvider(private val context: Context) {
* @param source the source to query.
*/
fun getSourceDirName(source: Source): String {
return source.toString()
return DiskUtil.buildValidFilename(source.toString())
}
/**
@@ -178,6 +176,7 @@ class DownloadProvider(private val context: Context) {
return listOf(
getChapterDirName(chapter),
// TODO: remove this
// Legacy chapter directory name used in v0.9.2 and before
DiskUtil.buildValidFilename(chapter.name)
)
@@ -8,7 +8,6 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.util.Scanner
class CustomMangaManager(val context: Context) {
@@ -23,7 +22,7 @@ class CustomMangaManager(val context: Context) {
val json = try {
Json.decodeFromString<MangaList>(
Scanner(editJson).useDelimiter("\\Z").next()
editJson.bufferedReader().use { it.readText() }
)
} catch (e: Exception) {
null
@@ -32,30 +31,15 @@ class CustomMangaManager(val context: Context) {
val mangasJson = json.mangas ?: return mutableMapOf()
return mangasJson.mapNotNull { mangaJson ->
val id = mangaJson.id ?: return@mapNotNull null
val manga = MangaImpl().apply {
this.id = id
title = mangaJson.title ?: ""
author = mangaJson.author
artist = mangaJson.artist
description = mangaJson.description
genre = mangaJson.genre?.joinToString(", ")
}
id to manga
id to mangaJson.toManga()
}.toMap().toMutableMap()
}
fun saveMangaInfo(manga: MangaJson) {
if (manga.title == null && manga.author == null && manga.artist == null && manga.description == null && manga.genre == null) {
if (manga.title == null && manga.author == null && manga.artist == null && manga.description == null && manga.genre == null && manga.status == null) {
customMangaMap.remove(manga.id!!)
} else {
customMangaMap[manga.id!!] = MangaImpl().apply {
id = manga.id
title = manga.title ?: ""
author = manga.author
artist = manga.artist
description = manga.description
genre = manga.genre?.joinToString(", ")
}
customMangaMap[manga.id!!] = manga.toManga()
}
saveCustomInfo()
}
@@ -75,7 +59,8 @@ class CustomMangaManager(val context: Context) {
author,
artist,
description,
genre?.split(", ")
genre?.split(", "),
status
)
}
@@ -86,24 +71,23 @@ class CustomMangaManager(val context: Context) {
@Serializable
data class MangaJson(
val id: Long? = null,
var id: Long? = null,
val title: String? = null,
val author: String? = null,
val artist: String? = null,
val description: String? = null,
val genre: List<String>? = null
val genre: List<String>? = null,
val status: Int? = null
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MangaJson
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id.hashCode()
fun toManga() = MangaImpl().apply {
id = this@MangaJson.id
title = this@MangaJson.title ?: ""
author = this@MangaJson.author
artist = this@MangaJson.artist
description = this@MangaJson.description
genre = this@MangaJson.genre?.joinToString(", ")
status = this@MangaJson.status ?: 0
}
}
}
@@ -111,7 +111,7 @@ class LibraryUpdateNotifier(private val context: Context) {
setContentIntent(errorLogIntent)
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_open_log),
context.getString(R.string.action_show_errors),
errorLogIntent
)
}
@@ -41,7 +41,7 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.isServiceRunning
import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil
import exh.metadata.metadata.base.insertFlatMetadata
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.LIBRARY_UPDATE_EXCLUDED_SOURCES
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
@@ -88,6 +88,7 @@ class LibraryUpdateService(
private lateinit var notifier: LibraryUpdateNotifier
private lateinit var ioScope: CoroutineScope
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
private var updateJob: Job? = null
/**
@@ -109,6 +110,8 @@ class LibraryUpdateService(
companion object {
private var instance: LibraryUpdateService? = null
/**
* Key for category to update.
*/
@@ -147,7 +150,7 @@ class LibraryUpdateService(
* @return true if service newly started, false otherwise
*/
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS /* SY --> */, group: Int = LibraryGroup.BY_DEFAULT, groupExtra: String? = null /* SY <-- */): Boolean {
if (!isRunning(context)) {
return if (!isRunning(context)) {
val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(KEY_TARGET, target)
category?.let { putExtra(KEY_CATEGORY, it.id) }
@@ -158,10 +161,11 @@ class LibraryUpdateService(
}
ContextCompat.startForegroundService(context, intent)
return true
true
} else {
instance?.addMangaToQueue(category?.id ?: -1, group, groupExtra, target)
false
}
return false
}
/**
@@ -198,6 +202,9 @@ class LibraryUpdateService(
if (wakeLock.isHeld) {
wakeLock.release()
}
if (instance == this) {
instance = null
}
super.onDestroy()
}
@@ -221,23 +228,27 @@ class LibraryUpdateService(
val target = intent.getSerializableExtra(KEY_TARGET) as? Target
?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed.
instance = this
// Unsubscribe from any previous subscription if needed
updateJob?.cancel()
// Update favorite manga. Destroy service when completed or in case of an error.
val selectedScheme = preferences.libraryUpdatePrioritization().get()
val mangaList = getMangaToUpdate(intent, target)
.sortedWith(rankingScheme[selectedScheme])
// Update favorite manga
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
val groupExtra = intent.getStringExtra(KEY_GROUP_EXTRA)
addMangaToQueue(categoryId, group, groupExtra, target)
// Destroy service when completed or in case of an error.
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
stopSelf(startId)
}
updateJob = ioScope.launch(handler) {
when (target) {
Target.CHAPTERS -> updateChapterList(mangaList)
Target.COVERS -> updateCovers(mangaList)
Target.TRACKING -> updateTrackings(mangaList)
Target.CHAPTERS -> updateChapterList()
Target.COVERS -> updateCovers()
Target.TRACKING -> updateTrackings()
// SY -->
Target.SYNC_FOLLOWS -> syncFollows()
Target.PUSH_FAVORITES -> pushFavorites()
@@ -250,36 +261,40 @@ class LibraryUpdateService(
}
/**
* Returns the list of manga to be updated.
* Adds list of manga to be updated.
*
* @param intent the update intent.
* @param category the ID of the category to update, or -1 if no category specified.
* @param target the target to update.
* @return a list of manga to update
*/
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
fun addMangaToQueue(categoryId: Int, group: Int, groupExtra: String?, target: Target) {
val libraryManga = db.getLibraryMangas().executeAsBlocking()
// SY -->
val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
// SY <--
var listToUpdate = if (categoryId != -1) {
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId }
libraryManga.filter { it.category == categoryId }
// SY -->
} else if (group == LibraryGroup.BY_DEFAULT || groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.GLOBAL || (groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)) {
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
if (categoriesToUpdate.isNotEmpty()) {
db.getLibraryMangas().executeAsBlocking()
.filter { it.category in categoriesToUpdate }
.distinctBy { it.id }
val listToInclude = if (categoriesToUpdate.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToUpdate }
} else {
db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
libraryManga
}
val categoriesToExclude = preferences.libraryUpdateCategoriesExclude().get().map(String::toInt)
val listToExclude = if (categoriesToExclude.isNotEmpty()) {
libraryManga.filter { it.category in categoriesToExclude }
} else {
emptyList()
}
listToInclude.minus(listToExclude)
} else {
val libraryManga = db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
when (group) {
LibraryGroup.BY_TRACK_STATUS -> {
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
val trackingExtra = groupExtra?.toIntOrNull() ?: -1
libraryManga.filter {
val loggedServices = trackManager.services.filter { it.isLogged }
val status: String = run {
@@ -298,12 +313,12 @@ class LibraryUpdateService(
}
}
LibraryGroup.BY_SOURCE -> {
val sourceExtra = intent.getStringExtra(KEY_GROUP_EXTRA).nullIfBlank()
val sourceExtra = groupExtra.nullIfBlank()
val source = sourceManager.getCatalogueSources().find { it.name == sourceExtra }
if (source != null) libraryManga.filter { it.source == source.id } else emptyList()
}
LibraryGroup.BY_STATUS -> {
val statusExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1
val statusExtra = groupExtra?.toIntOrNull() ?: -1
libraryManga.filter {
it.status == statusExtra
}
@@ -314,10 +329,13 @@ class LibraryUpdateService(
// SY <--
}
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filter { it.status != SManga.COMPLETED }
listToUpdate = listToUpdate.filterNot { it.status == SManga.COMPLETED }
}
return listToUpdate
val selectedScheme = preferences.libraryUpdatePrioritization().get()
mangaToUpdate = listToUpdate
.distinctBy { it.id }
.sortedWith(rankingScheme[selectedScheme])
}
/**
@@ -329,7 +347,7 @@ class LibraryUpdateService(
* @param mangaToUpdate the list to update
* @return an observable delivering the progress of each update.
*/
suspend fun updateChapterList(mangaToUpdate: List<LibraryManga>) {
suspend fun updateChapterList() {
val semaphore = Semaphore(5)
val progressCount = AtomicInteger(0)
val newUpdates = mutableListOf<Pair<LibraryManga, Array<Chapter>>>()
@@ -463,7 +481,7 @@ class LibraryUpdateService(
return syncChaptersWithSource(db, chapters, manga, source)
}
private suspend fun updateCovers(mangaToUpdate: List<LibraryManga>) {
private suspend fun updateCovers() {
var progressCount = 0
mangaToUpdate.forEach { manga ->
@@ -496,7 +514,7 @@ class LibraryUpdateService(
* Method that updates the metadata of the connected tracking services. It's called in a
* background thread, so it's safe to do heavy operations or network calls here.
*/
private suspend fun updateTrackings(mangaToUpdate: List<LibraryManga>) {
private suspend fun updateTrackings() {
var progressCount = 0
val loggedServices = trackManager.services.filter { it.isLogged }
@@ -539,11 +557,12 @@ class LibraryUpdateService(
private suspend fun syncFollows() {
val count = AtomicInteger(0)
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return
val syncFollowStatusInts = preferences.mangadexSyncToLibraryIndexes().get().map { it.toInt() }
val size: Int
mangaDex.fetchAllFollows(true)
.filter { (_, metadata) ->
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int
syncFollowStatusInts.contains(metadata.follow_status)
}
.also { size = it.size }
.forEach { (networkManga, metadata) ->
@@ -569,7 +588,7 @@ class LibraryUpdateService(
val id = db.insertManga(dbManga).executeOnIO().insertedId()
if (id != null) {
metadata.mangaId = id
db.insertFlatMetadata(metadata.flatten()).await()
db.insertFlatMetadataAsync(metadata.flatten()).await()
}
}
@@ -74,7 +74,7 @@ class NotificationReceiver : BroadcastReceiver() {
shareFile(
context,
intent.getParcelableExtra(EXTRA_URI),
if (intent.getBooleanExtra(EXTRA_IS_LEGACY_BACKUP, false)) "application/json" else "application/octet-stream+gzip",
if (intent.getBooleanExtra(EXTRA_IS_LEGACY_BACKUP, false)) "application/json" else "application/x-protobuf+gzip",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
ACTION_CANCEL_RESTORE -> cancelRestore(
@@ -17,13 +17,21 @@ object PreferenceKeys {
const val rotation = "pref_rotation_type_key"
const val enableTransitions = "pref_enable_transitions_key"
const val enableTransitionsPager = "pref_enable_transitions_pager_key"
const val enableTransitionsWebtoon = "pref_enable_transitions_webtoon_key"
const val doubleTapAnimationSpeed = "pref_double_tap_anim_speed"
const val showPageNumber = "pref_show_page_number_key"
const val dualPageSplit = "pref_dual_page_split"
const val dualPageSplitPaged = "pref_dual_page_split"
const val dualPageSplitWebtoon = "pref_dual_page_split_webtoon"
const val dualPageInvertPaged = "pref_dual_page_invert"
const val dualPageInvertWebtoon = "pref_dual_page_invert_webtoon"
const val showReadingMode = "pref_show_reading_mode"
@@ -73,6 +81,10 @@ object PreferenceKeys {
const val navigationModeWebtoon = "reader_navigation_mode_webtoon"
const val showNavigationOverlayNewUser = "reader_navigation_overlay_new_user"
const val showNavigationOverlayOnStart = "reader_navigation_overlay_on_start"
const val webtoonSidePadding = "webtoon_side_padding"
const val portraitColumns = "pref_library_columns_portrait_key"
@@ -114,6 +126,7 @@ object PreferenceKeys {
const val libraryUpdateRestriction = "library_update_restriction"
const val libraryUpdateCategories = "library_update_categories"
const val libraryUpdateCategoriesExclude = "library_update_categories_exclude"
const val libraryUpdatePrioritization = "library_update_prioritization"
@@ -158,6 +171,7 @@ object PreferenceKeys {
const val downloadNew = "download_new"
const val downloadNewCategories = "download_new_categories"
const val downloadNewCategoriesExclude = "download_new_categories_exclude"
const val libraryDisplayMode = "pref_display_mode_library"
@@ -183,7 +197,7 @@ object PreferenceKeys {
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
const val enableDoh = "enable_doh"
const val dohProvider = "doh_provider"
const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
@@ -199,6 +213,8 @@ object PreferenceKeys {
const val incognitoMode = "incognito_mode"
const val createLegacyBackup = "create_legacy_backup"
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
@@ -313,6 +329,8 @@ object PreferenceKeys {
const val mangadexSimilarOnlyOverWifi = "pref_simular_only_over_wifi_key"
const val mangadexSyncToLibraryIndexes = "pref_mangadex_sync_to_library_indexes"
const val preferredMangaDexId = "preferred_mangaDex_id"
const val dataSaver = "data_saver"
@@ -339,11 +357,15 @@ object PreferenceKeys {
const val sortTagsForLibrary = "sort_tags_for_library"
const val createLegacyBackup = "create_legacy_backup"
const val dontDeleteFromCategories = "dont_delete_from_categories"
const val extensionRepos = "extension_repos"
const val cropBordersContinuesVertical = "crop_borders_continues_vertical"
const val cropBordersContinuousVertical = "crop_borders_continues_vertical"
const val landscapeVerticalSeekbar = "pref_show_vert_seekbar_landscape"
const val leftVerticalSeekbar = "pref_left_handed_vertical_seekbar"
const val forceHorizontalSeekbar = "pref_force_horz_seekbar"
}
@@ -5,6 +5,8 @@ package eu.kanade.tachiyomi.data.preference
*/
object PreferenceValues {
/* ktlint-disable experimental:enum-entry-name-case */
// Keys are lowercase to match legacy string values
enum class ThemeMode {
light,
@@ -22,11 +24,15 @@ object PreferenceValues {
enum class DarkThemeVariant {
default,
blue,
amoledblue,
amoled,
red,
midnightdusk,
hotpink,
}
/* ktlint-enable experimental:enum-entry-name-case */
enum class DisplayMode {
COMPACT_GRID,
COMFORTABLE_GRID,
@@ -22,7 +22,7 @@ import java.util.Locale
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
fun <T> Preference<T>.asImmediateFlow(block: (value: T) -> Unit): Flow<T> {
fun <T> Preference<T>.asImmediateFlow(block: (T) -> Unit): Flow<T> {
block(get())
return asFlow()
.onEach { block(it) }
@@ -36,6 +36,10 @@ operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
set(get() - item)
}
fun Preference<Boolean>.toggle() {
set(!get())
}
class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@@ -83,13 +87,21 @@ class PreferencesHelper(val context: Context) {
fun rotation() = flowPrefs.getInt(Keys.rotation, 1)
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true)
fun pageTransitionsPager() = flowPrefs.getBoolean(Keys.enableTransitionsPager, true)
fun pageTransitionsWebtoon() = flowPrefs.getBoolean(Keys.enableTransitionsWebtoon, true)
fun doubleTapAnimSpeed() = flowPrefs.getInt(Keys.doubleTapAnimationSpeed, 500)
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
fun dualPageSplit() = flowPrefs.getBoolean(Keys.dualPageSplit, false)
fun dualPageSplitPaged() = flowPrefs.getBoolean(Keys.dualPageSplitPaged, false)
fun dualPageSplitWebtoon() = flowPrefs.getBoolean(Keys.dualPageSplitWebtoon, false)
fun dualPageInvertPaged() = flowPrefs.getBoolean(Keys.dualPageInvertPaged, false)
fun dualPageInvertWebtoon() = flowPrefs.getBoolean(Keys.dualPageInvertWebtoon, false)
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
@@ -143,6 +155,10 @@ class PreferencesHelper(val context: Context) {
fun navigationModeWebtoon() = flowPrefs.getInt(Keys.navigationModeWebtoon, 0)
fun showNavigationOverlayNewUser() = flowPrefs.getBoolean(Keys.showNavigationOverlayNewUser, true)
fun showNavigationOverlayOnStart() = flowPrefs.getBoolean(Keys.showNavigationOverlayOnStart, false)
fun portraitColumns() = flowPrefs.getInt(Keys.portraitColumns, 0)
fun landscapeColumns() = flowPrefs.getInt(Keys.landscapeColumns, 0)
@@ -204,6 +220,7 @@ class PreferencesHelper(val context: Context) {
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi"))
fun libraryUpdateCategories() = flowPrefs.getStringSet(Keys.libraryUpdateCategories, emptySet())
fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet(Keys.libraryUpdateCategoriesExclude, emptySet())
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
@@ -254,6 +271,7 @@ class PreferencesHelper(val context: Context) {
fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false)
fun downloadNewCategories() = flowPrefs.getStringSet(Keys.downloadNewCategories, emptySet())
fun downloadNewCategoriesExclude() = flowPrefs.getStringSet(Keys.downloadNewCategoriesExclude, emptySet())
fun lang() = prefs.getString(Keys.lang, "")
@@ -267,7 +285,7 @@ class PreferencesHelper(val context: Context) {
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet())
fun enableDoh() = prefs.getBoolean(Keys.enableDoh, false)
fun dohProvider() = prefs.getInt(Keys.dohProvider, -1)
fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "")
@@ -425,6 +443,8 @@ class PreferencesHelper(val context: Context) {
fun mangadexSimilarOnlyOverWifi() = flowPrefs.getBoolean(Keys.mangadexSimilarOnlyOverWifi, true)
fun mangadexSyncToLibraryIndexes() = flowPrefs.getStringSet(Keys.mangadexSyncToLibraryIndexes, emptySet())
fun mangadexSimilarUpdateInterval() = flowPrefs.getInt(Keys.mangadexSimilarUpdateInterval, 2)
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
@@ -455,5 +475,11 @@ class PreferencesHelper(val context: Context) {
fun extensionRepos() = flowPrefs.getStringSet(Keys.extensionRepos, emptySet())
fun cropBordersContinuesVertical() = flowPrefs.getBoolean(Keys.cropBordersContinuesVertical, false)
fun cropBordersContinuousVertical() = flowPrefs.getBoolean(Keys.cropBordersContinuousVertical, false)
fun forceHorizontalSeekbar() = flowPrefs.getBoolean(Keys.forceHorizontalSeekbar, false)
fun landscapeVerticalSeekbar() = flowPrefs.getBoolean(Keys.landscapeVerticalSeekbar, false)
fun leftVerticalSeekbar() = flowPrefs.getBoolean(Keys.leftVerticalSeekbar, false)
}
@@ -35,6 +35,8 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
private val api by lazy { AnilistApi(client, interceptor) }
override val supportsReadingDates: Boolean = true
private val scorePreference = preferences.anilistScoreType()
init {
@@ -2,6 +2,9 @@ package eu.kanade.tachiyomi.data.track.anilist
import android.net.Uri
import androidx.core.net.toUri
import com.afollestad.date.dayOfMonth
import com.afollestad.date.month
import com.afollestad.date.year
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
@@ -9,6 +12,7 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
@@ -30,8 +34,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun addLibManga(track: Track): Track {
return withIOContext {
val query =
"""
val query = """
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id
@@ -65,10 +68,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun updateLibManga(track: Track): Track {
return withIOContext {
val query =
"""
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) {
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) {
val query = """
|mutation UpdateManga(
|${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus,
|${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|) {
|SaveMediaListEntry(
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status,
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|) {
|id
|status
|progress
@@ -82,6 +90,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus())
put("score", track.score.toInt())
put("startedAt", createDate(track.started_reading_date))
put("completedAt", createDate(track.finished_reading_date))
}
}
authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
@@ -92,8 +102,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
val query =
"""
val query = """
|query Search(${'$'}query: String) {
|Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@@ -143,8 +152,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun findLibManga(track: Track, userid: Int): Track? {
return withIOContext {
val query =
"""
val query = """
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@@ -152,6 +160,16 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|status
|scoreRaw: score(format: POINT_100)
|progress
|startedAt {
|year
|month
|day
|}
|completedAt {
|year
|month
|day
|}
|media {
|id
|title {
@@ -209,8 +227,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun getCurrentUser(): Pair<Int, String> {
return withIOContext {
val query =
"""
val query = """
|query User {
|Viewer {
|id
@@ -243,21 +260,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
private fun jsonToALManga(struct: JsonObject): ALManga {
val date = try {
val date = Calendar.getInstance()
date.set(
struct["startDate"]!!.jsonObject["year"]!!.jsonPrimitive.intOrNull ?: 0,
(
struct["startDate"]!!.jsonObject["month"]!!.jsonPrimitive.intOrNull
?: 0
) - 1,
struct["startDate"]!!.jsonObject["day"]!!.jsonPrimitive.intOrNull ?: 0
)
date.timeInMillis
} catch (_: Exception) {
0L
}
return ALManga(
struct["id"]!!.jsonPrimitive.int,
struct["title"]!!.jsonObject["romaji"]!!.jsonPrimitive.content,
@@ -265,7 +267,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
struct["description"]!!.jsonPrimitive.contentOrNull,
struct["type"]!!.jsonPrimitive.content,
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
date,
parseDate(struct, "startDate"),
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0
)
}
@@ -276,10 +278,44 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
struct["status"]!!.jsonPrimitive.content,
struct["scoreRaw"]!!.jsonPrimitive.int,
struct["progress"]!!.jsonPrimitive.int,
parseDate(struct, "startedAt"),
parseDate(struct, "completedAt"),
jsonToALManga(struct["media"]!!.jsonObject)
)
}
private fun parseDate(struct: JsonObject, dateKey: String): Long {
return try {
val date = Calendar.getInstance()
date.set(
struct[dateKey]!!.jsonObject["year"]!!.jsonPrimitive.int,
struct[dateKey]!!.jsonObject["month"]!!.jsonPrimitive.int - 1,
struct[dateKey]!!.jsonObject["day"]!!.jsonPrimitive.int
)
date.timeInMillis
} catch (_: Exception) {
0L
}
}
private fun createDate(dateValue: Long): JsonObject {
if (dateValue == 0L) {
return buildJsonObject {
put("year", JsonNull)
put("month", JsonNull)
put("day", JsonNull)
}
}
val calendar = Calendar.getInstance()
calendar.timeInMillis = dateValue
return buildJsonObject {
put("year", calendar.year)
put("month", calendar.month + 1)
put("day", calendar.dayOfMonth)
}
}
companion object {
private const val clientId = "385"
private const val apiUrl = "https://graphql.anilist.co/"
@@ -44,6 +44,8 @@ data class ALUserManga(
val list_status: String,
val score_raw: Int,
val chapters_read: Int,
val start_date_fuzzy: Long,
val completed_date_fuzzy: Long,
val manga: ALManga
) {
@@ -51,6 +53,8 @@ data class ALUserManga(
media_id = manga.media_id
status = toTrackStatus()
score = score_raw.toFloat()
started_reading_date = start_date_fuzzy
finished_reading_date = completed_date_fuzzy
last_chapter_read = chapters_read
library_id = this@ALUserManga.library_id
total_chapters = manga.total_chapters
@@ -45,8 +45,10 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return if (remoteTrack != null && statusTrack != null) {
track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id
track.status = remoteTrack.status
track.last_chapter_read = remoteTrack.last_chapter_read
track.status = statusTrack.status
track.score = statusTrack.score
track.last_chapter_read = statusTrack.last_chapter_read
track.total_chapters = remoteTrack.total_chapters
refresh(track)
} else {
// Set default fields if it's not found in the list
@@ -66,7 +68,6 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
track.copyPersonalFrom(remoteStatusTrack!!)
api.findLibManga(track)?.let { remoteTrack ->
track.total_chapters = remoteTrack.total_chapters
track.status = remoteTrack.status
}
return track
}
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@@ -46,6 +47,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
return withIOContext {
// read status update
val sbody = FormBody.Builder()
.add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus())
.build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody))
@@ -91,12 +93,24 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
}
private fun jsonToSearch(obj: JsonObject): TrackSearch {
val coverUrl = if (obj["images"] is JsonObject) {
obj["images"]?.jsonObject?.get("common")?.jsonPrimitive?.contentOrNull ?: ""
} else {
// Sometimes JsonNull
""
}
val totalChapters = if (obj["eps_count"] != null) {
obj["eps_count"]!!.jsonPrimitive.int
} else {
0
}
return TrackSearch.create(TrackManager.BANGUMI).apply {
media_id = obj["id"]!!.jsonPrimitive.int
title = obj["name_cn"]!!.jsonPrimitive.content
cover_url = obj["images"]!!.jsonObject["common"]!!.jsonPrimitive.content
cover_url = coverUrl
summary = obj["name"]!!.jsonPrimitive.content
tracking_url = obj["url"]!!.jsonPrimitive.content
total_chapters = totalChapters
}
}
@@ -119,14 +133,21 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.build()
// TODO: get user readed chapter here
authClient.newCall(requestUserRead)
.await()
.parseAs<Collection>()
.let {
var response = authClient.newCall(requestUserRead).await()
var responseBody = response.body?.string().orEmpty()
if (responseBody.isEmpty()) {
throw Exception("Null Response")
}
if (responseBody.contains("\"code\":400")) {
null
} else {
json.decodeFromString<Collection>(responseBody).let {
track.status = it.status?.id!!
track.last_chapter_read = it.ep_status!!
track.score = it.rating!!
track
}
}
}
}
@@ -8,7 +8,7 @@ data class Collection(
val comment: String? = "",
val ep_status: Int? = 0,
val lasttouch: Int? = 0,
val rating: Int? = 0,
val rating: Float? = 0f,
val status: Status? = Status(),
val tag: List<String?>? = listOf(),
val user: User? = User(),
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil
import tachiyomi.source.model.MangaInfo
@@ -46,80 +47,84 @@ class MdList(private val context: Context, id: Int) : TrackService(id) {
override suspend fun add(track: Track): Track = update(track)
override suspend fun update(track: Track): Track {
val mdex = mdex ?: throw MangaDexNotFoundException()
return withIOContext {
val mdex = mdex ?: throw MangaDexNotFoundException()
val remoteTrack = mdex.fetchTrackingInfo(track.tracking_url)
val followStatus = FollowStatus.fromInt(track.status)
val remoteTrack = mdex.fetchTrackingInfo(track.tracking_url)
val followStatus = FollowStatus.fromInt(track.status)
// this updates the follow status in the metadata
// allow follow status to update
if (remoteTrack.status != followStatus.int) {
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus)
remoteTrack.status = followStatus.int
// db.insertFlatMetadataAsync(mangaMetadata.flatten()).await()
}
if (track.score.toInt() > 0) {
mdex.updateRating(track)
}
// mangadex wont update chapters if manga is not follows this prevents unneeded network call
if (followStatus != FollowStatus.UNFOLLOWED) {
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
track.status = FollowStatus.COMPLETED.int
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), FollowStatus.COMPLETED)
}
if (followStatus == FollowStatus.PLAN_TO_READ && track.last_chapter_read > 0) {
val newFollowStatus = FollowStatus.READING
track.status = FollowStatus.READING.int
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), newFollowStatus)
remoteTrack.status = newFollowStatus.int
// db.insertFlatMetadataAsync(mangaMetadata.flatten()).await()
// this updates the follow status in the metadata
// allow follow status to update
if (remoteTrack.status != followStatus.int) {
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus)
remoteTrack.status = followStatus.int
}
mdex.updateReadingProgress(track)
} else if (track.last_chapter_read != 0) {
// When followStatus has been changed to unfollowed 0 out read chapters since dex does
track.last_chapter_read = 0
if (track.score.toInt() > 0) {
mdex.updateRating(track)
}
// mangadex wont update chapters if manga is not follows this prevents unneeded network call
if (followStatus != FollowStatus.UNFOLLOWED) {
if (track.total_chapters != 0 && track.last_chapter_read == track.total_chapters) {
track.status = FollowStatus.COMPLETED.int
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), FollowStatus.COMPLETED)
}
if (followStatus == FollowStatus.PLAN_TO_READ && track.last_chapter_read > 0) {
val newFollowStatus = FollowStatus.READING
track.status = FollowStatus.READING.int
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), newFollowStatus)
remoteTrack.status = newFollowStatus.int
}
mdex.updateReadingProgress(track)
} else if (track.last_chapter_read != 0) {
// When followStatus has been changed to unfollowed 0 out read chapters since dex does
track.last_chapter_read = 0
}
track
}
return track
}
override fun getCompletionStatus(): Int = FollowStatus.COMPLETED.int
override suspend fun bind(track: Track): Track = update(refresh(track))
override suspend fun bind(track: Track): Track = update(refresh(track).also { if (it.status == FollowStatus.UNFOLLOWED.int) it.status = FollowStatus.READING.int })
override suspend fun refresh(track: Track): Track {
val mdex = mdex ?: throw MangaDexNotFoundException()
val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track)
track.copyPersonalFrom(remoteTrack)
if (track.total_chapters == 0 && mangaMetadata.status == SManga.COMPLETED) {
track.total_chapters = mangaMetadata.maxChapterNumber ?: 0
return withIOContext {
val mdex = mdex ?: throw MangaDexNotFoundException()
val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track)
track.copyPersonalFrom(remoteTrack)
if (track.total_chapters == 0 && mangaMetadata.status == SManga.COMPLETED) {
track.total_chapters = mangaMetadata.maxChapterNumber ?: 0
}
track
}
return track
}
fun createInitialTracker(dbManga: Manga, mdManga: Manga = dbManga): Track {
val track = Track.create(TrackManager.MDLIST)
track.manga_id = dbManga.id!!
track.status = FollowStatus.UNFOLLOWED.int
track.tracking_url = MdUtil.baseUrl + mdManga.url
track.title = mdManga.title
return track
return Track.create(TrackManager.MDLIST).apply {
manga_id = dbManga.id!!
status = FollowStatus.UNFOLLOWED.int
tracking_url = MdUtil.baseUrl + mdManga.url
title = mdManga.title
}
}
override suspend fun search(query: String): List<TrackSearch> {
val mdex = mdex ?: throw MangaDexNotFoundException()
return mdex.fetchSearchManga(0, query, mdex.getFilterList())
.flatMap { page ->
runAsObservable({
page.mangas.map {
toTrackSearch(mdex.getMangaDetails(it.toMangaInfo()))
}
})
}
.awaitSingle()
return withIOContext {
val mdex = mdex ?: throw MangaDexNotFoundException()
mdex.fetchSearchManga(0, query, mdex.getFilterList())
.flatMap { page ->
runAsObservable({
page.mangas.map {
toTrackSearch(mdex.getMangaDetails(it.toMangaInfo()))
}
})
}
.awaitSingle()
}
}
private fun toTrackSearch(mangaInfo: MangaInfo): TrackSearch = TrackSearch.create(TrackManager.MDLIST).apply {
@@ -131,5 +136,8 @@ class MdList(private val context: Context, id: Int) : TrackService(id) {
override suspend fun login(username: String, password: String): Unit = throw Exception("not used")
override val isLogged: Boolean
get() = false
class MangaDexNotFoundException : Exception("Mangadex not enabled")
}
@@ -11,9 +11,6 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
private val json: Json by injectLazy()
private var oauth: OAuth? = null
set(value) {
field = value?.copy(expires_in = System.currentTimeMillis() + (value.expires_in * 1000))
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@@ -24,21 +21,19 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
if (oauth == null) {
oauth = myanimelist.loadOAuth()
}
// Refresh access token if null or expired.
if (oauth!!.isExpired()) {
// Refresh access token if expired
if (oauth != null && oauth!!.isExpired()) {
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
if (it.isSuccessful) {
setAuth(json.decodeFromString(it.body!!.string()))
}
}
}
// Throw on null auth.
if (oauth == null) {
throw Exception("No authentication token")
}
// Add the authorization header to the original request.
// Add the authorization header to the original request
val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build()
@@ -7,8 +7,9 @@ data class OAuth(
val refresh_token: String,
val access_token: String,
val token_type: String,
val created_at: Long = System.currentTimeMillis(),
val expires_in: Long
) {
fun isExpired() = System.currentTimeMillis() > expires_in
fun isExpired() = System.currentTimeMillis() > created_at + (expires_in * 1000)
}
@@ -1,9 +1,6 @@
package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
@@ -11,52 +8,26 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.updater.github.GithubUpdateChecker
import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
return runBlocking {
try {
val result = GithubUpdateChecker().checkForUpdate()
override fun doWork() = runBlocking {
try {
val result = GithubUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
NotificationCompat.Builder(context, Notifications.CHANNEL_COMMON).update {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done)
// Download action
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
)
}
}
Result.success()
} catch (e: Exception) {
Result.failure()
if (result is UpdateResult.NewUpdate<*>) {
UpdaterNotifier(context).promptUpdate(result.release.downloadLink)
}
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
fun NotificationCompat.Builder.update(block: NotificationCompat.Builder.() -> Unit) {
block()
context.notificationManager.notify(Notifications.ID_UPDATER, build())
}
companion object {
private const val TAG = "UpdateChecker"
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R
@@ -28,6 +30,27 @@ internal class UpdaterNotifier(private val context: Context) {
context.notificationManager.notify(id, build())
}
fun promptUpdate(url: String) {
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(UpdaterService.EXTRA_DOWNLOAD_URL, url)
}
val pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
with(notificationBuilder) {
setContentTitle(context.getString(R.string.app_name))
setContentText(context.getString(R.string.update_check_notification_update_available))
setSmallIcon(android.R.drawable.stat_sys_download_done)
setContentIntent(pendingIntent)
clearActions()
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
pendingIntent
)
}
notificationBuilder.show()
}
/**
* Call when apk download starts.
*
@@ -63,19 +86,20 @@ internal class UpdaterNotifier(private val context: Context) {
* @param uri path location of apk.
*/
fun onDownloadFinished(uri: Uri) {
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
with(notificationBuilder) {
setContentText(context.getString(R.string.update_check_notification_download_complete))
setSmallIcon(android.R.drawable.stat_sys_download_done)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
// Install action
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
setContentIntent(installIntent)
clearActions()
addAction(
R.drawable.ic_system_update_alt_white_24dp,
context.getString(R.string.action_install),
NotificationHandler.installApkPendingActivity(context, uri)
installIntent
)
// Cancel action
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
@@ -96,13 +120,13 @@ internal class UpdaterNotifier(private val context: Context) {
setSmallIcon(android.R.drawable.stat_sys_warning)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
// Retry action
clearActions()
addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry),
UpdaterService.downloadApkPendingService(context, url)
)
// Cancel action
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
@@ -32,8 +32,8 @@ class GithubUpdateChecker {
.parseAs<GithubRelease>()
.let {
// Check if latest version is different from current version
if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) {
GithubUpdateResult.NewUpdate(it)
if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) {
GithubUpdateResult.NewUpdate(it)
} else {
GithubUpdateResult.NoNewUpdate()
}
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension
import android.content.Context
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import com.elvishew.xlog.XLog
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -19,6 +18,7 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.toast
import exh.log.xLogD
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
@@ -156,15 +156,15 @@ class ExtensionManager(
// EXH -->
private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> {
val blacklistEnabled = preferences.enableSourceBlacklist().get()
return filter {
if (it.isBlacklisted(blacklistEnabled)) {
XLog.tag("ExtensionManager").d("Removing blacklisted extension: (name: %s, pkgName: %s)!", it.name, it.pkgName)
false
} else true
return filterNot { extension ->
extension.isBlacklisted(blacklistEnabled)
.also {
if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
}
}
}
fun Extension.isBlacklisted(blacklistEnabled: Boolean = preferences.enableSourceBlacklist().get()): Boolean {
private fun Extension.isBlacklisted(blacklistEnabled: Boolean = preferences.enableSourceBlacklist().get()): Boolean {
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
}
// EXH <--
@@ -333,7 +333,7 @@ class ExtensionManager(
private fun registerNewExtension(extension: Extension.Installed) {
// SY -->
if (extension.isBlacklisted()) {
XLog.tag("ExtensionManager").d("Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName)
xLogD("Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName)
return
}
// SY <--
@@ -351,7 +351,7 @@ class ExtensionManager(
private fun registerUpdatedExtension(extension: Extension.Installed) {
// SY -->
if (extension.isBlacklisted()) {
XLog.tag("ExtensionManager").d("Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName)
xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
return
}
// SY <--
@@ -32,7 +32,7 @@ internal class ExtensionGithubApi {
.let { parseResponse(it) }
} /* SY --> */ + preferences.extensionRepos().get().flatMap { repoPath ->
val url = "$BASE_URL$repoPath/repo/"
networkService.client
networkService.client
.newCall(GET("${url}index.min.json"))
.await()
.parseAs<JsonArray>()
@@ -163,7 +163,7 @@ internal object ExtensionLoader {
else -> throw Exception("Unknown source class type! ${obj.javaClass}")
}
} catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.")
Timber.e(e, "Extension load error: $extName ($it)")
return LoadResult.Error(e)
}
}
@@ -171,6 +171,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
companion object {
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
private val COOKIE_NAMES = listOf("cf_clearance")
}
}
@@ -0,0 +1,40 @@
package eu.kanade.tachiyomi.network
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import java.net.InetAddress
/**
* Based on https://github.com/square/okhttp/blob/ef5d0c83f7bbd3a0c0534e7ca23cbc4ee7550f3b/okhttp-dnsoverhttps/src/test/java/okhttp3/dnsoverhttps/DohProviders.java
*/
const val PREF_DOH_CLOUDFLARE = 1
const val PREF_DOH_GOOGLE = 2
fun OkHttpClient.Builder.dohCloudflare() = dns(
DnsOverHttps.Builder().client(build())
.url("https://cloudflare-dns.com/dns-query".toHttpUrl())
.bootstrapDnsHosts(
InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"),
InetAddress.getByName("1.1.1.1"),
InetAddress.getByName("1.0.0.1"),
InetAddress.getByName("162.159.132.53"),
InetAddress.getByName("2606:4700:4700::1111"),
InetAddress.getByName("2606:4700:4700::1001"),
InetAddress.getByName("2606:4700:4700::0064"),
InetAddress.getByName("2606:4700:4700::6400")
)
.build()
)
fun OkHttpClient.Builder.dohGoogle() = dns(
DnsOverHttps.Builder().client(build())
.url("https://dns.google/dns-query".toHttpUrl())
.bootstrapDnsHosts(
InetAddress.getByName("8.8.4.4"),
InetAddress.getByName("8.8.8.8")
)
.build()
)
@@ -4,13 +4,10 @@ import android.content.Context
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit
/* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) {
@@ -38,25 +35,9 @@ import java.util.concurrent.TimeUnit
builder.addInterceptor(httpLoggingInterceptor)
}
if (preferences.enableDoh()) {
builder.dns(
DnsOverHttps.Builder().client(builder.build())
.url("https://cloudflare-dns.com/dns-query".toHttpUrl())
.bootstrapDnsHosts(
listOf(
InetAddress.getByName("162.159.36.1"),
InetAddress.getByName("162.159.46.1"),
InetAddress.getByName("1.1.1.1"),
InetAddress.getByName("1.0.0.1"),
InetAddress.getByName("162.159.132.53"),
InetAddress.getByName("2606:4700:4700::1111"),
InetAddress.getByName("2606:4700:4700::1001"),
InetAddress.getByName("2606:4700:4700::0064"),
InetAddress.getByName("2606:4700:4700::6400")
)
)
.build()
)
when (preferences.dohProvider()) {
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
PREF_DOH_GOOGLE -> builder.dohGoogle()
}
builder.build()
@@ -33,7 +33,7 @@ import java.util.zip.ZipFile
class LocalSource(private val context: Context) : CatalogueSource {
companion object {
const val ID = 0L
const val HELP_URL = "https://tachiyomi.org/help/guides/reading-local-manga/"
const val HELP_URL = "https://tachiyomi.org/help/guides/local-manga/"
private const val COVER_NAME = "cover.jpg"
private val SUPPORTED_ARCHIVE_TYPES = setOf("zip", "rar", "cbr", "cbz", "epub")
@@ -64,6 +64,12 @@ class LocalSource(private val context: Context) : CatalogueSource {
val c = context.getString(R.string.app_name) + File.separator + "local"
return DiskUtil.getExternalStorages(context).map { File(it.absolutePath, c) }
}
// SY -->
val json = Json {
prettyPrint = true
}
// SY <--
}
override val id = ID
@@ -151,19 +157,16 @@ class LocalSource(private val context: Context) : CatalogueSource {
// SY -->
fun updateMangaInfo(manga: SManga) {
val directory = getBaseDirectories(context).mapNotNull { File(it, manga.url) }.find {
val directory = getBaseDirectories(context).map { File(it, manga.url) }.find {
it.exists()
} ?: return
val json = Json {
prettyPrint = true
}
val existingFileName = directory.listFiles()?.find { it.extension == "json" }?.name
val file = File(directory, existingFileName ?: "info.json")
file.writeText(json.encodeToString(manga.toJson()))
}
private fun SManga.toJson(): MangaJson {
return MangaJson(title, author, artist, description, genre?.split(", ")?.toTypedArray())
return MangaJson(title, author, artist, description, genre?.split(", "), status)
}
@Serializable
@@ -172,7 +175,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
val author: String?,
val artist: String?,
val description: String?,
val genre: Array<String>?
val genre: List<String>?,
val status: Int
) {
override fun equals(other: Any?): Boolean {
@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.source
import android.content.Context
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.Page
@@ -10,15 +9,14 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.source.online.all.PervEden
import eu.kanade.tachiyomi.source.online.english.EightMuses
import eu.kanade.tachiyomi.source.online.english.HBrowse
import eu.kanade.tachiyomi.source.online.english.HentaiCafe
import eu.kanade.tachiyomi.source.online.english.Pururin
import eu.kanade.tachiyomi.source.online.english.Tsumino
import exh.log.xLogD
import exh.source.BlacklistedSources
import exh.source.DelegatedHttpSource
import exh.source.EH_SOURCE_ID
@@ -26,7 +24,6 @@ import exh.source.EIGHTMUSES_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import exh.source.EnhancedHttpSource
import exh.source.HBROWSE_SOURCE_ID
import exh.source.HENTAI_CAFE_SOURCE_ID
import exh.source.PERV_EDEN_EN_SOURCE_ID
import exh.source.PERV_EDEN_IT_SOURCE_ID
import exh.source.PURURIN_SOURCE_ID
@@ -115,7 +112,7 @@ open class SourceManager(private val context: Context) {
} else DELEGATED_SOURCES[sourceQName]
} else null
val newSource = if (source is HttpSource && delegate != null) {
XLog.tag("SourceManager").d("Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName)
xLogD("Delegating source: %s -> %s!", sourceQName, delegate.newSourceClass.qualifiedName)
val enhancedSource = EnhancedHttpSource(
source,
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(source, context)
@@ -132,7 +129,7 @@ open class SourceManager(private val context: Context) {
} else source
if (source.id in BlacklistedSources.BLACKLISTED_EXT_SOURCES) {
XLog.tag("SourceManager").d("Removing blacklisted source: (id: %s, name: %s, lang: %s)!", source.id, source.name, (source as? CatalogueSource)?.lang)
xLogD("Removing blacklisted source: (id: %s, name: %s, lang: %s)!", source.id, source.name, (source as? CatalogueSource)?.lang)
return
}
// EXH <--
@@ -155,13 +152,12 @@ open class SourceManager(private val context: Context) {
// SY -->
private fun createEHSources(): List<Source> {
val exSrcs = mutableListOf<HttpSource>(
val sources = listOf<HttpSource>(
EHentai(EH_SOURCE_ID, false, context)
)
if (prefs.enableExhentai().get()) {
exSrcs += EHentai(EXH_SOURCE_ID, true, context)
}
return exSrcs
return if (prefs.enableExhentai().get()) {
sources + EHentai(EXH_SOURCE_ID, true, context)
} else sources
}
// SY <--
@@ -195,12 +191,6 @@ open class SourceManager(private val context: Context) {
companion object {
private const val fillInSourceId = Long.MAX_VALUE
val DELEGATED_SOURCES = listOf(
DelegatedSource(
"Hentai Cafe",
HENTAI_CAFE_SOURCE_ID,
"eu.kanade.tachiyomi.extension.all.foolslide.HentaiCafe",
HentaiCafe::class
),
DelegatedSource(
"Pururin",
PURURIN_SOURCE_ID,
@@ -213,13 +203,13 @@ open class SourceManager(private val context: Context) {
"eu.kanade.tachiyomi.extension.en.tsumino.Tsumino",
Tsumino::class
),
DelegatedSource(
/*DelegatedSource(
"MangaDex",
fillInSourceId,
"eu.kanade.tachiyomi.extension.all.mangadex",
MangaDex::class,
true
),
),*/
DelegatedSource(
"HBrowse",
HBROWSE_SOURCE_ID,
@@ -229,7 +219,7 @@ open class SourceManager(private val context: Context) {
DelegatedSource(
"8Muses",
EIGHTMUSES_SOURCE_ID,
"eu.kanade.tachiyomi.extension.all.eromuse.EroMuse",
"eu.kanade.tachiyomi.extension.en.eightmuses.EightMuses",
EightMuses::class
),
DelegatedSource(
@@ -276,7 +266,7 @@ open class SourceManager(private val context: Context) {
get() = internalMap.size
override fun containsKey(key: K): Boolean = internalMap.containsKey(key)
override fun containsValue(value: V): Boolean = internalMap.containsValue(value)
override fun get(key: K): V? = get(key)
override fun get(key: K): V? = internalMap[key]
override fun isEmpty(): Boolean = internalMap.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = internalMap.entries
@@ -2,8 +2,26 @@ package eu.kanade.tachiyomi.source.model
import exh.metadata.metadata.base.RaisedSearchMetadata
/* SY --> */ open /* SY <-- */ class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)
/* SY --> */ open /* SY <-- */ class MangasPage(open val mangas: List<SManga>, open val hasNextPage: Boolean) {
// SY -->
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is MangasPage) return false
if (mangas != other.mangas) return false
if (hasNextPage != other.hasNextPage) return false
return true
}
override fun hashCode(): Int {
var result = mangas.hashCode()
result = 31 * result + hasNextPage.hashCode()
return result
}
// SY <--
}
// SY -->
class MetadataMangasPage(mangas: List<SManga>, hasNextPage: Boolean, val mangasMetadata: List<RaisedSearchMetadata>) : MangasPage(mangas, hasNextPage)
data class MetadataMangasPage(override val mangas: List<SManga>, override val hasNextPage: Boolean, val mangasMetadata: List<RaisedSearchMetadata>) : MangasPage(mangas, hasNextPage)
// SY <--
@@ -35,6 +35,8 @@ interface SManga : Serializable {
get() = (this as? MangaImpl)?.ogDesc ?: description
val originalGenre: String?
get() = (this as? MangaImpl)?.ogGenre ?: genre
val originalStatus: Int
get() = (this as? MangaImpl)?.ogStatus ?: status
// SY <--
fun copyFrom(other: SManga) {
@@ -1,17 +1,25 @@
package eu.kanade.tachiyomi.source.online
import android.app.Activity
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.DialogController
interface LoginSource : Source {
val needsLogin: Boolean
val requiresLogin: Boolean
val twoFactorAuth: AuthSupport
fun isLogged(): Boolean
fun getLoginDialog(source: Source, activity: Activity): DialogController
fun getUsername(): String
suspend fun login(username: String, password: String, twoFactorCode: String = ""): Boolean
fun getPassword(): String
suspend fun login(username: String, password: String, twoFactorCode: String?): Boolean
suspend fun logout(): Boolean
enum class AuthSupport {
NOT_SUPPORTED,
SUPPORTED,
REQUIRED
}
}
@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadata
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.metadata.metadata.base.insertFlatMetadataCompletable
import exh.util.executeOnIO
import rx.Completable
import rx.Single
@@ -72,7 +72,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
}.flatMapCompletable {
if (mangaId != null) {
it.mangaId = mangaId
db.insertFlatMetadata(it.flatten())
db.insertFlatMetadataCompletable(it.flatten())
} else Completable.complete()
}
}
@@ -87,7 +87,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
parseInfoIntoMetadata(metadata, input)
if (mangaId != null) {
metadata.mangaId = mangaId
db.insertFlatMetadataAsync(metadata.flatten()).await()
db.insertFlatMetadata(metadata.flatten())
}
return metadata.createMangaInfo(manga)
@@ -119,7 +119,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
val newMetaSingle = Single.just(newMeta)
if (mangaId != null) {
newMeta.mangaId = mangaId
db.insertFlatMetadata(newMeta.flatten()).andThen(newMetaSingle)
db.insertFlatMetadataCompletable(newMeta.flatten()).andThen(newMetaSingle)
} else newMetaSingle
}
} else Single.just(existingMeta)
@@ -146,7 +146,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
parseInfoIntoMetadata(newMeta, input)
if (mangaId != null) {
newMeta.mangaId = mangaId
db.insertFlatMetadataAsync(newMeta.flatten()).await().let { newMeta }
db.insertFlatMetadata(newMeta.flatten()).let { newMeta }
} else newMeta
}
}
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.net.Uri
import androidx.core.net.toUri
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -31,6 +30,7 @@ import exh.eh.EHTags
import exh.eh.EHentaiUpdateHelper
import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.GalleryEntry
import exh.log.xLogD
import exh.metadata.MetadataUtil
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE
@@ -40,7 +40,7 @@ import exh.metadata.metadata.EHentaiSearchMetadata.Companion.TAG_TYPE_WEAK
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.toGenreString
import exh.metadata.metadata.base.RaisedTag
import exh.ui.login.LoginController
import exh.ui.login.EhLoginActivity
import exh.ui.metadata.adapters.EHentaiDescriptionAdapter
import exh.util.UriFilter
import exh.util.UriGroup
@@ -229,7 +229,7 @@ class EHentai(
} else {
parsedLocation.queryParameter(REVERSE_PARAM)!!.toBoolean()
}
Pair(parsedMangas, hasNextPage)
parsedMangas to hasNextPage
}
private fun getGenre(element: Element? = null, genreString: String? = null): String? {
@@ -325,7 +325,7 @@ class EHentai(
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
} else break
} else {
XLog.tag("EHentai").d("Parent cache hit: %s!", gid)
this@EHentai.xLogD("Parent cache hit: %s!", gid)
url = EHentaiSearchMetadata.idAndTokenToUrl(
cachedParent.gId,
cachedParent.gToken
@@ -613,7 +613,7 @@ class EHentai(
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME
) {
aged = true
XLog.tag("EHentai").d("aged %s - too old", title)
this@EHentai.xLogD("aged %s - too old", title)
}
// Parse ratings
@@ -713,7 +713,7 @@ class EHentai(
page++
} while (parsed.second)
return Pair(result.toList(), favNames!!)
return Pair(result.toList(), favNames.orEmpty())
}
fun spPref() = if (exh) {
@@ -725,9 +725,9 @@ class EHentai(
private fun rawCookies(sp: Int): Map<String, String> {
val cookies: MutableMap<String, String> = mutableMapOf()
if (preferences.enableExhentai().get()) {
cookies[LoginController.MEMBER_ID_COOKIE] = preferences.memberIdVal().get()
cookies[LoginController.PASS_HASH_COOKIE] = preferences.passHashVal().get()
cookies[LoginController.IGNEOUS_COOKIE] = preferences.igneousVal().get()
cookies[EhLoginActivity.MEMBER_ID_COOKIE] = preferences.memberIdVal().get()
cookies[EhLoginActivity.PASS_HASH_COOKIE] = preferences.passHashVal().get()
cookies[EhLoginActivity.IGNEOUS_COOKIE] = preferences.igneousVal().get()
cookies["sp"] = sp.toString()
val sessionKey = preferences.exhSettingsKey().get()
@@ -879,13 +879,20 @@ class EHentai(
stringBuilder.append(" ")
}
XLog.tag("EHentai").d(stringBuilder.toString())
return stringBuilder.toString().trim()
return stringBuilder.toString().trim().also { xLogD(it) }
}
data class AdvSearchEntry(val search: Pair<String, String>, val exclude: Boolean)
class AutoCompleteTags(tags: List<String>, skipAutoFillTags: List<String>, excludePrefix: String) : Filter.AutoComplete(name = "Tags", hint = "Search tags here (limit of 8)", values = tags, skipAutoFillTags = skipAutoFillTags, excludePrefix = excludePrefix, state = emptyList())
class AutoCompleteTags(tags: List<String>, skipAutoFillTags: List<String>, excludePrefix: String) :
Filter.AutoComplete(
name = "Tags",
hint = "Search tags here (limit of 8)",
values = tags,
skipAutoFillTags = skipAutoFillTags,
excludePrefix = excludePrefix,
state = emptyList()
)
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
@@ -1,10 +1,8 @@
package eu.kanade.tachiyomi.source.online.all
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.core.text.HtmlCompat
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -13,7 +11,6 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@@ -27,7 +24,6 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.RandomMangaSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
@@ -47,13 +43,18 @@ import exh.metadata.metadata.MangaDexSearchMetadata
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.MangaDexDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import exh.widget.preference.MangadexLoginDialog
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.int
import okhttp3.CacheControl
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.closeQuietly
import okio.EOFException
import rx.Observable
import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo
@@ -73,11 +74,10 @@ class MangaDex(delegate: HttpSource, val context: Context) :
RandomMangaSource {
override val lang: String = delegate.lang
override val headers: Headers
get() = super.headers.newBuilder().apply {
add("X-Requested-With", "XMLHttpRequest")
add("Referer", MdUtil.baseUrl)
}.build()
override val headers: Headers = super.headers.newBuilder().apply {
add("X-Requested-With", "XMLHttpRequest")
add("Referer", MdUtil.baseUrl)
}.build()
private val mdLang by lazy {
MdLang.values().find { it.lang == lang }?.dexLang ?: lang
@@ -176,21 +176,27 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return FollowsHandler(client, headers, Injekt.get(), useLowQualityThumbnail()).fetchFollows()
}
override val needsLogin: Boolean = true
override val requiresLogin: Boolean = true
override fun getLoginDialog(source: Source, activity: Activity): DialogController {
return MangadexLoginDialog(source as MangaDex)
}
override val twoFactorAuth = LoginSource.AuthSupport.SUPPORTED
override fun isLogged(): Boolean {
val httpUrl = MdUtil.baseUrl.toHttpUrl()
return trackManager.mdList.isLogged && network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME }
}
override fun getUsername(): String {
return trackManager.mdList.getUsername()
}
override fun getPassword(): String {
return trackManager.mdList.getPassword()
}
override suspend fun login(
username: String,
password: String,
twoFactorCode: String
twoFactorCode: String?
): Boolean {
return withIOContext {
val formBody = FormBody.Builder().apply {
@@ -198,27 +204,29 @@ class MangaDex(delegate: HttpSource, val context: Context) :
add("login_password", password)
add("no_js", "1")
add("remember_me", "1")
add("two_factor", twoFactorCode ?: "")
}
twoFactorCode.let {
formBody.add("two_factor", it)
runCatching {
client.newCall(
POST(
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=login",
headers,
formBody.build()
)
).await().closeQuietly()
}
val response = client.newCall(
POST(
"${MdUtil.baseUrl}/ajax/actions.ajax.php?function=login",
headers,
formBody.build()
)
).await()
val response = client.newCall(GET(MdUtil.apiUrl + MdUtil.isLoggedInApi, headers)).await()
withIOContext { response.body?.string() }.let { result ->
if (result != null && result.isEmpty()) {
true
withIOContext { response.body?.string() }.let { jsonData ->
if (jsonData != null) {
MdUtil.jsonParser.decodeFromString<JsonObject>(jsonData)["code"]?.let { it as? JsonPrimitive }?.int == 200
} else {
val error = result?.let { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() }
throw Exception(error)
throw Exception("Json data was null")
}
}.also {
preferences.setTrackCredentials(trackManager.mdList, username, password)
}
}
}
@@ -233,11 +241,17 @@ class MangaDex(delegate: HttpSource, val context: Context) :
if (token.isNullOrEmpty()) {
return@withIOContext true
}
val result = client.newCall(
POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build()
).await()
val resultStr = withIOContext { result.body?.string() }
if (resultStr?.contains("success", true) == true) {
try {
val result = client.newCall(
POST("${MdUtil.baseUrl}/ajax/actions.ajax.php?function=logout", headers).newBuilder().addHeader(REMEMBER_ME, token).build()
).await()
val resultStr = withIOContext { result.body?.string() }
if (resultStr?.contains("success", true) == true) {
network.cookieManager.remove(httpUrl)
trackManager.mdList.logout()
return@withIOContext true
}
} catch (e: EOFException) {
network.cookieManager.remove(httpUrl)
trackManager.mdList.logout()
return@withIOContext true
@@ -269,7 +283,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
}
suspend fun getTrackingAndMangaInfo(track: Track): Pair<Track, MangaDexSearchMetadata> {
return MangaHandler(client, headers, lang).getTrackingInfo(track, useLowQualityThumbnail())
return MangaHandler(client, headers, mdLang).getTrackingInfo(track, useLowQualityThumbnail())
}
override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
@@ -281,7 +295,7 @@ class MangaDex(delegate: HttpSource, val context: Context) :
}
override suspend fun fetchRandomMangaUrl(): String {
return MangaHandler(client, headers, mdLang).fetchRandomMangaId()
return withIOContext { MangaHandler(client, headers, mdLang).fetchRandomMangaId() }
}
fun fetchMangaSimilar(manga: Manga): Observable<MangasPage> {
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.source.online.all
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -19,6 +18,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import exh.log.xLogW
import exh.merged.sql.models.MergedMangaReference
import exh.source.MERGED_SOURCE_ID
import exh.util.executeOnIO
@@ -181,11 +181,11 @@ class MergedSource : HttpSource() {
}
manga.copyFrom(source.getMangaDetails(manga.toMangaInfo()).toSManga())
try {
manga.id = db.insertManga(manga).executeOnIO().insertedId()
manga.id = db.insertManga(manga).executeAsBlocking().insertedId()
mangaId = manga.id
db.insertNewMergedMangaId(this).executeOnIO()
db.insertNewMergedMangaId(this).executeAsBlocking()
} catch (e: Exception) {
XLog.tag("MergedSource").enableStackTrace(e.stackTrace.contentToString(), 5)
xLogW("Error inserting merged manga id", e)
}
}
return LoadedMangaSource(source, manga, this)
@@ -1,143 +0,0 @@
package eu.kanade.tachiyomi.source.online.english
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.metadata.metadata.HentaiCafeSearchMetadata
import exh.metadata.metadata.HentaiCafeSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.HentaiCafeDescriptionAdapter
import exh.util.urlImportFetchSearchManga
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.nodes.Document
import rx.Observable
import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo
class HentaiCafe(delegate: HttpSource, val context: Context) :
DelegatedHttpSource(delegate),
MetadataSource<HentaiCafeSearchMetadata, Document>,
UrlImportableSource {
/**
* An ISO 639-1 compliant language code (two letters in lower case).
*/
override val lang = "en"
/**
* The class of the metadata used by this source
*/
override val metaClass = HentaiCafeSearchMetadata::class
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.flatMap {
parseToManga(manga, it.asJsoup()).andThen(
Observable.just(
manga.apply {
initialized = true
}
)
)
}
}
override suspend fun getMangaDetails(manga: MangaInfo): MangaInfo {
val response = client.newCall(mangaDetailsRequest(manga.toSManga())).await()
return parseToManga(manga, response.asJsoup())
}
/**
* Parse the supplied input into the supplied metadata object
*/
override fun parseIntoMetadata(metadata: HentaiCafeSearchMetadata, input: Document) {
with(metadata) {
url = input.location()
title = input.select("h3").text()
val contentElement = input.select(".entry-content").first()
thumbnailUrl = contentElement.child(0).child(0).attr("src")
fun filterableTagsOfType(type: String) = contentElement.select("a")
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
.map { it.text() }
tags.clear()
tags += filterableTagsOfType("tag").map {
RaisedTag(null, it, TAG_TYPE_DEFAULT)
}
val artists = filterableTagsOfType("artist")
artist = artists.joinToString()
tags += artists.map {
RaisedTag("artist", it, TAG_TYPE_VIRTUAL)
}
readerId = input.select("[title=Read]").attr("href").toHttpUrlOrNull()!!.pathSegments[2]
}
}
override fun fetchChapterList(manga: SManga) = runAsObservable({
fetchOrLoadMetadata(manga.id) {
val response = client.newCall(mangaDetailsRequest(manga)).await()
response.asJsoup()
}
}).map {
listOf(
SChapter.create().apply {
url = "/manga/read/${it.readerId}/en/0/1/"
name = "Chapter"
chapter_number = 0.0f
}
)
}
override suspend fun getChapterList(manga: MangaInfo): List<ChapterInfo> {
val metadata = fetchOrLoadMetadata(manga.id()) {
val response = client.newCall(mangaDetailsRequest(manga.toSManga())).await()
response.asJsoup()
}
return listOf(
ChapterInfo(
key = "/manga/read/${metadata.readerId}/en/0/1/",
name = "Chapter",
number = 0F
)
)
}
override val matchingHosts = listOf(
"hentai.cafe"
)
override suspend fun mapUrlToMangaUrl(uri: Uri): String? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.takeUnless { it.equals("manga", true) } ?: return null
return if (lcFirstPathSegment.equals("hc.fyi", true)) {
"/$lcFirstPathSegment/${uri.pathSegments[1]}"
} else null
}
override fun getDescriptionAdapter(controller: MangaController): HentaiCafeDescriptionAdapter {
return HentaiCafeDescriptionAdapter(controller)
}
}
@@ -1,67 +1,43 @@
package eu.kanade.tachiyomi.ui.base.activity
import android.content.res.Configuration
import android.os.Build
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DarkThemeVariant
import eu.kanade.tachiyomi.data.preference.PreferenceValues.LightThemeVariant
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ThemeMode
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
abstract class BaseThemedActivity : AppCompatActivity() {
val preferences: PreferencesHelper by injectLazy()
private val isDarkMode: Boolean by lazy {
val themeMode = preferences.themeMode().get()
(themeMode == Values.ThemeMode.dark) ||
(
themeMode == Values.ThemeMode.system &&
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES)
)
}
private val lightTheme: Int by lazy {
when (preferences.themeLight().get()) {
Values.LightThemeVariant.blue -> R.style.Theme_Tachiyomi_LightBlue
else -> {
when {
// Light status + navigation bar
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 -> {
R.style.Theme_Tachiyomi_Light_Api27
}
// Light status bar + fallback gray navigation bar
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
R.style.Theme_Tachiyomi_Light_Api23
}
// Fallback gray status + navigation bar
else -> {
R.style.Theme_Tachiyomi_Light
}
}
}
}
}
private val darkTheme: Int by lazy {
when (preferences.themeDark().get()) {
Values.DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_DarkBlue
Values.DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Amoled
Values.DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Red
Values.DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_MidnightDusk
else -> R.style.Theme_Tachiyomi_Dark
}
}
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(
when {
isDarkMode -> darkTheme
else -> lightTheme
val isDarkMode = when (preferences.themeMode().get()) {
ThemeMode.light -> false
ThemeMode.dark -> true
ThemeMode.system -> resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES
}
val themeId = if (isDarkMode) {
when (preferences.themeDark().get()) {
DarkThemeVariant.default -> R.style.Theme_Tachiyomi_Dark
DarkThemeVariant.blue -> R.style.Theme_Tachiyomi_Dark_Blue
DarkThemeVariant.amoledblue -> R.style.Theme_Tachiyomi_Dark_AmoledBlue
DarkThemeVariant.amoled -> R.style.Theme_Tachiyomi_Dark_Amoled
DarkThemeVariant.red -> R.style.Theme_Tachiyomi_Dark_Red
DarkThemeVariant.midnightdusk -> R.style.Theme_Tachiyomi_Dark_MidnightDusk
DarkThemeVariant.hotpink -> R.style.Theme_Tachiyomi_Dark_HotPink
}
)
} else {
when (preferences.themeLight().get()) {
LightThemeVariant.default -> R.style.Theme_Tachiyomi_Light
LightThemeVariant.blue -> R.style.Theme_Tachiyomi_Light_Blue
}
}
setTheme(themeId)
super.onCreate(savedInstanceState)
}
}
@@ -7,11 +7,12 @@ import android.view.View
import android.view.ViewGroup
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
/**
* An [AnimatorChangeHandler] that will cross fade two views
* An [AnimatorChangeHandler] that will remove the from view and fade in the to view
*/
class OneWayFadeChangeHandler : AnimatorChangeHandler {
class OneWayFadeChangeHandler : FadeChangeHandler {
constructor()
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
constructor(duration: Long) : super(duration)
@@ -33,10 +34,6 @@ class OneWayFadeChangeHandler : AnimatorChangeHandler {
return animator
}
override fun resetFromView(from: View) {
from.alpha = 1f
}
override fun copy(): ControllerChangeHandler {
return OneWayFadeChangeHandler(animationDuration, removesFromViewOnPush())
}
@@ -19,7 +19,8 @@ import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle) {
lateinit var binding: VB
protected lateinit var binding: VB
private set
lateinit var viewScope: CoroutineScope
@@ -51,11 +52,12 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
return inflateView(inflater, container)
}
abstract fun createBinding(inflater: LayoutInflater): VB
abstract fun inflateView(inflater: LayoutInflater, container: ViewGroup): View
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
binding = createBinding(inflater)
return binding.root
}
open fun onViewCreated(view: View) {}
@@ -121,7 +123,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
* [expandActionViewFromInteraction] should be set to true in [onOptionsItemSelected] when the expandable item is selected
* This method should be called as part of [MenuItem.OnActionExpandListener.onMenuItemActionExpand]
*/
fun invalidateMenuOnExpand(): Boolean {
open fun invalidateMenuOnExpand(): Boolean {
return if (expandActionViewFromInteraction) {
activity?.invalidateOptionsMenu()
false
@@ -0,0 +1,196 @@
package eu.kanade.tachiyomi.ui.base.controller
import android.app.Activity
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.annotation.StringRes
import androidx.appcompat.widget.SearchView
import androidx.viewbinding.ViewBinding
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
/**
* Implementation of the NucleusController that has a built-in ViewSearch
*/
abstract class SearchableNucleusController<VB : ViewBinding, P : BasePresenter<*>>
(bundle: Bundle? = null) : NucleusController<VB, P>(bundle) {
enum class SearchViewState { LOADING, LOADED, COLLAPSING, FOCUSED }
/**
* Used to bypass the initial searchView being set to empty string after an onResume
*/
private var currentSearchViewState: SearchViewState = SearchViewState.LOADING
/**
* Store the query text that has not been submitted to reassign it after an onResume, UI-only
*/
protected var nonSubmittedQuery: String = ""
/**
* To be called by classes that extend this subclass in onCreateOptionsMenu
*/
protected fun createOptionsMenu(
menu: Menu,
inflater: MenuInflater,
menuId: Int,
searchItemId: Int,
@StringRes queryHint: Int? = null,
restoreCurrentQuery: Boolean = true
) {
// Inflate menu
inflater.inflate(menuId, menu)
// Initialize search option.
val searchItem = menu.findItem(searchItemId)
val searchView = searchItem.actionView as SearchView
searchItem.fixExpand(onExpand = { invalidateMenuOnExpand() })
searchView.maxWidth = Int.MAX_VALUE
searchView.queryTextEvents()
.onEach {
val newText = it.queryText.toString()
if (newText.isNotBlank() or acceptEmptyQuery()) {
if (it is QueryTextEvent.QuerySubmitted) {
// Abstract function for implementation
// Run it first in case the old query data is needed (like BrowseSourceController)
onSearchViewQueryTextSubmit(newText)
presenter.query = newText
nonSubmittedQuery = ""
} else if ((it is QueryTextEvent.QueryChanged) && (presenter.query != newText)) {
nonSubmittedQuery = newText
// Abstract function for implementation
onSearchViewQueryTextChange(newText)
}
}
// clear the collapsing flag
setCurrentSearchViewState(SearchViewState.LOADED, SearchViewState.COLLAPSING)
}
.launchIn(viewScope)
val query = presenter.query
// Restoring a query the user had not submitted
if (nonSubmittedQuery.isNotBlank() and (nonSubmittedQuery != query)) {
searchItem.expandActionView()
searchView.setQuery(nonSubmittedQuery, false)
onSearchViewQueryTextChange(nonSubmittedQuery)
} else {
if (queryHint != null) {
searchView.queryHint = applicationContext?.getString(queryHint)
}
if (restoreCurrentQuery) {
// Restoring a query the user had submitted
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
onSearchViewQueryTextChange(query)
onSearchViewQueryTextSubmit(query)
}
}
}
// Workaround for weird behavior where searchView gets empty text change despite
// query being set already, prevents the query from being cleared
binding.root.post {
setCurrentSearchViewState(SearchViewState.LOADED, SearchViewState.LOADING)
}
searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->
if (hasFocus) {
setCurrentSearchViewState(SearchViewState.FOCUSED)
} else {
setCurrentSearchViewState(SearchViewState.LOADED, SearchViewState.FOCUSED)
}
}
searchItem.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
onSearchMenuItemActionExpand(item)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
val localSearchView = searchItem.actionView as SearchView
// if it is blank the flow event won't trigger so we would stay in a COLLAPSING state
if (localSearchView.toString().isNotBlank()) {
setCurrentSearchViewState(SearchViewState.COLLAPSING)
}
onSearchMenuItemActionCollapse(item)
return true
}
}
)
}
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
// Until everything is up and running don't accept empty queries
setCurrentSearchViewState(SearchViewState.LOADING)
}
private fun acceptEmptyQuery(): Boolean {
return when (currentSearchViewState) {
SearchViewState.COLLAPSING, SearchViewState.FOCUSED -> true
else -> false
}
}
private fun setCurrentSearchViewState(to: SearchViewState, from: SearchViewState? = null) {
// When loading ignore all requests other than loaded
if ((currentSearchViewState == SearchViewState.LOADING) && (to != SearchViewState.LOADED)) {
return
}
// Prevent changing back to an unwanted state when using async flows (ie onFocus event doing
// COLLAPSING -> LOADED)
if ((from != null) && (currentSearchViewState != from)) {
return
}
currentSearchViewState = to
}
/**
* Called by the SearchView since since the implementation of these can vary in subclasses
* Not abstract as they are optional
*/
protected open fun onSearchViewQueryTextChange(newText: String?) {
}
protected open fun onSearchViewQueryTextSubmit(query: String?) {
}
protected open fun onSearchMenuItemActionExpand(item: MenuItem?) {
}
protected open fun onSearchMenuItemActionCollapse(item: MenuItem?) {
}
/**
* During the conversion to SearchableNucleusController (after which I plan to merge its code
* into BaseController) this addresses an issue where the searchView.onTextFocus event is not
* triggered
*/
override fun invalidateMenuOnExpand(): Boolean {
return if (expandActionViewFromInteraction) {
activity?.invalidateOptionsMenu()
setCurrentSearchViewState(SearchViewState.FOCUSED) // we are technically focused here
false
} else {
true
}
}
}
@@ -12,6 +12,11 @@ open class BasePresenter<V> : RxPresenter<V>() {
lateinit var presenterScope: CoroutineScope
/**
* Query from the view where applicable
*/
var query: String = ""
override fun onCreate(savedState: Bundle?) {
try {
super.onCreate(savedState)
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.browse
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
@@ -51,10 +50,7 @@ class BrowseController :
return resources!!.getString(R.string.browse)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = PagerControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = PagerControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
@@ -5,11 +5,11 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.recyclerview.widget.LinearLayoutManager
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@@ -56,14 +56,17 @@ open class ExtensionController :
return ExtensionPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = ExtensionControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = ExtensionControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
binding.swipeRefresh.isRefreshing = true
binding.swipeRefresh.refreshes()
.onEach { presenter.findAvailableExtensions() }
@@ -104,6 +107,8 @@ open class ExtensionController :
override fun onButtonClick(position: Int) {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return
when (extension) {
is Extension.Available -> presenter.installExtension(extension)
is Extension.Untrusted -> openTrustDialog(extension)
is Extension.Installed -> {
if (!extension.hasUpdate) {
openDetails(extension)
@@ -111,12 +116,6 @@ open class ExtensionController :
presenter.updateExtension(extension)
}
}
is Extension.Available -> {
presenter.installExtension(extension)
}
is Extension.Untrusted -> {
openTrustDialog(extension)
}
}
}
@@ -147,12 +146,11 @@ open class ExtensionController :
override fun onItemClick(view: View, position: Int): Boolean {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false
if (extension is Extension.Installed) {
openDetails(extension)
} else if (extension is Extension.Untrusted) {
openTrustDialog(extension)
when (extension) {
is Extension.Available -> presenter.installExtension(extension)
is Extension.Untrusted -> openTrustDialog(extension)
is Extension.Installed -> openDetails(extension)
}
return false
}
@@ -4,12 +4,12 @@ import android.annotation.SuppressLint
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
class ExtensionGroupHolder(view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter) {
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
private val binding = SectionHeaderItemBinding.bind(view)
@SuppressLint("SetTextI18n")
fun bind(item: ExtensionGroupItem) {
@@ -19,7 +19,7 @@ data class ExtensionGroupItem(val name: String, val size: Int, val showSize: Boo
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.source_main_controller_card_header
return R.layout.section_header_item
}
/**
@@ -12,7 +12,6 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.os.bundleOf
import androidx.preference.Preference
@@ -22,6 +21,7 @@ import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.EmptyPreferenceDataStore
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -64,10 +64,9 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
setHasOptionsMenu(true)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
override fun createBinding(inflater: LayoutInflater): ExtensionDetailControllerBinding {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
binding = ExtensionDetailControllerBinding.inflate(themedInflater)
return binding.root
return ExtensionDetailControllerBinding.inflate(themedInflater)
}
override fun createPresenter(): ExtensionDetailsPresenter {
@@ -82,6 +81,12 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.extensionPrefsRecycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
val extension = presenter.extension ?: return
val context = view.context
@@ -6,7 +6,6 @@ import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.os.bundleOf
import androidx.preference.DialogPreference
@@ -46,10 +45,9 @@ class SourcePreferencesController(bundle: Bundle? = null) :
bundleOf(SOURCE_ID to sourceId)
)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
override fun createBinding(inflater: LayoutInflater): SourcePreferencesControllerBinding {
val themedInflater = inflater.cloneInContext(getPreferenceThemeContext())
binding = SourcePreferencesControllerBinding.inflate(themedInflater)
return binding.root
return SourcePreferencesControllerBinding.inflate(themedInflater)
}
override fun createPresenter(): SourcePreferencesPresenter {
@@ -2,11 +2,9 @@ package eu.kanade.tachiyomi.ui.browse.latest
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
@@ -33,22 +31,6 @@ open class LatestController :
*/
protected var adapter: LatestAdapter? = null
/*init {
setHasOptionsMenu(true)
}*/
/**
* Initiate the view with [R.layout.global_search_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = LatestControllerBinding.inflate(inflater)
return binding.root
}
override fun getTitle(): String? {
return applicationContext?.getString(R.string.latest)
}
@@ -82,33 +64,7 @@ open class LatestController :
onMangaClick(manga)
}
/**
* Adds items to the options menu.
*
* @param menu menu containing options.
* @param inflater used to load the menu xml.
*/
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate menu.
/*inflater.inflate(R.menu.global_search, menu)
// Initialize search menu
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
searchView.onActionViewExpanded() // Required to show the query in the view
searchView.setQuery(presenter.query, false)
return true
}
override fun onMenuItemActionCollapse(item: MenuItem?): Boolean {
return true
}
})*/
}
override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater)
/**
* Called when the view is created
@@ -118,6 +74,12 @@ open class LatestController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = LatestAdapter(this)
// Create recycler and set adapter.
@@ -30,7 +30,6 @@ import uy.kohesive.injekt.api.get
* @param preferences manages the preference calls.
*/
open class LatestPresenter(
private val sourcesToUse: List<CatalogueSource>? = null,
val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get()
@@ -6,12 +6,12 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.Router
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -45,14 +45,17 @@ class PreMigrationController(bundle: Bundle? = null) :
override fun getTitle() = view?.context?.getString(R.string.select_sources)
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = PreMigrationControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = PreMigrationControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
val ourAdapter = adapter ?: MigrationSourceAdapter(
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
this
@@ -24,7 +24,7 @@ class MigratingManga(
val migrationJob = parentContext + SupervisorJob() + Dispatchers.Default
var migrationStatus: Int = MigrationStatus.RUNNING
var migrationStatus = MigrationStatus.RUNNING
@Volatile
private var manga: Manga? = null
@@ -42,11 +42,3 @@ class MigratingManga(
return MigrationProcessItem(this)
}
}
class MigrationStatus {
companion object {
const val RUNNING = 0
const val MANGA_FOUND = 1
const val MANGA_NOT_FOUND = 2
}
}
@@ -8,7 +8,6 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.graphics.ColorUtils
import androidx.core.os.bundleOf
@@ -16,6 +15,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
@@ -35,7 +35,6 @@ import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationContr
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.system.getResourceColor
@@ -48,7 +47,9 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import timber.log.Timber
@@ -73,6 +74,7 @@ class MigrationListController(bundle: Bundle? = null) :
private val smartSearchEngine = SmartSearchEngine(config?.extraSearchParams)
private val migrationScope = CoroutineScope(Job() + Dispatchers.IO)
var migrationsJob: Job? = null
private set
private var migratingManga: MutableList<MigratingManga>? = null
@@ -81,25 +83,29 @@ class MigrationListController(bundle: Bundle? = null) :
private val throttleManager = EHentaiThrottleManager()
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationListControllerBinding.inflate(inflater)
return binding.root
}
override fun getTitle(): String {
return resources?.getString(R.string.migration) + " (${adapter?.items?.count {
it.manga.migrationStatus != MigrationStatus.RUNNING
}}/${adapter?.itemCount ?: 0})"
}
override fun createBinding(inflater: LayoutInflater) = MigrationListControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
setTitle()
val config = this.config ?: return
val newMigratingManga = migratingManga ?: run {
val new = config.mangaIds.map {
MigratingManga(db, sourceManager, it, viewScope.coroutineContext + Dispatchers.IO)
MigratingManga(db, sourceManager, it, migrationScope.coroutineContext)
}
migratingManga = new.toMutableList()
new
@@ -114,7 +120,7 @@ class MigrationListController(bundle: Bundle? = null) :
adapter?.updateDataSet(newMigratingManga.map { it.toModal() })
if (migrationsJob == null) {
migrationsJob = viewScope.launchIO {
migrationsJob = migrationScope.launch {
runMigrations(newMigratingManga)
}
}
@@ -275,6 +281,7 @@ class MigrationListController(bundle: Bundle? = null) :
override fun onDestroy() {
super.onDestroy()
migrationScope.cancel()
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
import android.view.MenuItem
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper
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.preference.PreferencesHelper
@@ -102,16 +103,30 @@ class MigrationProcessAdapter(
// Update chapters read
if (MigrationFlags.hasChapters(flags)) {
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
val maxChapterRead = prevMangaChapters.filter { it.read }.maxByOrNull { it.chapter_number }?.chapter_number
if (maxChapterRead != null) {
val dbChapters = db.getChapters(manga).executeAsBlocking()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
val maxChapterRead =
prevMangaChapters.filter { it.read }.maxOfOrNull { it.chapter_number }
val dbChapters = db.getChapters(manga).executeAsBlocking()
val prevHistoryList = db.getHistoryByMangaId(prevManga.id!!).executeAsBlocking()
val historyList = mutableListOf<History>()
for (chapter in dbChapters) {
if (chapter.isRecognizedNumber) {
val prevChapter =
prevMangaChapters.find { it.isRecognizedNumber && it.chapter_number == chapter.chapter_number }
if (prevChapter != null) {
chapter.bookmark = prevChapter.bookmark
chapter.read = prevChapter.read
chapter.date_fetch = prevChapter.date_fetch
prevHistoryList.find { it.chapter_id == prevChapter.id }?.let { prevHistory ->
val history = History.create(chapter).apply { last_read = prevHistory.last_read }
historyList.add(history)
}
} else if (maxChapterRead != null && chapter.chapter_number <= maxChapterRead) {
chapter.read = true
}
}
db.insertChapters(dbChapters).executeAsBlocking()
}
db.insertChapters(dbChapters).executeAsBlocking()
db.updateHistoryLastRead(historyList).executeAsBlocking()
}
// Update categories
if (MigrationFlags.hasCategories(flags)) {
@@ -55,14 +55,12 @@ class MigrationProcessHolder(
binding.migrationMenu.setVectorCompat(
R.drawable.ic_more_vert_24dp,
view.context
.getResourceColor(R.attr.colorOnPrimary)
view.context.getResourceColor(R.attr.colorOnPrimary)
)
binding.skipManga.setVectorCompat(
R.drawable.ic_close_24dp,
view.context.getResourceColor(
R
.attr.colorOnPrimary
R.attr.colorOnPrimary
)
)
binding.migrationMenu.isInvisible = true
@@ -79,7 +77,8 @@ class MigrationProcessHolder(
true
).withFadeTransaction()
)
}.launchIn(adapter.controller.viewScope)
}
.launchIn(adapter.controller.viewScope)
}
/*launchUI {
@@ -115,7 +114,8 @@ class MigrationProcessHolder(
true
).withFadeTransaction()
)
}.launchIn(adapter.controller.viewScope)
}
.launchIn(adapter.controller.viewScope)
} else {
binding.migrationMangaCardTo.loadingGroup.isVisible = false
binding.migrationMangaCardTo.title.text = view.context.applicationContext
@@ -0,0 +1,7 @@
package eu.kanade.tachiyomi.ui.browse.migration.advanced.process
enum class MigrationStatus {
RUNNING,
MANGA_FOUND,
MANGA_NOT_FOUND
}
@@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -51,14 +51,17 @@ class MigrationMangaController :
return MigrationMangaPresenter(sourceId)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationMangaControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = MigrationMangaControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = MigrationMangaAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
@@ -5,9 +5,12 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsMultiChoice
import com.bluelinelabs.conductor.Controller
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.CatalogueSource
@@ -24,17 +27,37 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class SearchController(
private var manga: Manga? = null,
private var sources: List<CatalogueSource>? = null
) : GlobalSearchController(manga?.originalTitle) {
) : GlobalSearchController(
manga?.originalTitle,
bundle = bundleOf(
OLD_MANGA to manga?.id,
SOURCES to sources?.map { it.id }?.toLongArray()
)
) {
private var newManga: Manga? = null
private var progress = 1
var totalProgress = 0
constructor(mangaId: Long, sources: LongArray) :
this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking(),
sources.map { Injekt.get<SourceManager>().getOrStub(it) }.filterIsInstance<CatalogueSource>()
)
@Suppress("unused")
constructor(bundle: Bundle) : this(
bundle.getLong(OLD_MANGA),
bundle.getLongArray(SOURCES) ?: LongArray(0)
)
/**
* Called when controller is initialized.
*/
@@ -58,31 +81,15 @@ class SearchController(
)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable(::manga.name, manga)
outState.putSerializable(::newManga.name, newManga)
super.onSaveInstanceState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
manga = savedInstanceState.getSerializable(::manga.name) as? Manga
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
}
fun migrateManga() {
fun migrateManga(manga: Manga, newManga: Manga) {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
val nextManga = target.migrateManga(manga, newManga, true)
replaceWithNewSearchController(nextManga)
}
fun copyManga() {
fun copyManga(manga: Manga, newManga: Manga) {
val target = targetController as? MigrationInterface ?: return
val manga = manga ?: return
val newManga = newManga ?: return
val nextManga = target.migrateManga(manga, newManga, false)
replaceWithNewSearchController(nextManga)
@@ -102,14 +109,15 @@ class SearchController(
override fun onMangaClick(manga: Manga) {
if (targetController is MigrationListController) {
val migrationListController = targetController as? MigrationListController
val sourceManager: SourceManager by injectLazy()
val sourceManager = Injekt.get<SourceManager>()
val source = sourceManager.get(manga.source) ?: return
migrationListController?.useMangaForMigration(manga, source)
router.popCurrentController()
return
}
newManga = manga
val dialog = MigrationDialog()
val dialog =
MigrationDialog(this.manga ?: return, newManga ?: return, this)
dialog.targetController = this
dialog.showDialog(router)
}
@@ -119,12 +127,26 @@ class SearchController(
super.onMangaClick(manga)
}
class MigrationDialog : DialogController() {
class MigrationDialog(bundle: Bundle) : DialogController(bundle) {
constructor(manga: Manga, newManga: Manga, callingController: Controller) : this(
bundleOf(
MANGA_KEY to manga,
NEW_MANGA_KEY to newManga
)
) {
this.callingController = callingController
}
private val manga: Manga = args.getSerializable(MANGA_KEY) as Manga
private val newManga: Manga = args.getSerializable(NEW_MANGA_KEY) as Manga
private var callingController: Controller? = null
private val preferences: PreferencesHelper by injectLazy()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val prefValue = preferences.migrateFlags().get()
val callingController = callingController
val preselected =
MigrationFlags.getEnabledFlagsPositions(
@@ -132,7 +154,7 @@ class SearchController(
)
return MaterialDialog(activity!!)
.message(R.string.migration_dialog_what_to_include)
.title(R.string.migration_dialog_what_to_include)
.listItemsMultiChoice(
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
initialSelection = preselected.toIntArray()
@@ -145,13 +167,27 @@ class SearchController(
preferences.migrateFlags().set(newValue)
}
.positiveButton(R.string.migrate) {
(targetController as? SearchController)?.migrateManga()
if (callingController != null) {
if (callingController.javaClass == SourceSearchController::class.java) {
router.popController(callingController)
}
}
(targetController as? SearchController)?.migrateManga(manga, newManga)
}
.negativeButton(R.string.copy) {
(targetController as? SearchController)?.copyManga()
if (callingController != null) {
if (callingController.javaClass == SourceSearchController::class.java) {
router.popController(callingController)
}
}
(targetController as? SearchController)?.copyManga(manga, newManga)
}
.neutralButton(android.R.string.cancel)
}
companion object {
const val MANGA_KEY = "manga_key"
const val NEW_MANGA_KEY = "new_manga_key"
}
}
/**
@@ -183,4 +219,15 @@ class SearchController(
}
.launchIn(viewScope)
}
override fun onTitleClick(source: CatalogueSource) {
presenter.preferences.lastUsedSource().set(source.id)
router.pushController(SourceSearchController(manga!!, source, presenter.query).withFadeTransaction())
}
companion object {
const val OLD_MANGA = "old_manga"
const val SOURCES = "sources"
}
}
@@ -0,0 +1,38 @@
package eu.kanade.tachiyomi.ui.browse.migration.search
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceItem
class SourceSearchController(
bundle: Bundle
) : BrowseSourceController(bundle) {
constructor(manga: Manga, source: CatalogueSource, searchQuery: String? = null) : this(
bundleOf(
SOURCE_ID_KEY to source.id,
MANGA_KEY to manga,
SEARCH_QUERY_KEY to searchQuery
)
)
private var oldManga: Manga = args.getSerializable(MANGA_KEY) as Manga
private var newManga: Manga? = null
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? SourceItem ?: return false
newManga = item.manga
val searchController = router.backstack.findLast { it.controller().javaClass == SearchController::class.java }?.controller() as SearchController?
val dialog =
SearchController.MigrationDialog(oldManga, newManga!!, this)
dialog.targetController = searchController
dialog.showDialog(router)
return true
}
private companion object {
const val MANGA_KEY = "oldManga"
}
}
@@ -5,8 +5,8 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -41,14 +41,17 @@ class MigrationSourcesController :
return MigrationSourcesPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationSourcesControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = MigrationSourcesControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = SourceAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
@@ -28,9 +28,14 @@ class MigrationSourcesPresenter(
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
val header = SelectionHeader()
return library.asSequence().map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID /* SY --> */ && it != MERGED_SOURCE_ID /* SY <-- */) sourceManager.getOrStub(it) else null }
.sortedBy { it.name.toLowerCase() }
.map { SourceItem(it, header) }.toList()
return library
.groupBy { it.source }
.filterKeys { it != LocalSource.ID /* SY --> */ && it != MERGED_SOURCE_ID /* SY <-- */ }
.map {
val source = sourceManager.getOrStub(it.key)
SourceItem(source, it.value.size, header)
}
.sortedBy { it.source.name.toLowerCase() }
.toList()
}
}
@@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
/**
* Item that contains the selection header.
@@ -18,7 +18,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.source_main_controller_card_header
return R.layout.section_header_item
}
/**
@@ -46,7 +46,7 @@ class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
}
class Holder(view: View, adapter: FlexibleAdapter</* SY --> */ IFlexible<RecyclerView.ViewHolder> /* SY <-- */>) : FlexibleViewHolder(view, adapter) {
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
private val binding = SectionHeaderItemBinding.bind(view)
init {
binding.title.text = view.context.getString(/* SY --> */ R.string.select_a_source_to_migrate_from /* SY <-- */)
@@ -26,8 +26,8 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
fun bind(item: SourceItem) {
val source = item.source
binding.title.text = source.name
binding.subtitle.isVisible = true
binding.title.text = "${source.name} (${item.mangaCount})"
binding.subtitle.isVisible = source.lang != ""
binding.subtitle.text = LocaleHelper.getDisplayName(source.lang)
itemView.post {
@@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.Source
* @param source Instance of [Source] containing source information.
* @param header The header for this item.
*/
data class SourceItem(val source: Source, val header: SelectionHeader) :
data class SourceItem(val source: Source, val mangaCount: Int, val header: SelectionHeader) :
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
/**
@@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.ui.browse.source
import android.view.View
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardHeaderBinding
import eu.kanade.tachiyomi.databinding.SectionHeaderItemBinding
import eu.kanade.tachiyomi.util.system.LocaleHelper
class LangHolder(view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter) {
private val binding = SourceMainControllerCardHeaderBinding.bind(view)
private val binding = SectionHeaderItemBinding.bind(view)
fun bind(item: LangItem) {
binding.title.text = LocaleHelper.getSourceDisplayName(item.code, itemView.context)
@@ -18,7 +18,7 @@ data class LangItem(val code: String) : AbstractHeaderItem<LangHolder>() {
* Returns the layout resource of this item.
*/
override fun getLayoutRes(): Int {
return R.layout.source_main_controller_card_header
return R.layout.section_header_item
}
/**
@@ -9,14 +9,13 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItems
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@@ -28,7 +27,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.BrowseController
@@ -39,12 +38,7 @@ import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.ui.category.sources.ChangeSourceCategoriesDialog
import eu.kanade.tachiyomi.util.system.toast
import exh.ui.smartsearch.SmartSearchController
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.parcelize.Parcelize
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -55,7 +49,7 @@ import uy.kohesive.injekt.api.get
* [SourceAdapter.OnLatestClickListener] call function data on latest item click
*/
class SourceController(bundle: Bundle? = null) :
NucleusController<SourceMainControllerBinding, SourcePresenter>(bundle),
SearchableNucleusController<SourceMainControllerBinding, SourcePresenter>(bundle),
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
SourceAdapter.OnSourceClickListener,
@@ -93,21 +87,17 @@ class SourceController(bundle: Bundle? = null) :
return SourcePresenter(/* SY --> */ controllerMode = mode /* SY <-- */)
}
/**
* Initiate the view with [R.layout.source_main_controller].
*
* @param inflater used to load the layout xml.
* @param container containing parent views.
* @return inflated view.
*/
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = SourceMainControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = SourceMainControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
adapter = SourceAdapter(this)
// Create recycler and set adapter.
@@ -203,7 +193,7 @@ class SourceController(bundle: Bundle? = null) :
items.add(
Pair(
activity.getString(R.string.label_categories),
activity.getString(R.string.categories),
{ addToCategories(item.source) }
)
)
@@ -333,44 +323,6 @@ class SourceController(bundle: Bundle? = null) :
}
// SY <--
/**
* Adds items to the options menu.
*
* @param menu menu containing options.
* @param inflater used to load the menu xml.
*/
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
// Inflate menu
inflater.inflate(R.menu.source_main, menu)
// SY -->
if (mode == Mode.SMART_SEARCH) {
menu.findItem(R.id.action_search).isVisible = false
menu.findItem(R.id.action_settings).isVisible = false
}
// SY <--
// Initialize search option.
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
// Change hint to show global search.
searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint)
// Create query listener which opens the global search view.
searchView.queryTextEvents()
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
.onEach { performGlobalSearch(it.queryText.toString()) }
.launchIn(viewScope)
}
private fun performGlobalSearch(query: String) {
parentController!!.router.pushController(
GlobalSearchController(query).withFadeTransaction()
)
}
/**
* Called when an option menu item has been selected by the user.
*
@@ -431,6 +383,29 @@ class SourceController(bundle: Bundle? = null) :
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (mode == Mode.CATALOGUE) {
createOptionsMenu(
menu,
inflater,
R.menu.source_main,
R.id.action_search,
R.string.action_global_search_hint,
false // GlobalSearch handles the searching here
)
}
}
override fun onSearchViewQueryTextSubmit(query: String?) {
// SY -->
if (mode == Mode.CATALOGUE) {
parentController!!.router.pushController(
GlobalSearchController(query).withFadeTransaction()
)
}
// SY <--
}
// SY -->
@Parcelize
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long? = null) : Parcelable
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.databinding.SourceMainControllerCardItemBinding
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.setVectorCompat
class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY --> */, private val showLatest: Boolean, private val showPins: Boolean /* SY <-- */) :
@@ -52,9 +51,9 @@ class SourceHolder(private val view: View, val adapter: SourceAdapter /* SY -->
binding.pin.isVisible = showPins
if (item.isPinned) {
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, view.context.getResourceColor(R.attr.colorAccent))
binding.pin.setVectorCompat(R.drawable.ic_push_pin_24dp, R.attr.colorAccent)
} else {
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, view.context.getResourceColor(android.R.attr.textColorHint))
binding.pin.setVectorCompat(R.drawable.ic_push_pin_outline_24dp, android.R.attr.textColorHint)
}
}
}
@@ -8,7 +8,6 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.GridLayoutManager
@@ -17,10 +16,10 @@ import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import com.afollestad.materialdialogs.list.listItems
import com.elvishew.xlog.XLog
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.tfcporciuncula.flow.Preference
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
@@ -38,7 +37,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
import eu.kanade.tachiyomi.ui.browse.source.SourceController
@@ -57,25 +56,23 @@ import eu.kanade.tachiyomi.util.view.shrinkOnScroll
import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.widget.AutofitRecyclerView
import eu.kanade.tachiyomi.widget.EmptyView
import exh.log.xLogW
import exh.md.similar.ui.EnableMangaDexSimilarDialogController
import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource
import exh.source.isEhBasedSource
import exh.widget.preference.MangadexLoginDialog
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.appcompat.QueryTextEvent
import reactivecircus.flowbinding.appcompat.queryTextEvents
import uy.kohesive.injekt.injectLazy
/**
* Controller to manage the catalogues available in the app.
*/
open class BrowseSourceController(bundle: Bundle) :
NucleusController<SourceControllerBinding, BrowseSourcePresenter>(bundle),
SearchableNucleusController<SourceControllerBinding, BrowseSourcePresenter>(bundle),
FabController,
FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
@@ -162,10 +159,7 @@ open class BrowseSourceController(bundle: Bundle) :
// SY <--
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = SourceControllerBinding.inflate(inflater)
return binding.root
}
override fun createBinding(inflater: LayoutInflater) = SourceControllerBinding.inflate(inflater)
override fun onViewCreated(view: View) {
super.onViewCreated(view)
@@ -186,8 +180,8 @@ open class BrowseSourceController(bundle: Bundle) :
preferences.shownMangaDexSimilarAskDialog().set(true)
}
if (mainSource is LoginSource && mainSource.needsLogin && !mainSource.isLogged()) {
val dialog = mainSource.getLoginDialog(mainSource, activity!!)
if (mainSource is LoginSource && mainSource.requiresLogin && !mainSource.isLogged()) {
val dialog = MangadexLoginDialog(mainSource)
dialog.showDialog(router)
}
// SY <--
@@ -389,6 +383,11 @@ open class BrowseSourceController(bundle: Bundle) :
actionFab?.shrinkOnScroll(recycler)
}
recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
recycler.setHasFixedSize(true)
recycler.adapter = adapter
@@ -401,25 +400,8 @@ open class BrowseSourceController(bundle: Bundle) :
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.source_browse, menu)
// Initialize search menu
createOptionsMenu(menu, inflater, R.menu.source_browse, R.id.action_search)
val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView
searchView.maxWidth = Int.MAX_VALUE
val query = presenter.query
if (query.isNotBlank()) {
searchItem.expandActionView()
searchView.setQuery(query, true)
searchView.clearFocus()
}
searchView.queryTextEvents()
.filter { router.backstack.lastOrNull()?.controller() == this@BrowseSourceController }
.filterIsInstance<QueryTextEvent.QuerySubmitted>()
.onEach { searchWithQuery(it.queryText.toString()) }
.launchIn(viewScope)
searchItem.fixExpand(
onExpand = { invalidateMenuOnExpand() },
@@ -427,6 +409,7 @@ open class BrowseSourceController(bundle: Bundle) :
if (router.backstackSize >= 2 && router.backstack[router.backstackSize - 2].controller() is GlobalSearchController) {
router.popController(this)
} else {
nonSubmittedQuery = ""
searchWithQuery("")
}
@@ -450,6 +433,10 @@ open class BrowseSourceController(bundle: Bundle) :
// SY <--
}
override fun onSearchViewQueryTextSubmit(query: String?) {
searchWithQuery(query ?: "")
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
@@ -542,8 +529,8 @@ open class BrowseSourceController(bundle: Bundle) :
*/
/* SY --> */ open /* SY <-- */fun onAddPageError(error: Throwable) {
// SY -->
XLog.tag("BrowseSourceController").enableStackTrace(2).w("> Failed to load next catalogue page!", error)
XLog.tag("BrowseSourceController").enableStackTrace(2).w(
xLogW("> Failed to load next catalogue page!", error)
xLogW(
"> (source.id: %s, source.name: %s)",
presenter.source.id,
presenter.source.name

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