Compare commits

...

509 Commits

Author SHA1 Message Date
Jobobby04 51a109285a Release 1.7.0 2021-06-01 20:48:37 -04:00
Jobobby04 02601aa32a Temporarily disable Double Page Spread 2021-06-01 18:40:42 -04:00
Jobobby04 60e520e37e Temporarily disable Mangadex delegation 2021-06-01 18:33:48 -04:00
arkon 3fb4b565fa Temporarily hide Komga tracker
(cherry picked from commit 31997fe50a)
2021-06-01 18:22:12 -04:00
arkon b2e33ab950 Update some icons
(cherry picked from commit 5e5ceef122)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceController.kt
2021-06-01 18:22:00 -04:00
Jozef Hollý acad70d8db Weblate translations (#5141)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Dhimas Admaja <dhimasadmaja@gmail.com>
Co-authored-by: Elosy <gaudic99@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: HeavenShadow <heavenshadow@outlook.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@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: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Riztard Lanthorn <riyanluqman@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Wojciech Kosztyła <wojtex1221@gmail.com>
Co-authored-by: matdeluis <luisdebonoir@protonmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: whenwesober <naomi16i_1298q@cikuh.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/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/jv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
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/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: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Dhimas Admaja <dhimasadmaja@gmail.com>
Co-authored-by: Elosy <gaudic99@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: HeavenShadow <heavenshadow@outlook.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@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: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Riztard Lanthorn <riyanluqman@gmail.com>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tooster <max@polarczyk.pl>
Co-authored-by: Wojciech Kosztyła <wojtex1221@gmail.com>
Co-authored-by: matdeluis <luisdebonoir@protonmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: whenwesober <naomi16i_1298q@cikuh.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit 40edbac7f0)
2021-06-01 18:17:40 -04:00
Jobobby04 ebc1f2bb41 Update string reference 2021-06-01 18:17:06 -04:00
arkon 391e477e04 Remove more unused strings
(cherry picked from commit 8622e6492c)
2021-05-31 23:48:06 -04:00
arkon 5fa3d55bc9 Show toast when toggling crop borders
Consistent with reading mode and orientation shortcuts.

(cherry picked from commit 1feac9c559)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-05-31 23:47:48 -04:00
Jobobby04 ef02bd112c Fix some string references 2021-05-31 23:44:15 -04:00
arkon ae7d94a1f2 Remove unused strings
(cherry picked from commit fce81dd6d9)
2021-05-31 23:44:15 -04:00
arkon 0a96252ce8 Fix last library category not being saved
(cherry picked from commit aa50554f06)
2021-05-31 23:44:15 -04:00
Jobobby04 7907723623 Move rec button higher on tablet view 2021-05-31 23:44:14 -04:00
Eugene 8d3c0199b4 Rus locale fix/additions (#330)
* Rus locale fix/additions

* Delete Gallery types (because filters are still in English)

* New strings

* New 2

* fix1
2021-05-31 23:26:24 -04:00
arkon d5fcded898 Fix toolbar menu being broken on transition (fixes #5135)
(cherry picked from commit 034506f56b)
2021-05-29 18:19:40 -04:00
arkon 674a9c5067 Fix double free when when parsing webp images (fixes #5227)
Co-authored-by: inorichi <inorichi@users.noreply.github.com>
(cherry picked from commit 2d8858edb4)
2021-05-29 18:19:32 -04:00
arkon 1f37d571cf [SKIP CI] Add issue moderator action
(cherry picked from commit b2601ad696)
2021-05-29 18:19:24 -04:00
arkon 36c45c9450 Run formatter on some resources
(cherry picked from commit 8099f561c5)
2021-05-29 18:19:16 -04:00
arkon cc2f976c81 Update AboutLibraries
(cherry picked from commit 8a014ddb0c)
2021-05-29 18:19:05 -04:00
arkon d92e790c5e Try to avoid some Webview-related crashes
Related to #5218

(cherry picked from commit 3d9383ce67)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
2021-05-29 18:18:56 -04:00
Ivan Iskandar 7d659f559e Adjust chapter download button visual (#5213)
* Removed the blinking icon and added back the indeterminate indicator for
queued items
* Make the downloading indicator a solid circle

(cherry picked from commit 9de07c11a6)
2021-05-29 18:18:10 -04:00
Jobobby04 3a20e24ad0 Fix some drawable references 2021-05-29 18:17:54 -04:00
Ivan Iskandar 9a97a97aa7 More bottom sheet improvements (#5183)
* Edge-to-edge bottom sheet when possible

* ReaderSettingsSheet: Animate background dim changes

* Adjust modal bottom sheet in-out animation

* Use public method to get bottom sheet behavior

* Set bottom sheet peek size to 50% screen height

The current auto peek height gives too low value on landscape orientation

* Set bottom sheet navigation bar scrim when needed

(cherry picked from commit 9f744bc445)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2021-05-29 17:58:17 -04:00
Soitora 84076c2582 Add animations for Bottom Navigation items (#5181)
* Animated icon for the Library tab

Co-Authored-By: CrepeTF <70870719+CrepeTF@users.noreply.github.com>

* Animated icon for the More tab

Co-Authored-By: CrepeTF <70870719+CrepeTF@users.noreply.github.com>

* Rename more_vert to overflow

Should maybe help any confusion together with the other more_horiz changes.

* Animated icon for the History tab

Co-Authored-By: CrepeTF <70870719+CrepeTF@users.noreply.github.com>

* Clarify names and clean files

* Animated icon for the Updates tab

* Animated icon for the Browse tab

* Recreate the animated icon for the History tab

History icons look better when the internal clock is not moving.

Co-authored-by: CrepeTF <70870719+CrepeTF@users.noreply.github.com>
(cherry picked from commit aed6e12119)
2021-05-29 17:57:44 -04:00
Jobobby04 b67d2bba40 Last chapter can be null 2021-05-28 21:17:26 -04:00
Jobobby04 9fa278f708 Fix SY themes a bit more 2021-05-28 14:36:53 -04:00
Jobobby04 56ea025e20 Update coroutines, fix E-Hentai gallery update restrictions 2021-05-28 14:36:52 -04:00
arkon dab002cf4d Update GMS plugin
(cherry picked from commit c57d0046bc)
2021-05-28 14:36:52 -04:00
arkon bca6f39a33 Update Kotlin and kotlinter
If this breaks anything, I blame Jay.

(cherry picked from commit 07b9fc9b31)
2021-05-28 14:36:52 -04:00
arkon fbfad27c27 Dependency updates
(cherry picked from commit 2c6bcb85a0)
2021-05-28 14:36:51 -04:00
Soitora a93129c4f2 Allow themes to style more objects (#5197)
* Add Tertiary color and use it for Badges

* Define ripple color for Material Dialog

Although it doesn't do anything, but it should

* Add tertiary colors for Green Apple theme

* Use the correct theme accent in AlertDialog

* Declare a global colorControlHighlight

Fixes some of the ripples listed on #5154.

* Change md_ripple_color to primary ripple color

(cherry picked from commit 11a232a2df)
2021-05-28 14:36:51 -04:00
Hunter Nickel 89531cd549 Implement feature to hide "Local" badge from library manga (#5202)
(cherry picked from commit 8dcd919ff0)
2021-05-28 14:36:51 -04:00
Gauthier e50f04ec54 Display the currently active restrictions in the library update preference (#5187)
* display the currently active restrictions in the library update preference

* removed first line

* use constant instead of literal string

* remove spanned string builder

(cherry picked from commit d9c27e7109)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
2021-05-28 14:36:50 -04:00
arkon d8b40c2dc4 Add link to official subreddit
(cherry picked from commit 8af8c57bb4)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutLinksPreference.kt
2021-05-28 14:36:50 -04:00
arkon d62de90b77 Update dependencies
(cherry picked from commit a1a4916abf)
2021-05-28 14:36:49 -04:00
Soitora ab14ce0d75 Set grey setting sheet for Grey background option (#5193)
(cherry picked from commit 9be8f675ac)
2021-05-28 14:36:49 -04:00
inorichi f2efe49ea1 Fix GIF detection on previous commit
(cherry picked from commit a271c3726e)
2021-05-28 14:36:49 -04:00
inorichi 2eb5436b25 Display animated webp whenever possible, otherwise fallback to static image. Fixes #5139
(cherry picked from commit 8c18a14dfd)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
2021-05-28 14:36:48 -04:00
Gauthier fba4bd163b Hide the score display on the tracksheet if not supported (#5169)
* hide the score display on the tracksheet if not supported

* Convert track item to use LinearLayout

Co-authored-by: Andreas E <andreas.everos@gmail.com>
(cherry picked from commit 9a801cfdfb)
2021-05-28 14:36:48 -04:00
Soitora ae073f9207 Added "Green Apple" theme (#5184)
* Added "Green Apple" theme

Totally not stolen from other forks.

Although to cover all my bases I co-author all people who adapted the theme in any form.

Neko, J2K and Tako

Co-Authored-By: Carlos <2092019+CarlosEsco@users.noreply.github.com>
Co-Authored-By: Jays2Kings <jays@outlook.com>
Co-Authored-By: CrepeTF <70870719+CrepeTF@users.noreply.github.com>

* Add black as the OnSecondary color

Co-authored-by: Carlos <2092019+CarlosEsco@users.noreply.github.com>
Co-authored-by: Jays2Kings <jays@outlook.com>
Co-authored-by: CrepeTF <70870719+CrepeTF@users.noreply.github.com>
(cherry picked from commit 4af13e3536)
2021-05-28 14:36:47 -04:00
Gauthier 1c084d42df Background tracker update during Library update (#5166)
* add preference to auto update trackers during library update

* also update trackers when updating chapters and preference is set

* remove unnecessary launch/join

* perform tracking update within the same chapter update loop to avoid double notifications

(cherry picked from commit e76e903060)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
2021-05-28 14:36:47 -04:00
Jobobby04 f245bbcfeb Move Kapt 2021-05-28 14:36:47 -04:00
arkon 29310c86e2 Use 1.x source-api artifact from Maven Central
(cherry picked from commit d8251224cb)
2021-05-28 14:36:46 -04:00
Gauthier f492ad2529 Refactor TrackService.kt to remove unused add function (#5164)
* removed the add function from TrackService.kt as it's not used except within the tracker implementations

* add private modifier

(cherry picked from commit acd927a937)
2021-05-28 14:36:46 -04:00
Jobobby04 33b6912c22 nullIfEmpty 2021-05-28 14:36:45 -04:00
Jobobby04 9e63f32a82 Localize Find in another source 2021-05-28 14:36:45 -04:00
arkon 834e3d78ab Put shortcut to backup menu in More
(cherry picked from commit a498f940c6)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/MoreController.kt
2021-05-28 14:36:45 -04:00
arkon 2d2378a1e2 Use Title Case for theme names
(cherry picked from commit 948cb31d1a)
2021-05-28 14:36:44 -04:00
arkon 3cc61bc3b8 Make some strings in LocalSource translatable (closes #5178)
(cherry picked from commit 179cb8eb50)
2021-05-28 14:36:44 -04:00
arkon 6b771b4a70 Handle toolbar title in manga details on tablets similar to on phones
(cherry picked from commit 47f865aa72)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
2021-05-28 14:36:43 -04:00
arkon 9c2eadd8ca Remove unused Gson singleton factory
We don't inject it anywhere anymore.

(cherry picked from commit b47face2f8)
2021-05-28 14:36:43 -04:00
Ivan Iskandar f09c977661 TabbedBottomSheetDialog: Fix scrollable pages (#5173)
(cherry picked from commit 69869115f6)
2021-05-28 14:36:43 -04:00
Soitora 2acc364960 Added "Strawberry Daiquiri" theme (#5176)
(cherry picked from commit 0fb9ca3e8b)
2021-05-28 14:36:42 -04:00
arkon c63300d9ad Tweak tablet manga info column width
(cherry picked from commit eaf9c9b2d8)
2021-05-28 14:36:42 -04:00
arkon c90aec5c3d Organize some classes
(cherry picked from commit 70d9b0c390)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
2021-05-28 14:36:41 -04:00
Gauthier 8e6f04f258 pull the rate limit interceptors from the extensions repo (#5163)
apply a rate limit to anilist, current limit is 90 per minute

(cherry picked from commit e57a999c9c)
2021-05-28 14:36:41 -04:00
arkon 354c61cf59 Update AGP and AboutLibraries
(cherry picked from commit 3b49289cfb)
2021-05-28 14:36:41 -04:00
Soitora 606101dc8b Add "Midnight Dusk" and "Hot Pink" themes (#5161)
* Organize and clarify themes file

Increases clarification with better commenting, should make it easier to add or modify current themes.

* Make AMOLED its own theme category

* Tweak ripples for AMOLED

* Add "Midnight Dusk" theme

Ports it from jobobby04/TachiyomiSY.

Co-Authored-By: CrepeTF <70870719+CrepeTF@users.noreply.github.com>

* Add "Hot Pink" theme

Ports it from jobobby04/TachiyomiSY.

Co-Authored-By: OncePunchedMan <64155117+OncePunchedMan@users.noreply.github.com>

* Make AMOLED a base theme

* Final tweaks

Rename "Toolbar.Light" to "Custom.PopupTheme"
Changes placing of backgroundDusk

Removes HotPink and MidnightDusk custom Toolbars, I believe they are unnecessary.

* Rename a Midnight Dusk color

* Make AMOLED independent from Dark as a theme

Co-authored-by: CrepeTF <70870719+CrepeTF@users.noreply.github.com>
Co-authored-by: OncePunchedMan <64155117+OncePunchedMan@users.noreply.github.com>
(cherry picked from commit 176e984b56)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
#	app/src/main/res/values/styles.xml
#	app/src/main/res/values/themes.xml
2021-05-28 14:36:40 -04:00
Ivan Iskandar 97eda86ac4 Remove top margin of PreferenceCategory if no title set (#5168)
(cherry picked from commit b5a700276a)
2021-05-28 14:36:40 -04:00
Ivan Iskandar dd4384767c Fix incognito mode disabled after the app kicked out of memory (#5167)
The application class onCreate will also be called when user navigates to an
activity after the app process is killed by the system.

So make sure the incognito is disabled only when the entry point of the app is
created from scratch (e.g. after being force closed by the user).

(cherry picked from commit 3c186a3c8d)
2021-05-28 14:36:39 -04:00
Jobobby04 b18075de14 Cleanup 2021-05-28 14:36:39 -04:00
Jobobby04 c39ec81f42 Metadata cleanup 2021-05-28 14:36:38 -04:00
Eugene 99d23a47b7 18+ tag to Lewd Check (#336) 2021-05-25 14:54:54 -04:00
Jobobby04 481f600056 Double page spread share/save options 2021-05-23 18:23:45 -04:00
Jobobby04 143d0d2518 Fix reader settings crash 2021-05-23 18:23:44 -04:00
Soitora 38fef11287 Fix SY themes after custom ripple changes (#332)
* Fix SY Themes and Ripples

* Slightly increase ripple brightness in AMOLED

* Override AMOLED highlight for the failure that is Hot Pink
2021-05-23 18:23:22 -04:00
arkon 9adbb1b115 Option to move nav rail buttons to bottom of screen (closes #5158)
Based on https://github.com/Jays2Kings/tachiyomiJ2K/commit/90be3e34948d41daf3fd695ef09d60eb6adf960e

Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
(cherry picked from commit a462ce3626)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/res/values/strings.xml
2021-05-23 15:40:27 -04:00
arkon ec9967d2d6 Fix reader chapter sort ordering (fixes #5157)
(cherry picked from commit 065cf42aea)
2021-05-23 15:36:20 -04:00
arkon ba0e353ea1 Revert "Prevent view from being removed if a to is found (#5135) (#5152)"
This reverts commit 974275a429.

(cherry picked from commit 986b709f2c)
2021-05-23 15:36:10 -04:00
Soitora e5131e1985 Create a toolbar variable for ripples (#5159)
* Rename rippleNavColor to rippleSecondaryColor

It's not just for the navigation anymore

* Add a rippleToolbarColor for toolbar ripples

Fixes so you can use specialized colors in case of using a theme such as Dark Blue or Light Blue

* Add so text buttons are also themed secondary color

Apparently this is also a common Google app thing

* Changes variable name for Navigation Rail

(cherry picked from commit fed6f44995)

# Conflicts:
#	app/src/main/res/values/styles.xml
#	app/src/main/res/values/themes.xml
2021-05-23 15:35:59 -04:00
arkon 43555b3b1a Hide irrelevant settings on tablets
(cherry picked from commit 1b52acdad7)
2021-05-23 15:34:50 -04:00
arkon 3419bebb70 Update Discord logo asset (closes #5143)
(cherry picked from commit 10a638c6b8)
2021-05-23 15:34:42 -04:00
Jobobby04 b13ca1a097 Lint and build fixes 2021-05-23 15:34:29 -04:00
arkon dd914047f8 Tablet manga view
(cherry picked from commit 7875f363a8)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
2021-05-23 15:34:13 -04:00
arkon 666447faac Fix resume button not considering filters
(cherry picked from commit 685736b9ec)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2021-05-23 14:56:33 -04:00
arkon e9a21a6bbe Use same chapter sorting logic in manga and reader views
(cherry picked from commit aefd2bf6f8)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt
2021-05-23 14:54:40 -04:00
arkon de4e0abef4 Update reader chapter list filtering to handle not downloaded/bookmarked (closes #5107)
(cherry picked from commit ce9fb2f1fe)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
2021-05-23 14:51:35 -04:00
Antoine Gaudreau Simard ac7e2909ba Prevent view from being removed if a to is found (#5135) (#5152)
Introduced in c68e7c8da7

(cherry picked from commit 974275a429)
2021-05-23 14:49:55 -04:00
arkon 14de1973a3 Fix some RecyclerView heights (fixes #5146)
(cherry picked from commit 98461f9bca)
2021-05-23 14:49:46 -04:00
Jobobby04 3053bf9d5d Double page spread
(cherry picked from commit 7832d1abe1fdcdb962f388e5a86dd3fcecad6712)
2021-05-23 14:49:09 -04:00
Jobobby04 3192140421 Disable mangadex covers and use temp covers 2021-05-23 14:48:14 -04:00
Jobobby04 80cf38a70d Fix mangadex covers 2021-05-23 14:43:12 -04:00
arkon 42bbf07859 Update ripples for tablet NavigationRail (fixes #5145)
(cherry picked from commit 094f78fb41)
2021-05-22 18:28:39 -04:00
Soitora 9b4aac7ce5 Ripples patch (#5144)
* Combine ripple_dark and ripple_light

Fixes AMOLED theme and doesn't require two seperate items.

* Fix ripple for the new About icons

* Fixes nav/tab ripple in Dark Blue and Light Blue themes

* Theme some ripples using their Style instead

(cherry picked from commit 33dcdc1599)

# Conflicts:
#	app/src/main/res/values/themes.xml
2021-05-22 18:28:04 -04:00
arkon ab64e51c6b Fix action toolbar positioning (fixes #5099)
(cherry picked from commit 8870ccb18c)
2021-05-22 18:22:36 -04:00
arkon 82a92b9497 Fallback to default viewer properly (closes #5068)
(cherry picked from commit 2a7ed1375a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
2021-05-22 18:22:21 -04:00
arkon a855c4a929 Fix vector from being rasterized during build
(cherry picked from commit 54b50cca71)
2021-05-22 18:08:28 -04:00
arkon de6c428d72 Enforce same height for about links
Discord is kind of scuffed because the icon isn't vertically centered properly.

(cherry picked from commit 1c10ba7925)
2021-05-22 18:08:20 -04:00
Soitora a34133f526 Change tab ripple color (#5142)
Does what Google apps does, uses the colored ripple for Tabs as well and not just bottom nav.

(cherry picked from commit 2b8df691ff)
2021-05-22 18:08:13 -04:00
arkon 56d127003f Shrink Komga logo asset
(cherry picked from commit 15da856303)
2021-05-22 18:07:55 -04:00
Jozef Hollý 323ce4dbab Weblate translations (#4973)
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: Allen Chang <allen.ty.chang@gmail.com>
Co-authored-by: Andreas <howangandreas@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Bùi Nguyễn Hoàng Thọ <buinguyenhoangtho97@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
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: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Hajba Károly <karoly.hajba98@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jacque Fresco <aidter@use.startmail.com>
Co-authored-by: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: LigthA_ <ligthaa@gmail.com>
Co-authored-by: Lyaiya <hipsnafoha@outlook.com>
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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Maxime Dias <maxime-dias_student2021@wilder.school>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Narin <narin.tana@hotmail.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: Pratik Subedi <pratikk.subedee@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: SmolderingGummy <bairamsaieesh@gmail.com>
Co-authored-by: Techeira Damián <damian.techeira@mercadolibre.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: plr20 <hobdob@tuta.io>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: waitingmoon <takeda.s1027@gmail.com>
Co-authored-by: Николаев Павел Дмитриевич <pavliknikolaev128@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/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/ms/
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/ru/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sah/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/te/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/th/
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/vi/
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: 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: Allen Chang <allen.ty.chang@gmail.com>
Co-authored-by: Andreas <howangandreas@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Bùi Nguyễn Hoàng Thọ <buinguyenhoangtho97@gmail.com>
Co-authored-by: C201 <derasetad@gmail.com>
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: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Hajba Károly <karoly.hajba98@gmail.com>
Co-authored-by: Jacque Fresco <aidter@use.startmail.com>
Co-authored-by: Jakub Fabijan <animatorzPolski@gmail.com>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: LigthA_ <ligthaa@gmail.com>
Co-authored-by: Lyaiya <hipsnafoha@outlook.com>
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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Maxime Dias <maxime-dias_student2021@wilder.school>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Narin <narin.tana@hotmail.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: Pratik Subedi <pratikk.subedee@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: SmolderingGummy <bairamsaieesh@gmail.com>
Co-authored-by: Techeira Damián <damian.techeira@mercadolibre.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: plr20 <hobdob@tuta.io>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: waitingmoon <takeda.s1027@gmail.com>
Co-authored-by: Николаев Павел Дмитриевич <pavliknikolaev128@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
(cherry picked from commit a62628423f)
2021-05-22 18:07:43 -04:00
Ivan Iskandar a802eb1cca Bump compileSdk to 30 (#5140)
(cherry picked from commit ef8a87a30f)
2021-05-22 18:07:27 -04:00
arkon 3bb005acb2 Disable swiping on switch in SwitchSettingsPreference
(cherry picked from commit 147978b932)
2021-05-22 17:56:44 -04:00
arkon d8ee654a65 Rename some biometrics things since it's no longer specifically for biometric auth
(cherry picked from commit c741920ec0)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt
2021-05-22 17:56:36 -04:00
CrepeTF 2b075b5a39 Fixed tracker sheet corner radius not being rounded on start (#4799)
(cherry picked from commit bbbcb18b91)
2021-05-22 17:49:55 -04:00
Gauthier e085855ec6 Add Komga as an unattended track service (#5049)
* fix: prevent crash if TrackService.getScoreList() is empty

* disabled track score button if service doesn't support scoring

* first implementation of the Komga tracking
this doesn't work for read lists

* auto track when adding to library

* handle refresh

* 2-way sync of chapters for unattended tracking services

* Update app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSheet.kt

Co-authored-by: Andreas <andreas.everos@gmail.com>

* group strings together

* support for read lists

* sync read chapters on bind

* only mark local chapters as read during 2-way sync (incoming)

* local progress from read chapters will be sent to remote tracker on bind/refresh
this enables syncing after reading offline

* remove unused variable

* refactor the 2-way sync in a util function

* handle auto add to track for unattended services from the browse source screen when long clicking
this will also sync chapters, as it is possible to have read or marked as read chapters from there

* 2-way sync when library update for TRACKING

* refactor

* better handling of what has been read server side

* refactor: extract function

* fix: localLastRead could be -1 when all chapters are read

* refactor to rethrow exception so it can be shown in toast

* extract strings

* replace komga logo

Co-authored-by: Andreas <andreas.everos@gmail.com>
(cherry picked from commit d6b3b0baf7)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt
2021-05-22 17:49:45 -04:00
arkon 1518c2aa25 Run formatter on drawables and layouts
(cherry picked from commit dbe8931cf0)

# Conflicts:
#	app/src/main/res/drawable/ic_launcher_foreground.xml
#	app/src/main/res/layout/main_activity.xml
#	app/src/main/res/layout/manga_info_header.xml
#	app/src/main/res/layout/reader_activity.xml
#	app/src/main/res/layout/reader_general_settings.xml
2021-05-22 17:47:08 -04:00
arkon 623b64aa79 Fix top padding in up Updates/History
(cherry picked from commit d2eb5d7f45)
2021-05-22 17:41:05 -04:00
Soitora 2f9069765e Ripple overhaul (#5109)
* Create drawables for the ripples

Temporary colors in them to aid in unifying the ripples.

The 'ripple_circular' and 'ripple_normal' are probably going to be merge as one in the end.

* Change selectableItems to drawables

Changes 'selectableItemBackgroundBorderless' to 'ripple_circular' drawable.

Changes 'selectableItemBackground' to 'selectable_item_background' drawable.

* Add temporary colors to aid in finding unstyled ripples

* Fix button sizes to not make oval ripples

* Make the chip selectable follow ripple color

* Style using the built in rippleColor when possible

* Ripple away 💸

* Set ripple color for tabs

Main activity tabs as well as sheet tabs

* Set ripple color in seekbar buttons

* Fix ripple color for the toolbar

* Round off and start to finish the ripples

* Set custom colorful ripple for bottom navigation

Makes the app a little more fun than just black and white. Took inspiration from a ton of updated Google apps.

* Revert two layout changes

These were not necessary for the ripple as it is designed now, but it was before.

Co-authored-by: Andreas E <andreas.everos@gmail.com>
(cherry picked from commit 562dce60ee)

# Conflicts:
#	app/src/main/res/layout/main_activity.xml
#	app/src/main/res/layout/manga_info_header.xml
#	app/src/main/res/values/styles.xml
2021-05-22 17:40:53 -04:00
arkon eaa2bb22ed Remove 1 or 2 hour library updates to avoid DDoSing sources
(cherry picked from commit 569df39fb8)

# Conflicts:
#	app/build.gradle.kts
2021-05-22 17:33:29 -04:00
arkon 1389649553 Try to avoid crashing when source fails to return pages
(cherry picked from commit 2f7f00c7a2)
2021-05-22 17:29:26 -04:00
Ivan Iskandar b3ddc1dfa3 Remove material-design-dimens (#5133)
(cherry picked from commit afd59eabbb)
2021-05-22 17:29:16 -04:00
arkon d55802a2f2 Adjust tablet layout (closes #5113)
(cherry picked from commit cf99446a12)
2021-05-22 17:17:54 -04:00
Jobobby04 08cb46f6cd Make github link point to the SY github 2021-05-22 17:17:41 -04:00
arkon 15482914ef Tweak About view, make links actually clickable
(cherry picked from commit 68286b2acc)
2021-05-22 17:13:58 -04:00
arkon 3e5e983b9e Collapse about links into a single row
(cherry picked from commit a410184e0a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
2021-05-22 17:13:53 -04:00
Soitora 36484bc349 Add icons for links in the About section (#5117)
* Add an icon for each link in About

* Add icon for Open source licenses

* Reorder Facebook and Discord

* Revert "Add icon for Open source licenses"

This reverts commit 9b73f8443d1afea60f8bc4165663c8bef0ebf108.

(cherry picked from commit d3ceecf620)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt
2021-05-22 17:09:54 -04:00
arkon 85362e2030 Refactor BaseBottomSheetDialog to set maxWidth with newer API
(cherry picked from commit 940c5b3838)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt
2021-05-22 17:03:50 -04:00
arkon 3ebdd7b351 [SKIP CI] Update issue-closer-action
(cherry picked from commit 17c321286d)
2021-05-22 16:53:53 -04:00
Soitora 2a32c9cb5d Increase padding to progress bar (#5110)
(cherry picked from commit 0dbb79359b)
2021-05-22 16:53:42 -04:00
Hunter Nickel 677f93a6ec Change ordering of labels in "when" clause (#5103)
(cherry picked from commit 19f39fcdb0)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt
2021-05-22 16:53:35 -04:00
arkon 584f7b516f Restore original preference background after highlight animation (fixes #5094)
(cherry picked from commit ab021c1302)
2021-05-22 16:52:20 -04:00
Riztard Lanthorn 7d49479783 library update notif text progress (#5098)
(cherry picked from commit cf4b870846)
2021-05-22 16:52:09 -04:00
arkon 970e6e2560 Remove legacy backup creation
(cherry picked from commit 5e37f72d74)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/LegacyBackupManager.kt
2021-05-22 16:51:59 -04:00
inorichi 19d539b197 Fix decoder crash with 1px images and crop borders
(cherry picked from commit 6843dbf7e1)
2021-05-22 16:48:54 -04:00
arkon 97e47b98b1 Avoid invalid reading mode/orientation selections
Related to #5068

(cherry picked from commit 09c07faafd)
2021-05-22 16:48:36 -04:00
arkon 844fb8129c Don't hide side nav when opening ActionToolbar on tablets
(cherry picked from commit fa872f6cf7)
2021-05-22 16:47:48 -04:00
arkon 7a9c2afe87 Fix grid columns on tablets, split out common main_activity layout sections
(cherry picked from commit ef53d4ec07)
2021-05-22 16:47:40 -04:00
arkon 35879921dd Initial tablet NavigationRailView implementation
TODO:
- Make the side nav go beside the toolbar too
- Extract out common main_activity stuff to remove duplicated code

(cherry picked from commit de35a4c62a)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
#	app/src/main/res/layout/main_activity.xml
2021-05-22 16:47:17 -04:00
arkon f5a90d46ec Minor cleanup
(cherry picked from commit fcde6c2b84)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt
2021-05-22 16:34:09 -04:00
arkon d332f98b34 Update dependencies
(cherry picked from commit 9cbe053e79)
2021-05-22 16:30:08 -04:00
Riztard Lanthorn b339bd4f3f Add cancel all for series in download queue (#5062)
Co-authored-by: Jays2Kings <jays@outlook.com>
(cherry picked from commit 7ba43ae5c2)
2021-05-22 16:29:46 -04:00
arkon de414fb49b Instantiate extension preferences with proper datastore earlier
(cherry picked from commit 5700c7a0c7)
2021-05-22 16:29:37 -04:00
arkon 185b7fe70e Fix MultiSelectListPreference crash in extensions
(cherry picked from commit 5069d8dee6)
2021-05-22 16:29:25 -04:00
arkon a947fcd9b9 Fix rotation shortcut tooltip
(cherry picked from commit 47c120e58c)
2021-05-22 16:29:13 -04:00
arkon 7b9f5474bb Don't toggle favorite status from snackbar if already added (fixes #5038)
(cherry picked from commit 8d7ab13f5c)
2021-05-22 16:28:55 -04:00
Andreas 6238f06d39 Automatic background color for PagerViewer (#4996)
* Add J2K implementation of automatic background

Co-authored-by: Jays2Kings <8617760+Jays2Kings@users.noreply.github.com>

* Tweak the monstrosity called automatic background

* Add ability to choose Automatic as a background

* More tweaks

Co-authored-by: Jays2Kings <8617760+Jays2Kings@users.noreply.github.com>
(cherry picked from commit 122cdae5bc)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
#	app/src/main/res/values/arrays.xml
2021-05-22 16:28:40 -04:00
Jobobby04 7e612e63b4 Proguard fixes 2021-05-22 16:07:11 -04:00
simakover 63139a5c08 hide update and history buttons option (#324)
* hide update and history buttons option

* Move updates and history button in More then hiding

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2021-05-22 15:17:35 -04:00
Jobobby04 b63df25f7b Cleanup 2021-05-22 14:40:44 -04:00
Jobobby04 44385ed9cc Update some dependencies 2021-05-22 14:40:11 -04:00
Jobobby04 9eb1927d2e Better handling of Okhttp in awaitResponse 2021-05-22 14:39:22 -04:00
Jobobby04 20cbadb23d Update mangadex to api 5.0.10 2021-05-22 14:38:47 -04:00
Jobobby04 e37d4afce6 Revert "Fix duplicates in E-Hentai search 99% of the time, keep that 1% so it doesnt error"
This reverts commit fbc98ddb0a.
2021-05-21 21:06:45 -04:00
Jobobby04 fbc98ddb0a Fix duplicates in E-Hentai search 99% of the time, keep that 1% so it doesnt error 2021-05-21 19:15:17 -04:00
Jobobby04 91ca176c28 When throttling E-Hentai, suspend the thread instead of sleeping it 2021-05-21 17:06:40 -04:00
Jobobby04 fd65db51c1 Fix some text colors 2021-05-18 13:05:40 -04:00
Jobobby04 33a590d895 Mangadex fixes 2021-05-17 12:45:33 -04:00
Jobobby04 bc871cd2ee Dismiss the filter sheet when a button is pressed 2021-05-14 13:54:46 -04:00
Jobobby04 65f66630cf Make backing up read manga optional 2021-05-14 13:47:36 -04:00
Jobobby04 ab0f5d107f Use a unofficial cover api for mangadex browse
Co-authored-by: Henrik <henrik9999@users.noreply.github.com>
2021-05-13 14:07:22 -04:00
Jobobby04 4a2d9dbdf8 Fix Publication Complete status showing as unknown 2021-05-12 23:32:26 -04:00
Jobobby04 08f1eff450 Update Mangadex Similar to GoldBattles latest version 2021-05-12 23:29:14 -04:00
Jobobby04 e500d0bebf Exh login menu supports custom igneous cookies. Supports translation now as well 2021-05-12 18:19:43 -04:00
Jobobby04 2629b3420b Update for Mangadex api 5.0.5 2021-05-12 16:17:39 -04:00
Jobobby04 81e7d674a2 Update the Android Gradle plugin 2021-05-12 16:16:59 -04:00
Jobobby04 0c33b7915b Remove unused sheet extensions 2021-05-11 13:07:10 -04:00
Jobobby04 29e4392490 Cleanup the mangadex list calls 2021-05-11 13:06:55 -04:00
Jobobby04 ba9db7ceb9 Fix EHentai Syncing a bit 2021-05-11 12:40:11 -04:00
Jobobby04 32197b1491 Add erotica and pornographic to lewd 2021-05-11 12:39:29 -04:00
Jobobby04 3f56c81c03 Cleanup some tag stuff 2021-05-11 12:38:50 -04:00
Jobobby04 b657af8d1c Fix a Guya crashlytics crash 2021-05-10 22:03:17 -04:00
Jobobby04 273d61e69b Chunk the statuses endpoint 2021-05-10 22:03:17 -04:00
Jobobby04 2ec5581e8c Likely fix background crashes 2021-05-10 22:03:16 -04:00
Jobobby04 be6637c7fd Fix mangadex manga not adding all chapters 2021-05-10 22:03:16 -04:00
Jobobby04 93d317629f Disable the mangadex cover setting for now 2021-05-10 22:03:16 -04:00
Jobobby04 df188b7b90 Add copy function to MangasPage 2021-05-10 22:03:15 -04:00
OncePunchedMan 27f2e8ecbc [IDEA] Changing to labels not always visible (#296)
* disable always visible labels

* make it a setting

* remove redundant line

* Fix preference keys

* Update keys again

* Fix import

* Remove extra line

Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2021-05-10 22:02:55 -04:00
Henrik 44ba757ad8 add goldbattle mdaltimage data api (#312) 2021-05-10 22:02:31 -04:00
Jobobby04 d04cdd9b34 Half fix legacy backup with dex manga 2021-05-09 23:27:18 -04:00
Jobobby04 333f55a44b Enable MangaPlus chapters in mangadex delegation 2021-05-09 22:20:39 -04:00
Jobobby04 1f9b69fc07 Multiple bugfixes for mangadex delegation
Chapters should be sorted
Multiple language fixes
Multiple empty result fixes
2021-05-09 22:10:22 -04:00
Jobobby04 09802c3609 Fix chapter names to match the extension 2021-05-09 19:19:20 -04:00
Jobobby04 93fe927de2 Cleanup some delegation 2021-05-09 18:56:26 -04:00
Jobobby04 428a9e82f3 Use a string builder instead of a list to make chapter names 2021-05-09 18:56:25 -04:00
Jobobby04 7e1389ef05 Update merge chapters in parallel, protect against one failing and stopping the whole thing 2021-05-09 18:56:25 -04:00
Jobobby04 fc354f5792 Mangadex Api 5.0.4 2021-05-09 18:56:25 -04:00
Jobobby04 c26adbb81d Remove unused glide proguard rules 2021-05-09 18:56:24 -04:00
Jobobby04 31473351af Mangadex cleanup 2021-05-09 18:56:24 -04:00
Jobobby04 5c2d26aa7c Genkan.io crash fix 2021-05-09 18:56:23 -04:00
Jobobby04 f0a2b85dd5 Fix typo 2021-05-09 18:56:23 -04:00
Henrik 85398f7c30 add mal cover for mangadex and fix kitsu covers in some cases (#311) 2021-05-09 18:56:01 -04:00
Jobobby04 c787498b85 Use kitsu cover for mangadex manga if avalible 2021-05-07 20:53:59 -04:00
Jobobby04 8532a9e2c5 Backup filtered scanlators using the Neko key 2021-05-07 14:42:57 -04:00
Jobobby04 aa6013b7ca Convert the Chapter sheet into a dialog 2021-05-07 14:27:07 -04:00
Jobobby04 cb2432cce9 Fix a few bugs that were found in the extension 2021-05-07 14:14:05 -04:00
Jobobby04 dd81e5f2b9 Add artist parsing, fix possibly broken author parsing 2021-05-07 14:13:45 -04:00
Jobobby04 c3be087472 Remove unneeded metadata source functions 2021-05-07 14:02:08 -04:00
Jobobby04 84a1da2952 Fix updating remote status, allow editing MdList tracking 2021-05-07 13:43:34 -04:00
Jobobby04 e62de734aa Remove Uneeded Injekt.get calls 2021-05-07 13:42:20 -04:00
Jobobby04 866a92474f Disable requesting metadata for now 2021-05-07 13:41:33 -04:00
Jobobby04 b297d580b0 Get statuslist first when parsing tracking 2021-05-07 13:39:50 -04:00
Jobobby04 858a5e6eee Cleanup token authenticator, fixes ctd when opening tracking 2021-05-07 13:39:15 -04:00
Jobobby04 7c3c452ac2 Dont require logging into mangadex 2021-05-06 22:48:21 -04:00
Jobobby04 b9b5ef55ab Rewrite and enable Mangadex delegation for V5 of Mangadex (Thanks Cesco)
Co-authored-by: CarlosEsco <CarlosEsco@users.noreply.github.com>
2021-05-06 21:19:30 -04:00
Jobobby04 8686fecb1f Browse source from migration now properly adds it as a result 2021-05-06 16:57:27 -04:00
Jobobby04 c5148b4739 Cleanup E-Hentai code 2021-05-06 15:35:42 -04:00
Jobobby04 a4933388aa Add reader bottom button menu customization 2021-05-06 15:33:28 -04:00
Jobobby04 9095c98159 Organize reader settings a bit 2021-05-04 16:47:17 -04:00
arkon 8c2de86b16 Fix source filter FAB disappear on rotation (fixes #4994)
(cherry picked from commit 8d58a8d548)
2021-05-04 15:40:39 -04:00
arkon 635dd0cda5 Fix settings search crash (fixes #5002)
Can't lateinit since the controllers are instantiated via reflection.

(cherry picked from commit b453be081e)
2021-05-04 15:40:28 -04:00
arkon bde5d4da26 Fix navigation issue when activity is recreated
(cherry picked from commit 3c947f323f)
2021-05-04 15:40:18 -04:00
Ivan Iskandar 4f02f652d9 Show notification to disable Incognito Mode when it's enabled (#4976)
* Show notification to disable Incognito Mode when it's enabled

* Finish ReaderActivity and BrowseSourceController when incognito is disabled

* CLeanup strings

* Only register DisableIncognitoReceiver when needed

(cherry picked from commit cb203ef02c)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt
2021-05-04 15:40:06 -04:00
arkon 6dad90e19c Reader grayscale filter (closes #2822)
(cherry picked from commit 908c9bc624)
2021-05-04 14:49:43 -04:00
Andreas 8fdd6c3bf9 Fix bugs in dual-page split (#4983)
If more bugs appear probably better to go back to the main thread and process dual-pages every time a page is shown as it did before

(cherry picked from commit fe373a95a2)
2021-05-04 14:49:35 -04:00
arkon 4cbf647365 [SKIP CI] Update issue-closer-action
(cherry picked from commit 60f18f3b5a)
2021-05-04 14:49:12 -04:00
arkon 86d67b9bf7 Cancel scope in SettingsControllers properly
(cherry picked from commit 284c019b32)
2021-05-04 14:49:01 -04:00
arkon 69aebb5571 Update kotlinx.serialization
(cherry picked from commit 32434471e5)
2021-05-04 14:48:52 -04:00
Jays2Kings 00afc11d4f Change string chop method default to use smaller ... instead
(cherry picked from commit 43b42f8d54bf8872b4fd2467b33fa06a9a44dbb6)
(cherry picked from commit 6a4c280235)
2021-05-04 14:48:43 -04:00
arkon c518b593ce Better handling of coroutine cancellations for http calls
Based on https://github.com/tachiyomiorg/tachiyomi-1.x/commit/b94b7eeb6d28a4c3f39488388589fa913c43fe5e

(cherry picked from commit 0afe3011bc)
2021-05-04 14:48:28 -04:00
Andreas 5c352cb3c0 Add manga-wised rotation mode settings (#4841)
* Add manga-wised rotation mode settings

Based on #3522

Co-authored-by: bboyz269 <4453811+bboyz269@users.noreply.github.com>

* Fix small mistakes

* Complete TODOs

* Rename functions

rotation -> orientation

* Fix orientation icon not changing

Bug from video

* Fix bug with force portrait not being force if a default value

Bug from video

* Backup viewer_flag as a seperate field in so legacy/forks doesn't crash

* Make viewer_flags nullable so old backups viewer gets restored

* Add migration for old rotation and viewer to new defaults ones

* Rename variable in enums

* Fix migration after OrientationType was changed

* Remove untrue comment

Co-authored-by: bboyz269 <4453811+bboyz269@users.noreply.github.com>
(cherry picked from commit 0fef546a0d)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt
2021-05-04 14:48:06 -04:00
Ivan Iskandar 73c9df9c43 Use Coil (#4870)
* Use Coil

* Remove coil-transformations lib

* Add MangaCoverFetcher

* Remove Glide

* MangaCoverFetcher: Allow skipping custom cover usage

* Adjust coil caching policy for some non-library items

* Allow coil to use RGB565 only on low ram devices

* Fix image loading progress view not showing

a

* Increase coil crossfade duration

Same as default glide duration

* Add back request clearing

(cherry picked from commit 93e6136795)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
#	app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
2021-05-04 14:13:17 -04:00
arkon 8b2f24c86a Update sqlite-android
This version is on jitpack instead of jcenter

(cherry picked from commit 7d23fd8ef5)
2021-05-04 14:13:16 -04:00
arkon 39c61a77b1 Move save pages to manga title setting to Reader section
(cherry picked from commit 224fcada17)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
2021-05-04 14:13:16 -04:00
OncePunchedMan d09bcafe7d Setting: Creates folders according to manga title (#4861)
* cherry-picking my changes

* Update SettingsDownloadController.kt

* Update SettingsDownloadController.kt

* Update ReaderPresenter.kt

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

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsDownloadController.kt
2021-05-04 14:13:15 -04:00
Ivan Iskandar 1fdf2225d9 Use adaptive icon for app shortcuts (#4968)
Also swap the color

(cherry picked from commit dad3292bdd)
2021-05-04 14:13:15 -04:00
arkon b4a226157c Drop support for Android 5.x
(cherry picked from commit 89619b7836)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
2021-05-04 14:13:14 -04:00
Jobobby04 39d6319e8f Remove useless getResourceColor call 2021-05-04 14:13:14 -04:00
Jobobby04 765eac843d Round migration card corners 2021-05-04 14:13:13 -04:00
Riztard Lanthorn 9a6b8a3f41 fix invisible system nav bar (#297)
new nav bar scrim already make it semi transparent
2021-05-04 14:12:28 -04:00
curche 28fab7a918 fixes to Migration menu layouts (#295)
* fix Migration process item option button color

fixed by comparing with other layouts and adding app:tint to migration
process item layout

fixes https://github.com/jobobby04/TachiyomiSY/issues/287

* fix migration manga card layout for white theme

compared with other layouts like source compact card item and realised
that textColor was missing.

Also added font so that it looks cooler :d
2021-05-03 12:16:38 -04:00
curche 900aa155ca make favorites sync less ambiguous (#294)
favorites sync is used for Ex/E-Hentai. Some users didn't know what it
meant and assumed it was something else. This commit makes it explicit
2021-04-30 14:48:45 -04:00
Jobobby04 f9b4cb5bc9 Fix manga plus 2021-04-28 21:43:37 -04:00
Jobobby04 1147bab1ce Quick fix to update check 2021-04-28 15:26:34 -04:00
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
656 changed files with 20275 additions and 13598 deletions
-1
View File
@@ -1,2 +1 @@
github: inorichi
ko_fi: inorichi ko_fi: inorichi
+10 -2
View File
@@ -2,9 +2,15 @@
I acknowledge that: I acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0) - I have updated:
- I have updated all extensions - To the latest version of the app (stable is v1.7.0)
- 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 - 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** **DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
@@ -24,3 +30,5 @@ I acknowledge that:
## Other details ## Other details
Additional details and attachments. 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 acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0) - I have updated:
- I have updated all extensions - To the latest version of the app (stable is v1.7.0)
- 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 - 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** **DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
@@ -34,3 +40,5 @@ This happened instead.
## Other details ## Other details
Additional details and attachments. 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 acknowledge that:
- I have updated to the latest version of the app (stable is v1.5.0) - I have updated:
- I have updated all extensions - To the latest version of the app (stable is v1.7.0)
- All extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-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** **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 - name: Clone repo
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up JDK 11 - name: Set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 1.8
- name: Copy CI gradle.properties - name: Copy CI gradle.properties
run: | run: |
+27 -28
View File
@@ -7,31 +7,30 @@ jobs:
autoclose: autoclose:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Autoclose when created in wrong repo - name: Autoclose issues
uses: arkon/issue-closer-action@v1.1 uses: arkon/issue-closer-action@v3.4
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
type: title rules: |
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 "type": "title",
uses: arkon/issue-closer-action@v1.1 "regex": ".*THIS ISSUE IS IN THE WRONG REPO.*",
with: "message": "It was not opened in the correct repo, as the template mentioned."
repo-token: ${{ secrets.GITHUB_TOKEN }} },
type: title {
regex: ".*<Write short description here>*" "type": "title",
message: "@${issue.user.login} this issue was automatically closed because you did not fill out the description in the title." "regex": ".*<Write short description here>*",
- name: Autoclose when body acknowledgement section not removed "message": "The description in the title was not filled out."
uses: arkon/issue-closer-action@v1.1 },
with: {
repo-token: ${{ secrets.GITHUB_TOKEN }} "type": "body",
type: body "regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
regex: ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*" "message": "The acknowledgment section was not removed."
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 "type": "body",
with: "regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
repo-token: ${{ secrets.GITHUB_TOKEN }} "message": "Requested information in the template was not filled out."
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."
+14
View File
@@ -0,0 +1,14 @@
name: Issue moderator
on:
issue_comment:
types: [created]
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
+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
+9 -4
View File
@@ -1,17 +1,17 @@
| Preview Builds | Release Builds | Tachiyomi Support Server | | Preview Builds | Release Builds | Tachiyomi Support Server |
|-------|----------|----------| |-------|----------|----------|
| [![Preview](https://github.com/jobobby04/TachiyomiSYPreview/workflows/Remote%20Dispatch%20Build%20App/badge.svg)](https://github.com/jobobby04/TachiyomiSYPreview/releases) | [![stable release](https://img.shields.io/github/release/jobobby04/tachiyomisy.svg?maxAge=3600&label=download)](https://github.com/jobobby04/tachiyomisy/releases/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 # ![app icon](./.github/readme-images/app-icon.png)TachiyomiSY
Tachiyomi is a free and open source manga reader for Android 5.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko. Tachiyomi is a free and open source manga reader for Android 6.0 and above. This version of Tachiyomi, TachiyomiSY was based off TachiyomiAZ. This version is meant to push forward in the ways of usability and features. TachiyomiSY tries to push forward where it can, but staying in a place where it can easily grab updates and features from the main app, it tries to make new features, or take features from other forks like J2K and Neko.
![screenshots of app](./.github/readme-images/screens.png) ![screenshots of app](./.github/readme-images/screens.png)
## Features ## Features
Features of Tachiyomi(original) include: 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 * Local reading of downloaded manga
* A configurable reader with multiple viewers, reading directions and other settings. * 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 * [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> <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> </details>
## FAQ ## FAQ
+42 -50
View File
@@ -8,11 +8,11 @@ plugins {
id("com.android.application") id("com.android.application")
id("com.mikepenz.aboutlibraries.plugin") id("com.mikepenz.aboutlibraries.plugin")
kotlin("android") kotlin("android")
kotlin("kapt")
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
kotlin("plugin.serialization") kotlin("plugin.serialization")
id("com.github.zellius.shortcut-helper") id("com.github.zellius.shortcut-helper")
// Realm (EH) // Realm (EH)
kotlin("kapt")
id("realm-android") id("realm-android")
} }
@@ -34,8 +34,8 @@ android {
minSdkVersion(AndroidConfig.minSdk) minSdkVersion(AndroidConfig.minSdk)
targetSdkVersion(AndroidConfig.targetSdk) targetSdkVersion(AndroidConfig.targetSdk)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
versionCode = 13 versionCode = 19
versionName = "1.5.0" versionName = "1.7.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"") buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
@@ -62,14 +62,12 @@ android {
applicationIdSuffix = ".rt" applicationIdSuffix = ".rt"
//isMinifyEnabled = true //isMinifyEnabled = true
//isShrinkResources = true //isShrinkResources = true
isZipAlignEnabled = true setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
} }
named("release") { named("release") {
isMinifyEnabled = true isMinifyEnabled = true
isShrinkResources = true isShrinkResources = true
isZipAlignEnabled = true setProguardFiles(listOf(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"))
setProguardFiles(listOf(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"))
} }
} }
@@ -95,6 +93,7 @@ android {
exclude("META-INF/LICENSE") exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt") exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE") exclude("META-INF/NOTICE")
exclude("META-INF/*.kotlin_module")
// Compatibility for two RxJava versions (EXH) // Compatibility for two RxJava versions (EXH)
exclude("META-INF/rxjava.properties") exclude("META-INF/rxjava.properties")
@@ -123,34 +122,34 @@ android {
dependencies { dependencies {
// Source models and interfaces from Tachiyomi 1.x // Source models and interfaces from Tachiyomi 1.x
implementation("tachiyomi.sourceapi:source-api:1.1") implementation("org.tachiyomi:source-api:1.1")
// AndroidX libraries // AndroidX libraries
implementation("androidx.annotation:annotation:1.2.0-beta01") implementation("androidx.annotation:annotation:1.3.0-alpha01")
implementation("androidx.appcompat:appcompat:1.3.0-beta01") implementation("androidx.appcompat:appcompat:1.4.0-alpha01")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha02") implementation("androidx.biometric:biometric-ktx:1.2.0-alpha03")
implementation("androidx.browser:browser:1.3.0") implementation("androidx.browser:browser:1.3.0")
implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.0-alpha2") implementation("androidx.constraintlayout:constraintlayout:2.1.0-beta02")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0") implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
implementation("androidx.core:core-ktx:1.5.0-beta01") implementation("androidx.core:core-ktx:1.6.0-beta01")
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
implementation("androidx.preference:preference-ktx:1.1.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") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
val lifecycleVersion = "2.3.0-rc01" val lifecycleVersion = "2.4.0-alpha01"
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
// Job scheduling // Job scheduling
implementation("androidx.work:work-runtime-ktx:2.5.0") implementation("androidx.work:work-runtime-ktx:2.7.0-alpha03")
// UI library // UI library
implementation("com.google.android.material:material:1.3.0") implementation("com.google.android.material:material:1.4.0-beta01")
"standardImplementation"("com.google.firebase:firebase-core:18.0.2") "standardImplementation"("com.google.firebase:firebase-core:19.0.0")
// ReactiveX // ReactiveX
implementation("io.reactivex:rxandroid:1.2.1") implementation("io.reactivex:rxandroid:1.2.1")
@@ -159,7 +158,7 @@ dependencies {
implementation("com.github.pwittchen:reactivenetwork:0.13.0") implementation("com.github.pwittchen:reactivenetwork:0.13.0")
// Network client // Network client
val okhttpVersion = "4.10.0-RC1" val okhttpVersion = "4.9.1"
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion") implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion") implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion") implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
@@ -169,7 +168,7 @@ dependencies {
implementation("org.conscrypt:conscrypt-android:2.5.1") implementation("org.conscrypt:conscrypt-android:2.5.1")
// JSON // 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-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
implementation("com.google.code.gson:gson:2.8.6") implementation("com.google.code.gson:gson:2.8.6")
@@ -180,7 +179,7 @@ dependencies {
// Disk // Disk
implementation("com.jakewharton:disklrucache:2.0.2") 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") implementation("com.github.junrar:junrar:7.4.0")
// HTML parser // HTML parser
@@ -190,10 +189,10 @@ dependencies {
implementation("androidx.sqlite:sqlite-ktx:2.1.0") implementation("androidx.sqlite:sqlite-ktx:2.1.0")
implementation("com.github.inorichi.storio:storio-common:8be19de@aar") implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar") implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
implementation("io.requery:sqlite-android:3.33.0") implementation("com.github.requery:sqlite-android:3.35.5")
// Preferences // Preferences
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.3.3") implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.4.0")
// Model View Presenter // Model View Presenter
val nucleusVersion = "3.0.0" val nucleusVersion = "3.0.0"
@@ -204,14 +203,14 @@ dependencies {
implementation("com.github.inorichi.injekt:injekt-core:65b0440") implementation("com.github.inorichi.injekt:injekt-core:65b0440")
// Image library // Image library
val glideVersion = "4.11.0" val coilVersion = "1.2.0"
implementation("com.github.bumptech.glide:glide:$glideVersion") implementation("io.coil-kt:coil:$coilVersion")
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion") implementation("io.coil-kt:coil-gif:$coilVersion")
kapt("com.github.bumptech.glide:compiler:$glideVersion")
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:6caf219") implementation("com.github.tachiyomiorg:subsampling-scale-image-view:846abe0") {
// TODO: switch to new decoder for stable releases exclude(module = "image-decoder")
// implementation("com.github.tachiyomiorg:subsampling-scale-image-view:ca26317") }
implementation("com.github.tachiyomiorg:image-decoder:7a44c9b")
// Logging // Logging
implementation("com.jakewharton.timber:timber:4.7.1") implementation("com.jakewharton.timber:timber:4.7.1")
@@ -223,13 +222,13 @@ dependencies {
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1") implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
// UI // UI
implementation("com.dmitrymalkovich.android:material-design-dimens:1.4")
implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4") implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4")
implementation("eu.davidea:flexible-adapter:5.1.0") implementation("eu.davidea:flexible-adapter:5.1.0")
implementation("eu.davidea:flexible-adapter-ui:1.0.0") implementation("eu.davidea:flexible-adapter-ui:1.0.0")
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0") implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
implementation("com.github.chrisbanes:PhotoView:2.3.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.6.0")
// 3.2.0+ introduces weird UI blinking or cut off issues on some devices // 3.2.0+ introduces weird UI blinking or cut off issues on some devices
val materialDialogsVersion = "3.1.1" val materialDialogsVersion = "3.1.1"
@@ -242,10 +241,10 @@ dependencies {
implementation("com.bluelinelabs:conductor-support:2.1.5") { implementation("com.bluelinelabs:conductor-support:2.1.5") {
exclude(group = "com.android.support") 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 // FlowBinding
val flowbindingVersion = "0.12.0" val flowbindingVersion = "1.0.0"
implementation("io.github.reactivecircus.flowbinding:flowbinding-android:$flowbindingVersion") implementation("io.github.reactivecircus.flowbinding:flowbinding-android:$flowbindingVersion")
implementation("io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbindingVersion") implementation("io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowbindingVersion")
implementation("io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbindingVersion") implementation("io.github.reactivecircus.flowbinding:flowbinding-recyclerview:$flowbindingVersion")
@@ -256,7 +255,7 @@ dependencies {
implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}") implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
// Tests // Tests
testImplementation("junit:junit:4.13.1") testImplementation("junit:junit:4.13.2")
testImplementation("org.assertj:assertj-core:3.16.1") testImplementation("org.assertj:assertj-core:3.16.1")
testImplementation("org.mockito:mockito-core:1.10.19") testImplementation("org.mockito:mockito-core:1.10.19")
@@ -267,12 +266,12 @@ dependencies {
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN)) implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
val coroutinesVersion = "1.4.2" val coroutinesVersion = "1.5.0"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
// For detecting memory leaks; see https://square.github.io/leakcanary/ // For detecting memory leaks; see https://square.github.io/leakcanary/
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.6") // debugImplementation("com.squareup.leakcanary:leakcanary-android:2.7")
// SY --> // SY -->
// [EXH] Android 7 SSL Workaround // [EXH] Android 7 SSL Workaround
@@ -285,11 +284,11 @@ dependencies {
implementation ("info.debatty:java-string-similarity:2.0.0") implementation ("info.debatty:java-string-similarity:2.0.0")
// Firebase (EH) // Firebase (EH)
implementation("com.google.firebase:firebase-analytics-ktx:18.0.0") implementation("com.google.firebase:firebase-analytics-ktx:19.0.0")
implementation("com.google.firebase:firebase-crashlytics-ktx:17.3.0") implementation("com.google.firebase:firebase-crashlytics-ktx:18.0.0")
// Better logging (EH) // Better logging (EH)
implementation("com.elvishew:xlog:1.7.1") implementation("com.elvishew:xlog:1.9.0")
// Debug utils (EH) // Debug utils (EH)
val debugOverlayVersion = "1.1.3" val debugOverlayVersion = "1.1.3"
@@ -299,15 +298,7 @@ dependencies {
testImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion") testImplementation("com.ms-square:debugoverlay-no-op:$debugOverlayVersion")
// RatingBar (SY) // RatingBar (SY)
implementation ("me.zhanghai.android.materialratingbar:library:1.4.0") implementation("me.zhanghai.android.materialratingbar:library:1.4.0")
// JsonReader for similar manga
implementation("com.squareup.moshi:moshi:1.11.0")
implementation("androidx.gridlayout:gridlayout:1.0.0")
implementation("com.mikepenz:fastadapter:5.3.4")
// SY -->
} }
tasks { tasks {
@@ -320,7 +311,8 @@ tasks {
"-Xuse-experimental=kotlinx.coroutines.FlowPreview", "-Xuse-experimental=kotlinx.coroutines.FlowPreview",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi", "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi" "-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi",
"-Xuse-experimental=coil.annotation.ExperimentalCoilApi",
) )
} }
+34
View File
@@ -0,0 +1,34 @@
-allowaccessmodification
-dontusemixedcaseclassnames
-verbose
-keepattributes *Annotation*
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final ** CREATOR;
}
-keep class androidx.annotation.Keep
-keep @androidx.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@androidx.annotation.Keep <init>(...);
}
+15 -42
View File
@@ -58,6 +58,7 @@
kotlinx.serialization.KSerializer serializer(...); kotlinx.serialization.KSerializer serializer(...);
} }
# Filter serializer
-keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; } -keep,includedescriptorclasses class xyz.nulldev.ts.api.http.serializer.**$$serializer { *; }
-keepclassmembers class xyz.nulldev.ts.api.http.serializer.** { -keepclassmembers class xyz.nulldev.ts.api.http.serializer.** {
*** Companion; *** Companion;
@@ -66,37 +67,22 @@
kotlinx.serialization.KSerializer serializer(...); kotlinx.serialization.KSerializer serializer(...);
} }
# Madokami extension username and password crash fix # Keep extension's common dependencies
-keepclassmembers class androidx.preference.EditTextPreference { -keep,allowoptimization class eu.kanade.tachiyomi.** { public protected *; }
*** mOnBindEditTextListener; -keep,allowoptimization class kotlin.** { public protected *; }
*** mText; -keep,allowoptimization class okhttp3.** { public protected *; }
public *; -keep,allowoptimization class rx.** { public protected *; }
} -keep,allowoptimization class org.jsoup.** { public protected *; }
-keep,allowoptimization class com.google.gson.** { public protected *; }
# Hitomi extension crash fix -keep,allowoptimization class com.github.salomonbrys.kotson.** { public protected *; }
-keepclassmembers class rx.Single { -keep,allowoptimization class com.squareup.duktape.** { public protected *; }
*** onSubscribe; -keep,allowoptimization class androidx.preference.** { *; }
final *; -keep,allowoptimization class okio.** { *; }
protected *; -keep,allowoptimization class kotlinx.serialization.** { *; }
public *;
}
# RxJava 1.1.0 # RxJava 1.1.0
-dontwarn sun.misc.** -dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
-dontnote rx.internal.util.PlatformDependent -dontnote rx.internal.util.PlatformDependent
# === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration # === Reactive network: https://github.com/pwittchen/ReactiveNetwork/tree/v0.12.4#proguard-configuration
@@ -133,8 +119,9 @@
# Application classes that will be serialized/deserialized over Gson # Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; } -keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapterFactory, # Prevent proguard from stripping interface information from TypeAdapterFactory, TypeAdapter,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory -keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer -keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer -keep class * implements com.google.gson.JsonDeserializer
@@ -155,20 +142,6 @@
## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1" ## From original config: "Attempt to fix: java.lang.NoClassDefFoundError: uy.kohesive.injekt.registry.default.DefaultRegistrar$NOKEY$1"
-keep class uy.kohesive.injekt.** { *; } -keep class uy.kohesive.injekt.** { *; }
# === Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
# === Glide-transformations: https://github.com/wasabeef/glide-transformations/blob/3aa8e53c6a51b8351d312f802ba1354c5b115168/transformations/proguard-rules.txt
-dontwarn jp.co.cyberagent.android.gpuimage.**
# === Conductor # === Conductor
# This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files # This isn't in the consumer proguard rules yet: https://github.com/bluelinelabs/Conductor/pull/550/files
-keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler { -keepclassmembers public class * extends com.bluelinelabs.conductor.ControllerChangeHandler {
+1 -1
View File
@@ -17,7 +17,7 @@
android:shortcutDisabledMessage="@string/app_not_available" android:shortcutDisabledMessage="@string/app_not_available"
android:shortcutId="show_recently_updated" android:shortcutId="show_recently_updated"
android:shortcutLongLabel="@string/label_recent_updates" android:shortcutLongLabel="@string/label_recent_updates"
android:shortcutShortLabel="@string/short_recent_updates"> android:shortcutShortLabel="@string/label_recent_updates">
<intent <intent
android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED" android:action="eu.kanade.tachiyomi.SHOW_RECENTLY_UPDATED"
android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" /> android:targetClass="eu.kanade.tachiyomi.ui.main.MainActivity" />
+12 -11
View File
@@ -14,6 +14,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- For managing extensions --> <!-- For managing extensions -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@@ -32,7 +33,7 @@
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Tachiyomi.Light" android:theme="@style/Theme.Base"
android:networkSecurityConfig="@xml/network_security_config"> android:networkSecurityConfig="@xml/network_security_config">
<activity <activity
android:name=".ui.main.MainActivity" android:name=".ui.main.MainActivity"
@@ -83,8 +84,8 @@
android:resource="@xml/s_pen_actions"/> android:resource="@xml/s_pen_actions"/>
</activity> </activity>
<activity <activity
android:name=".ui.security.BiometricUnlockActivity" android:name=".ui.security.UnlockActivity"
android:theme="@style/Theme.Splash" /> android:theme="@style/Theme.Base" />
<activity <activity
android:name=".ui.webview.WebViewActivity" android:name=".ui.webview.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize" /> android:configChanges="uiMode|orientation|screenSize" />
@@ -149,6 +150,10 @@
android:name=".extension.util.ExtensionInstallActivity" android:name=".extension.util.ExtensionInstallActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:theme="@android:style/Theme.Translucent.NoTitleBar" />
<activity
android:name="exh.ui.login.EhLoginActivity"
android:label="EHentaiLogin" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
@@ -184,10 +189,6 @@
android:exported="false" /> android:exported="false" />
<!-- EH --> <!-- EH -->
<service
android:name="exh.md.similar.SimilarUpdateService"
android:exported="false" />
<service <service
android:name="exh.eh.EHentaiUpdateWorker" android:name="exh.eh.EHentaiUpdateWorker"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
@@ -195,7 +196,7 @@
<activity <activity
android:name="exh.ui.intercept.InterceptActivity" android:name="exh.ui.intercept.InterceptActivity"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.EHActivity"> android:theme="@style/Theme.Base">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -315,7 +316,7 @@
android:scheme="https" /> android:scheme="https" />
<!-- MangaDex --> <!-- MangaDex -->
<data <!--<data
android:scheme="https" android:scheme="https"
android:host="www.mangadex.org" android:host="www.mangadex.org"
android:pathPrefix="/manga/" /> android:pathPrefix="/manga/" />
@@ -364,12 +365,12 @@
<data <data
android:scheme="https" android:scheme="https"
android:host="www.mangadex.cc" android:host="www.mangadex.cc"
android:pathPrefix="/chapter/" /> android:pathPrefix="/chapter/" />-->
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="exh.ui.captcha.BrowserActionActivity" android:name="exh.ui.captcha.BrowserActionActivity"
android:theme="@style/Theme.EHActivity" /> android:theme="@style/Theme.Base" />
</application> </application>
</manifest> </manifest>
+120 -51
View File
@@ -1,16 +1,29 @@
package eu.kanade.tachiyomi package eu.kanade.tachiyomi
import android.app.ActivityManager
import android.app.Application import android.app.Application
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.webkit.WebView
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.multidex.MultiDex import androidx.multidex.MultiDex
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import com.elvishew.xlog.LogConfiguration import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel import com.elvishew.xlog.LogLevel
import com.elvishew.xlog.XLog import com.elvishew.xlog.XLog
@@ -22,28 +35,34 @@ import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator
import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import com.ms_square.debugoverlay.DebugOverlay import com.ms_square.debugoverlay.DebugOverlay
import com.ms_square.debugoverlay.modules.FpsModule import com.ms_square.debugoverlay.modules.FpsModule
import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.notification
import exh.debug.DebugToggles import exh.debug.DebugToggles
import exh.log.CrashlyticsPrinter import exh.log.CrashlyticsPrinter
import exh.log.EHDebugModeOverlay import exh.log.EHDebugModeOverlay
import exh.log.EHLogLevel import exh.log.EHLogLevel
import exh.log.EnhancedFilePrinter import exh.log.EnhancedFilePrinter
import exh.log.XLogTree
import exh.log.xLogD
import exh.log.xLogE
import exh.syDebugVersion import exh.syDebugVersion
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
@@ -51,20 +70,20 @@ import java.security.Security
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
import kotlin.concurrent.thread
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import kotlin.time.days import kotlin.time.days
open class App : Application(), LifecycleObserver { open class App : Application(), LifecycleObserver, ImageLoaderFactory {
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
private lateinit var firebaseAnalytics: FirebaseAnalytics private val disableIncognitoReceiver = DisableIncognitoReceiver()
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree()) // if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
setupExhLogging() // EXH logging setupExhLogging() // EXH logging
Timber.plant(XLogTree()) // SY Redirect Timber to XLog
if (!BuildConfig.DEBUG) addAnalytics() if (!BuildConfig.DEBUG) addAnalytics()
workaroundAndroid7BrokenSSL() workaroundAndroid7BrokenSSL()
@@ -74,11 +93,16 @@ open class App : Application(), LifecycleObserver {
Security.insertProviderAt(Conscrypt.newProvider(), 1) Security.insertProviderAt(Conscrypt.newProvider(), 1)
} }
// Avoid potential crashes
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val process = getProcessName()
if (packageName != process) WebView.setDataDirectorySuffix(process)
}
Injekt.importModule(AppModule(this)) Injekt.importModule(AppModule(this))
setupNotificationChannels() setupNotificationChannels()
Realm.init(this) Realm.init(this)
GlobalScope.launch { deleteOldMetadataRealm() } // Delete old metadata DB (EH)
if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) { if ((BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "releaseTest") && DebugToggles.ENABLE_DEBUG_OVERLAY.enabled) {
setupDebugOverlay() setupDebugOverlay()
} }
@@ -86,6 +110,34 @@ open class App : Application(), LifecycleObserver {
LocaleHelper.updateConfiguration(this, resources.configuration) LocaleHelper.updateConfiguration(this, resources.configuration)
ProcessLifecycleOwner.get().lifecycle.addObserver(this) ProcessLifecycleOwner.get().lifecycle.addObserver(this)
// Show notification to disable Incognito Mode when it's enabled
preferences.incognitoMode().asFlow()
.onEach { enabled ->
val notificationManager = NotificationManagerCompat.from(this)
if (enabled) {
disableIncognitoReceiver.register()
val notification = notification(Notifications.CHANNEL_INCOGNITO_MODE) {
setContentTitle(getString(R.string.pref_incognito_mode))
setContentText(getString(R.string.notification_incognito_text))
setSmallIcon(R.drawable.ic_glasses_black_24dp)
setOngoing(true)
val pendingIntent = PendingIntent.getBroadcast(
this@App,
0,
Intent(ACTION_DISABLE_INCOGNITO_MODE),
PendingIntent.FLAG_ONE_SHOT
)
setContentIntent(pendingIntent)
}
notificationManager.notify(Notifications.ID_INCOGNITO_MODE, notification)
} else {
disableIncognitoReceiver.unregister()
notificationManager.cancel(Notifications.ID_INCOGNITO_MODE)
}
}
.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {
@@ -98,6 +150,23 @@ open class App : Application(), LifecycleObserver {
LocaleHelper.updateConfiguration(this, newConfig, true) LocaleHelper.updateConfiguration(this, newConfig, true)
} }
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
componentRegistry {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder(this@App))
} else {
add(GifDecoder())
}
add(ByteBufferFetcher())
add(MangaCoverFetcher())
}
okHttpClient(Injekt.get<NetworkHelper>().coilClient)
crossfade(300)
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
}.build()
}
private fun workaroundAndroid7BrokenSSL() { private fun workaroundAndroid7BrokenSSL() {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N || if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N ||
Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1 Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
@@ -105,23 +174,22 @@ open class App : Application(), LifecycleObserver {
try { try {
SSLContext.getInstance("TLSv1.2") SSLContext.getInstance("TLSv1.2")
} catch (e: NoSuchAlgorithmException) { } 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 { try {
ProviderInstaller.installIfNeeded(applicationContext) ProviderInstaller.installIfNeeded(applicationContext)
} catch (e: GooglePlayServicesRepairableException) { } 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) { } 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() { private fun addAnalytics() {
firebaseAnalytics = Firebase.analytics
if (syDebugVersion != "0") { if (syDebugVersion != "0") {
firebaseAnalytics.setUserProperty("preview_version", syDebugVersion) Firebase.analytics.setUserProperty("preview_version", syDebugVersion)
} }
} }
@@ -137,36 +205,13 @@ open class App : Application(), LifecycleObserver {
Notifications.createChannels(this) 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 // EXH
private fun setupExhLogging() { private fun setupExhLogging() {
EHLogLevel.init(this) EHLogLevel.init(this)
val logLevel = when { val logLevel = when {
EHLogLevel.shouldLog(EHLogLevel.EXTRA) -> LogLevel.ALL EHLogLevel.shouldLog(EHLogLevel.EXTREME) -> LogLevel.ALL
BuildConfig.DEBUG -> LogLevel.DEBUG EHLogLevel.shouldLog(EHLogLevel.EXTRA) || BuildConfig.DEBUG -> LogLevel.DEBUG
else -> LogLevel.WARN else -> LogLevel.WARN
} }
@@ -188,9 +233,8 @@ open class App : Application(), LifecycleObserver {
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
printers += EnhancedFilePrinter printers += EnhancedFilePrinter
.Builder(logFolder.absolutePath) .Builder(logFolder.absolutePath) {
.fileNameGenerator( fileNameGenerator = object : DateFileNameGenerator() {
object : DateFileNameGenerator() {
override fun generateFileName(logLevel: Int, timestamp: Long): String { override fun generateFileName(logLevel: Int, timestamp: Long): String {
return super.generateFileName( return super.generateFileName(
logLevel, logLevel,
@@ -198,13 +242,12 @@ open class App : Application(), LifecycleObserver {
) + "-${BuildConfig.BUILD_TYPE}.log" ) + "-${BuildConfig.BUILD_TYPE}.log"
} }
} }
) flattener { timeMillis, level, tag, message ->
.flattener { timeMillis, level, tag, message -> "${dateFormat.format(timeMillis)} ${LogLevel.getShortLevelName(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 // Install Crashlytics in prod
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
@@ -216,8 +259,8 @@ open class App : Application(), LifecycleObserver {
*printers.toTypedArray() *printers.toTypedArray()
) )
XLog.tag("Init").d("Application booting...") xLogD("Application booting...")
XLog.tag("Init").disableStackTrace().d( xLogD(
"App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})\n" + "App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}, ${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE})\n" +
"Preview build: $syDebugVersion\n" + "Preview build: $syDebugVersion\n" +
"Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) \n" + "Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}) \n" +
@@ -242,7 +285,33 @@ open class App : Application(), LifecycleObserver {
.install() .install()
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
// Crashes if app is in background // 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)
} }
} }
private inner class DisableIncognitoReceiver : BroadcastReceiver() {
private var registered = false
override fun onReceive(context: Context, intent: Intent) {
preferences.incognitoMode().set(false)
}
fun register() {
if (!registered) {
registerReceiver(this, IntentFilter(ACTION_DISABLE_INCOGNITO_MODE))
registered = true
}
}
fun unregister() {
if (registered) {
unregisterReceiver(this)
registered = false
}
}
}
companion object {
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
}
} }
@@ -2,7 +2,6 @@ package eu.kanade.tachiyomi
import android.app.Application import android.app.Application
import android.os.Handler import android.os.Handler
import com.google.gson.Gson
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@@ -44,8 +43,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { TrackManager(app) } addSingletonFactory { TrackManager(app) }
addSingletonFactory { Gson() }
addSingletonFactory { Json { ignoreUnknownKeys = true } } addSingletonFactory { Json { ignoreUnknownKeys = true } }
// SY --> // SY -->
@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi package eu.kanade.tachiyomi
import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
@@ -9,7 +10,9 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.updater.UpdaterJob import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob 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.ui.library.LibrarySort
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@@ -129,6 +132,61 @@ object Migrations {
context.toast(R.string.myanimelist_relogin) 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
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.contains("pref_rotation_type_key")) {
prefs.edit {
putInt("pref_rotation_type_key", 1)
}
}
// Disable update check for Android 5.x users
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
UpdaterJob.cancelTask(context)
}
}
if (oldVersion < 60) {
// Migrate Rotation and Viewer values to default values for viewer_flags
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
1 -> OrientationType.FREE.flagValue
2 -> OrientationType.PORTRAIT.flagValue
3 -> OrientationType.LANDSCAPE.flagValue
4 -> OrientationType.LOCKED_PORTRAIT.flagValue
5 -> OrientationType.LOCKED_LANDSCAPE.flagValue
else -> OrientationType.FREE.flagValue
}
// Reading mode flag and prefValue is the same value
val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
prefs.edit {
putInt("pref_default_orientation_type_key", newOrientation)
remove("pref_rotation_type_key")
putInt("pref_default_reading_mode_key", newReadingMode)
remove("pref_default_viewer_key")
}
}
if (oldVersion < 61) {
// Handle removed every 1 or 2 hour library updates
val updateInterval = preferences.libraryUpdateInterval().get()
if (updateInterval == 1 || updateInterval == 2) {
preferences.libraryUpdateInterval().set(3)
LibraryUpdateJob.setupTask(context, 3)
}
}
return true 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo 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.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
@@ -23,6 +24,10 @@ abstract class AbstractBackupManager(protected val context: Context) {
internal val trackManager: TrackManager by injectLazy() internal val trackManager: TrackManager by injectLazy()
protected val preferences: PreferencesHelper 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? 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track 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.data.track.TrackManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.chapter.NoChaptersException import eu.kanade.tachiyomi.util.chapter.NoChaptersException
@@ -24,6 +25,10 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
protected val db: DatabaseHelper by injectLazy() protected val db: DatabaseHelper by injectLazy()
protected val trackManager: TrackManager by injectLazy() protected val trackManager: TrackManager by injectLazy()
// SY -->
protected val customMangaManager: CustomMangaManager by injectLazy()
// SY <--
var job: Job? = null var job: Job? = null
protected lateinit var backupManager: T protected lateinit var backupManager: T
@@ -8,7 +8,6 @@ object BackupConst {
const val EXTRA_URI = "$ID.$NAME.EXTRA_URI" const val EXTRA_URI = "$ID.$NAME.EXTRA_URI"
const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS" const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS"
const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE" const val EXTRA_MODE = "$ID.$NAME.EXTRA_MODE"
const val EXTRA_TYPE = "$ID.$NAME.EXTRA_TYPE"
const val BACKUP_TYPE_LEGACY = 0 const val BACKUP_TYPE_LEGACY = 0
const val BACKUP_TYPE_FULL = 1 const val BACKUP_TYPE_FULL = 1
@@ -10,7 +10,6 @@ import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
@@ -30,7 +29,14 @@ class BackupCreateService : Service() {
internal const val BACKUP_HISTORY_MASK = 0x4 internal const val BACKUP_HISTORY_MASK = 0x4
internal const val BACKUP_TRACK = 0x8 internal const val BACKUP_TRACK = 0x8
internal const val BACKUP_TRACK_MASK = 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_READ_MANGA = 0x20
internal const val BACKUP_READ_MANGA_MASK = 0x20
internal const val BACKUP_ALL = 0x3F
// SY <--
/** /**
* Returns the status of the service. * Returns the status of the service.
@@ -48,12 +54,11 @@ class BackupCreateService : Service() {
* @param uri path of Uri * @param uri path of Uri
* @param flags determines what to backup * @param flags determines what to backup
*/ */
fun start(context: Context, uri: Uri, flags: Int, type: Int) { fun start(context: Context, uri: Uri, flags: Int) {
if (!isRunning(context)) { if (!isRunning(context)) {
val intent = Intent(context, BackupCreateService::class.java).apply { val intent = Intent(context, BackupCreateService::class.java).apply {
putExtra(BackupConst.EXTRA_URI, uri) putExtra(BackupConst.EXTRA_URI, uri)
putExtra(BackupConst.EXTRA_FLAGS, flags) putExtra(BackupConst.EXTRA_FLAGS, flags)
putExtra(BackupConst.EXTRA_TYPE, type)
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
@@ -101,17 +106,11 @@ class BackupCreateService : Service() {
if (intent == null) return START_NOT_STICKY if (intent == null) return START_NOT_STICKY
try { try {
val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) val uri = intent.getParcelableExtra<Uri>(BackupConst.EXTRA_URI)!!
val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0) val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0)
val backupType = intent.getIntExtra(BackupConst.EXTRA_TYPE, BackupConst.BACKUP_TYPE_LEGACY) val backupFileUri = FullBackupManager(this).createBackup(uri, backupFlags, false)?.toUri()
val backupManager = when (backupType) {
BackupConst.BACKUP_TYPE_FULL -> FullBackupManager(this)
else -> LegacyBackupManager(this)
}
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
val unifile = UniFile.fromUri(this, backupFileUri) val unifile = UniFile.fromUri(this, backupFileUri)
notifier.showBackupComplete(unifile, backupType == BackupConst.BACKUP_TYPE_LEGACY) notifier.showBackupComplete(unifile)
} catch (e: Exception) { } catch (e: Exception) {
notifier.showBackupError(e.message) notifier.showBackupError(e.message)
} }
@@ -8,7 +8,6 @@ import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@@ -23,9 +22,6 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val flags = BackupCreateService.BACKUP_ALL val flags = BackupCreateService.BACKUP_ALL
return try { return try {
FullBackupManager(context).createBackup(uri, flags, true) FullBackupManager(context).createBackup(uri, flags, true)
if (preferences.createLegacyBackup().get()) {
LegacyBackupManager(context).createBackup(uri, flags, true)
}
Result.success() Result.success()
} catch (e: Exception) { } catch (e: Exception) {
Result.failure() Result.failure()
@@ -24,6 +24,7 @@ class BackupNotifier(private val context: Context) {
setSmallIcon(R.drawable.ic_tachi) setSmallIcon(R.drawable.ic_tachi)
setAutoCancel(false) setAutoCancel(false)
setOngoing(true) setOngoing(true)
setOnlyAlertOnce(true)
} }
private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) { 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)) setContentTitle(context.getString(R.string.creating_backup))
setProgress(0, 0, true) setProgress(0, 0, true)
setOnlyAlertOnce(true)
} }
builder.show(Notifications.ID_BACKUP_PROGRESS) builder.show(Notifications.ID_BACKUP_PROGRESS)
@@ -60,7 +60,7 @@ class BackupNotifier(private val context: Context) {
} }
} }
fun showBackupComplete(unifile: UniFile, isLegacyFormat: Boolean) { fun showBackupComplete(unifile: UniFile) {
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) { with(completeNotificationBuilder) {
@@ -73,7 +73,7 @@ class BackupNotifier(private val context: Context) {
addAction( addAction(
R.drawable.ic_share_24dp, R.drawable.ic_share_24dp,
context.getString(R.string.action_share), context.getString(R.string.action_share),
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, isLegacyFormat, Notifications.ID_BACKUP_COMPLETE) NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE)
) )
show(Notifications.ID_BACKUP_COMPLETE) show(Notifications.ID_BACKUP_COMPLETE)
@@ -141,7 +141,7 @@ class BackupNotifier(private val context: Context) {
addAction( addAction(
R.drawable.ic_folder_24dp, R.drawable.ic_folder_24dp,
context.getString(R.string.action_open_log), context.getString(R.string.action_show_errors),
NotificationReceiver.openErrorLogPendingActivity(context, uri) NotificationReceiver.openErrorLogPendingActivity(context, uri)
) )
} }
@@ -43,12 +43,11 @@ class BackupRestoreService : Service() {
* @param context context of application * @param context context of application
* @param uri path of Uri * @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)) { if (!isRunning(context)) {
val intent = Intent(context, BackupRestoreService::class.java).apply { val intent = Intent(context, BackupRestoreService::class.java).apply {
putExtra(BackupConst.EXTRA_URI, uri) putExtra(BackupConst.EXTRA_URI, uri)
putExtra(BackupConst.EXTRA_MODE, mode) putExtra(BackupConst.EXTRA_MODE, mode)
online?.let { putExtra(BackupConst.EXTRA_TYPE, it) }
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
} }
@@ -119,13 +118,12 @@ class BackupRestoreService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY val uri = intent?.getParcelableExtra<Uri>(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
val mode = intent.getIntExtra(BackupConst.EXTRA_MODE, BackupConst.BACKUP_TYPE_FULL) 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. // Cancel any previous job if needed.
backupRestore?.job?.cancel() backupRestore?.job?.cancel()
backupRestore = when (mode) { backupRestore = when (mode) {
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online) BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier)
else -> LegacyBackupRestore(this, notifier) else -> LegacyBackupRestore(this, notifier)
} }
@@ -8,8 +8,12 @@ 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_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER 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_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
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_READ_MANGA
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_READ_MANGA_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.full.models.Backup import eu.kanade.tachiyomi.data.backup.full.models.Backup
@@ -29,11 +33,8 @@ import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track 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.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.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.savedsearches.JsonSavedSearch import exh.savedsearches.JsonSavedSearch
@@ -65,7 +66,11 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
var backup: Backup? = null var backup: Backup? = null
databaseHelper.inTransaction { databaseHelper.inTransaction {
val databaseManga = getFavoriteManga() /* SY --> */ + getReadManga() + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */ val databaseManga = getFavoriteManga() /* SY --> */ + if (flags and BACKUP_READ_MANGA_MASK == BACKUP_READ_MANGA) {
getReadManga()
} else {
emptyList()
} + getMergedManga() /* SY <-- */
backup = Backup( backup = Backup(
backupManga(databaseManga, flags), backupManga(databaseManga, flags),
@@ -164,7 +169,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
*/ */
private fun backupMangaObject(manga: Manga, options: Int): BackupManga { private fun backupMangaObject(manga: Manga, options: Int): BackupManga {
// Entry for this manga // 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 --> // SY -->
if (manga.source == MERGED_SOURCE_ID) { if (manga.source == MERGED_SOURCE_ID) {
@@ -237,24 +242,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
/** /**
* Fetches manga information * Fetches manga information
* *
* @param source source of manga
* @param manga manga that needs updating * @param manga manga that needs updating
* @return Updated manga info. * @return Updated manga info.
*/ */
suspend fun restoreMangaFetch(source: Source?, manga: Manga, online: Boolean): Manga { fun restoreManga(manga: Manga): Manga {
return if (online && source != null /* SY --> */ && source !is MergedSource /* SY <-- */) { return manga.also {
val networkManga = source.getMangaDetails(manga.toMangaInfo()) it.initialized = it.description != null
manga.also { it.id = insertManga(it)
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)
}
} }
} }
@@ -363,29 +357,26 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val trackToUpdate = mutableListOf<Track>() val trackToUpdate = mutableListOf<Track>()
tracks.forEach { track -> tracks.forEach { track ->
val service = trackManager.getService(track.sync_id) var isInDatabase = false
if (service != null && service.isLogged) { for (dbTrack in dbTracks) {
var isInDatabase = false if (track.sync_id == dbTrack.sync_id) {
for (dbTrack in dbTracks) { // The sync is already in the db, only update its fields
if (track.sync_id == dbTrack.sync_id) { if (track.media_id != dbTrack.media_id) {
// The sync is already in the db, only update its fields dbTrack.media_id = track.media_id
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 (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 if (!isInDatabase) {
track.id = null // Insert new sync. Let the db assign the id
trackToUpdate.add(track) track.id = null
} trackToUpdate.add(track)
} }
} }
// Update database // Update database
@@ -394,47 +385,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
} }
/** internal fun restoreChaptersForManga(manga: Manga, chapters: List<Chapter>) {
* 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>) {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking() val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
chapters.forEach { chapter -> chapters.forEach { chapter ->
@@ -527,8 +478,9 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
} }
} }
internal suspend fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) { internal fun restoreFlatMetadata(manga: Manga, backupFlatMetadata: BackupFlatMetadata) {
manga.id?.let { mangaId -> val mangaId = manga.id ?: return
launchIO {
databaseHelper.getFlatMetadataForManga(mangaId).executeOnIO().let { databaseHelper.getFlatMetadataForManga(mangaId).executeOnIO().let {
if (it == null) { if (it == null) {
val flatMetadata = backupFlatMetadata.getFlatMetadata(mangaId) 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.source.online.all.MergedSource
import exh.EXHMigrations import exh.EXHMigrations
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import okio.buffer import okio.buffer
@@ -24,7 +23,7 @@ import okio.gzip
import okio.source import okio.source
import java.util.Date 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 { override suspend fun performRestore(uri: Uri): Boolean {
// SY --> // SY -->
@@ -57,9 +56,11 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
return false return false
} }
restoreManga(it, backup.backupCategories, online) restoreManga(it, backup.backupCategories)
} }
// TODO: optionally trigger online library + tracker update
return true return true
} }
@@ -81,8 +82,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
} }
// SY <-- // SY <--
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) { private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>) {
var manga = backupManga.getMangaImpl() val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl() val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories val categories = backupManga.categories
val history = backupManga.history val history = backupManga.history
@@ -90,22 +91,17 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
// SY --> // SY -->
val mergedMangaReferences = backupManga.mergedMangaReferences val mergedMangaReferences = backupManga.mergedMangaReferences
val flatMetadata = backupManga.flatMetadata val flatMetadata = backupManga.flatMetadata
val customManga = backupManga.getCustomMangaInfo()
// SY <-- // SY <--
// SY --> // SY -->
manga = EXHMigrations.migrateBackupEntry(manga) EXHMigrations.migrateBackupEntry(manga)
// SY <-- // SY <--
val source = backupManager.sourceManager.get(manga.source)
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
try { try {
if (source != null || !online) { restoreMangaData(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
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)}")
}
} catch (e: Exception) { } catch (e: Exception) {
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}") 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 * Returns a manga restore observable
* *
* @param manga manga data from json * @param manga manga data from json
* @param source source to get manga data from
* @param chapters chapters data from json * @param chapters chapters data from json
* @param categories categories data from json * @param categories categories data from json
* @param history history data from json * @param history history data from json
* @param tracks tracking data from json * @param tracks tracking data from json
*/ */
private suspend fun restoreMangaData( private fun restoreMangaData(
manga: Manga, manga: Manga,
source: Source?,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
history: List<BackupHistory>, history: List<BackupHistory>,
tracks: List<Track>, tracks: List<Track>,
backupCategories: List<BackupCategory>, backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>, mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?, flatMetadata: BackupFlatMetadata?,
online: Boolean customManga: CustomMangaManager.MangaJson?,
// SY -->
) { ) {
val dbManga = backupManager.getMangaFromDatabase(manga)
db.inTransaction { db.inTransaction {
val dbManga = backupManager.getMangaFromDatabase(manga)
if (dbManga == null) { if (dbManga == null) {
// Manga not in database // Manga not in database
restoreMangaFetch(source, manga, chapters, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata, online) restoreMangaFetch(manga, chapters, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
} else { // Manga in database } else {
// Manga in database
// Copy information from manga already in database // Copy information from manga already in database
backupManager.restoreMangaNoFetch(manga, dbManga) backupManager.restoreMangaNoFetch(manga, dbManga)
// Fetch rest of manga information // 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 chapters chapters of manga that needs updating
* @param categories categories that need updating * @param categories categories that need updating
*/ */
private suspend fun restoreMangaFetch( private fun restoreMangaFetch(
source: Source?,
manga: Manga, manga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
history: List<BackupHistory>, history: List<BackupHistory>,
tracks: List<Track>, tracks: List<Track>,
backupCategories: List<BackupCategory>, backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>, mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?, flatMetadata: BackupFlatMetadata?,
online: Boolean customManga: CustomMangaManager.MangaJson?,
// SY <--
) { ) {
try { try {
val fetchedManga = backupManager.restoreMangaFetch(source, manga, online) val fetchedManga = backupManager.restoreManga(manga)
fetchedManga.id ?: return fetchedManga.id ?: return
backupManager.restoreChaptersForManga(fetchedManga, chapters)
if (online && source != null) { restoreExtraForManga(fetchedManga, categories, history, tracks, backupCategories /* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
// 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)
} catch (e: Exception) { } catch (e: Exception) {
errors.add(Date() to "${manga.title} - ${e.message}") errors.add(Date() to "${manga.title} - ${e.message}")
} }
} }
private suspend fun restoreMangaNoFetch( private fun restoreMangaNoFetch(
source: Source?,
backupManga: Manga, backupManga: Manga,
chapters: List<Chapter>, chapters: List<Chapter>,
categories: List<Int>, categories: List<Int>,
history: List<BackupHistory>, history: List<BackupHistory>,
tracks: List<Track>, tracks: List<Track>,
backupCategories: List<BackupCategory>, backupCategories: List<BackupCategory>,
// SY -->
mergedMangaReferences: List<BackupMergedMangaReference>, mergedMangaReferences: List<BackupMergedMangaReference>,
flatMetadata: BackupFlatMetadata?, flatMetadata: BackupFlatMetadata?,
online: Boolean customManga: CustomMangaManager.MangaJson?,
// SY <--
) { ) {
if (online && source != null) { backupManager.restoreChaptersForManga(backupManga, chapters)
if (/* SY --> */ source !is MergedSource && /* SY <-- */ !backupManager.restoreChaptersForManga(backupManga, chapters)) {
updateChapters(source, backupManga, chapters)
}
} else {
backupManager.restoreChaptersForMangaOffline(backupManga, chapters)
}
restoreExtraForManga(backupManga, categories, history, tracks, backupCategories, mergedMangaReferences, flatMetadata) restoreExtraForManga(backupManga, categories, history, tracks, backupCategories/* SY --> */, mergedMangaReferences, flatMetadata, customManga/* SY <-- */)
updateTracking(backupManga, tracks)
} }
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 // Restore categories
backupManager.restoreCategoriesForManga(manga, categories, backupCategories) backupManager.restoreCategoriesForManga(manga, categories, backupCategories)
@@ -232,6 +222,10 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
// Restore flat metadata for metadata sources // Restore flat metadata for metadata sources
flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) } flatMetadata?.let { backupManager.restoreFlatMetadata(manga, it) }
// Restore Custom Info
customManga?.id = manga.id!!
customManga?.let { customMangaManager.saveMangaInfo(it) }
// SY <-- // 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.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
@@ -25,7 +26,7 @@ data class BackupManga(
// @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x // @ProtoNumber(11) val lastUpdate: Long = 0, 1.x value, not used in 0.x
// @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x // @ProtoNumber(12) val lastInit: Long = 0, 1.x value, not used in 0.x
@ProtoNumber(13) var dateAdded: Long = 0, @ProtoNumber(13) var dateAdded: Long = 0,
@ProtoNumber(14) var viewer: Int = 0, @ProtoNumber(14) var viewer: Int = 0, // Replaced by viewer_flags
// @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x // @ProtoNumber(15) val flags: Int = 0, 1.x value, not used in 0.x
@ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(), @ProtoNumber(16) var chapters: List<BackupChapter> = emptyList(),
@ProtoNumber(17) var categories: List<Int> = emptyList(), @ProtoNumber(17) var categories: List<Int> = emptyList(),
@@ -34,9 +35,22 @@ data class BackupManga(
@ProtoNumber(100) var favorite: Boolean = true, @ProtoNumber(100) var favorite: Boolean = true,
@ProtoNumber(101) var chapterFlags: Int = 0, @ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(), @ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null,
// SY specific values // SY specific values
@ProtoNumber(600) var mergedMangaReferences: List<BackupMergedMangaReference> = emptyList(), @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,
// Neko specific values
@ProtoNumber(901) var filtered_scanlators: String? = null,
) { ) {
fun getMangaImpl(): MangaImpl { fun getMangaImpl(): MangaImpl {
return MangaImpl().apply { return MangaImpl().apply {
@@ -51,8 +65,9 @@ data class BackupManga(
favorite = this@BackupManga.favorite favorite = this@BackupManga.favorite
source = this@BackupManga.source source = this@BackupManga.source
date_added = this@BackupManga.dateAdded date_added = this@BackupManga.dateAdded
viewer = this@BackupManga.viewer viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer
chapter_flags = this@BackupManga.chapterFlags chapter_flags = this@BackupManga.chapterFlags
filtered_scanlators = this@BackupManga.filtered_scanlators
} }
} }
@@ -62,6 +77,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> { fun getTrackingImpl(): List<TrackImpl> {
return tracking.map { return tracking.map {
it.getTrackingImpl() it.getTrackingImpl()
@@ -69,22 +107,37 @@ data class BackupManga(
} }
companion object { companion object {
fun copyFrom(manga: Manga): BackupManga { fun copyFrom(manga: Manga /* SY --> */, customMangaManager: CustomMangaManager?/* SY <-- */): BackupManga {
return BackupManga( return BackupManga(
url = manga.url, url = manga.url,
title = manga.title, // SY -->
artist = manga.artist, title = manga.originalTitle,
author = manga.author, artist = manga.originalArtist,
description = manga.description, author = manga.originalAuthor,
genre = manga.getGenres() ?: emptyList(), description = manga.originalDescription,
status = manga.status, genre = manga.getOriginalGenres() ?: emptyList(),
status = manga.originalStatus,
// SY <--
thumbnailUrl = manga.thumbnail_url, thumbnailUrl = manga.thumbnail_url,
favorite = manga.favorite, favorite = manga.favorite,
source = manga.source, source = manga.source,
dateAdded = manga.date_added, dateAdded = manga.date_added,
viewer = manga.viewer, viewer = manga.readingModeType,
chapterFlags = manga.chapter_flags viewer_flags = manga.viewer_flags,
) chapterFlags = manga.chapter_flags,
filtered_scanlators = manga.filtered_scanlators
// 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 <--
} }
} }
} }
@@ -5,33 +5,13 @@ import android.net.Uri
import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.registerTypeAdapter import com.github.salomonbrys.kotson.registerTypeAdapter
import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter import com.github.salomonbrys.kotson.registerTypeHierarchyAdapter
import com.github.salomonbrys.kotson.set
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonArray import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupManager import eu.kanade.tachiyomi.data.backup.AbstractBackupManager
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_HISTORY_MASK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_TRACK_MASK
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CATEGORIES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CHAPTERS
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CURRENT_VERSION import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.CURRENT_VERSION
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.EXTENSIONS
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.HISTORY
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MANGA
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.MERGEDMANGAREFERENCES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.SAVEDSEARCHES
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup.TRACK
import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory import eu.kanade.tachiyomi.data.backup.legacy.models.DHistory
import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeAdapter import eu.kanade.tachiyomi.data.backup.legacy.serializer.CategoryTypeAdapter
import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeAdapter import eu.kanade.tachiyomi.data.backup.legacy.serializer.ChapterTypeAdapter
@@ -49,8 +29,6 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
@@ -62,8 +40,6 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.RuntimeException import java.lang.RuntimeException
import kotlin.math.max import kotlin.math.max
@@ -89,180 +65,8 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
* @param uri path of Uri * @param uri path of Uri
* @param isJob backup called from job * @param isJob backup called from job
*/ */
override fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? { override fun createBackup(uri: Uri, flags: Int, isJob: Boolean) =
// Create root object throw IllegalStateException("Legacy backup creation is not supported")
val root = JsonObject()
// Create manga array
val mangaEntries = JsonArray()
// Create category array
val categoryEntries = JsonArray()
// Create extension ID/name mapping
val extensionEntries = JsonArray()
// Merged Manga References
val mergedMangaReferenceEntries = JsonArray()
// Add value's to root
root[Backup.VERSION] = CURRENT_VERSION
root[Backup.MANGAS] = mangaEntries
root[CATEGORIES] = categoryEntries
root[EXTENSIONS] = extensionEntries
// SY -->
root[MERGEDMANGAREFERENCES] = mergedMangaReferenceEntries
// SY <--
databaseHelper.inTransaction {
val mangas = getFavoriteManga()/* SY --> */.filterNot { it.source == MERGED_SOURCE_ID } + getMergedManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY <-- */
val extensions: MutableSet<String> = mutableSetOf()
// Backup library manga and its dependencies
mangas.forEach { manga ->
mangaEntries.add(backupMangaObject(manga, flags))
// Maintain set of extensions/sources used (excludes local source)
if (manga.source != LocalSource.ID) {
sourceManager.get(manga.source)?.let {
extensions.add("${manga.source}:${it.name}")
}
}
}
// Backup categories
if ((flags and BACKUP_CATEGORY_MASK) == BACKUP_CATEGORY) {
backupCategories(categoryEntries)
}
// Backup extension ID/name mapping
backupExtensionInfo(extensionEntries, extensions)
// SY -->
root[SAVEDSEARCHES] =
Injekt.get<PreferencesHelper>().savedSearches().get().joinToString(separator = "***")
backupMergedMangaReferences(mergedMangaReferenceEntries)
// SY <--
}
try {
val file: UniFile = (
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
dir = dir.createDirectory("automatic")
// Delete older backups
val numberOfBackups = numberOfBackups()
val backupRegex = Regex("""tachiyomi_\d+-\d+-\d+_\d+-\d+.json""")
dir.listFiles { _, filename -> backupRegex.matches(filename) }
.orEmpty()
.sortedByDescending { it.name }
.drop(numberOfBackups - 1)
.forEach { it.delete() }
// Create new file to place backup
dir.createFile(Backup.getDefaultFilename())
} else {
UniFile.fromUri(context, uri)
}
)
?: throw Exception("Couldn't create backup file")
file.openOutputStream().bufferedWriter().use {
parser.toJson(root, it)
}
return file.uri.toString()
} catch (e: Exception) {
Timber.e(e)
throw e
}
}
private fun backupExtensionInfo(root: JsonArray, extensions: Set<String>) {
extensions.sorted().forEach {
root.add(it)
}
}
// SY -->
private fun backupMergedMangaReferences(root: JsonArray) {
val mergedMangaReferences = databaseHelper.getMergedMangaReferences().executeAsBlocking()
mergedMangaReferences.forEach { root.add(parser.toJsonTree(it)) }
}
// SY <--
/**
* Backup the categories of library
*
* @param root root of categories json
*/
internal fun backupCategories(root: JsonArray) {
val categories = databaseHelper.getCategories().executeAsBlocking()
categories.forEach { root.add(parser.toJsonTree(it)) }
}
/**
* Convert a manga to Json
*
* @param manga manga that gets converted
* @return [JsonElement] containing manga information
*/
internal fun backupMangaObject(manga: Manga, options: Int): JsonElement {
// Entry for this manga
val entry = JsonObject()
// Backup manga fields
entry[MANGA] = parser.toJsonTree(manga)
// Check if user wants chapter information in backup
if (options and BACKUP_CHAPTER_MASK == BACKUP_CHAPTER) {
// Backup all the chapters
val chapters = databaseHelper.getChapters(manga).executeAsBlocking()
if (chapters.isNotEmpty()) {
val chaptersJson = parser.toJsonTree(chapters)
if (chaptersJson.asJsonArray.size() > 0) {
entry[CHAPTERS] = chaptersJson
}
}
}
// Check if user wants category information in backup
if (options and BACKUP_CATEGORY_MASK == BACKUP_CATEGORY) {
// Backup categories for this manga
val categoriesForManga = databaseHelper.getCategoriesForManga(manga).executeAsBlocking()
if (categoriesForManga.isNotEmpty()) {
val categoriesNames = categoriesForManga.map { it.name }
entry[CATEGORIES] = parser.toJsonTree(categoriesNames)
}
}
// Check if user wants track information in backup
if (options and BACKUP_TRACK_MASK == BACKUP_TRACK) {
val tracks = databaseHelper.getTracks(manga).executeAsBlocking()
if (tracks.isNotEmpty()) {
entry[TRACK] = parser.toJsonTree(tracks)
}
}
// Check if user wants history information in backup
if (options and BACKUP_HISTORY_MASK == BACKUP_HISTORY) {
val historyForManga = databaseHelper.getHistoryByMangaId(manga.id!!).executeAsBlocking()
if (historyForManga.isNotEmpty()) {
val historyData = historyForManga.mapNotNull { history ->
val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url
url?.let { DHistory(url, history.last_read) }
}
val historyJson = parser.toJsonTree(historyData)
if (historyJson.asJsonArray.size() > 0) {
entry[HISTORY] = historyJson
}
}
}
return entry
}
fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) { fun restoreMangaNoFetch(manga: Manga, dbManga: Manga) {
manga.id = dbManga.id manga.id = dbManga.id
@@ -91,7 +91,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
// SY <-- // SY <--
private suspend fun restoreManga(mangaJson: JsonObject) { private suspend fun restoreManga(mangaJson: JsonObject) {
/* SY --> */ var /* SY <-- */ manga = backupManager.parser.fromJson<MangaImpl>( val manga = backupManager.parser.fromJson<MangaImpl>(
mangaJson.get( mangaJson.get(
Backup.MANGA Backup.MANGA
) )
@@ -114,7 +114,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
) )
// EXH --> // EXH -->
manga = EXHMigrations.migrateBackupEntry(manga) EXHMigrations.migrateBackupEntry(manga)
// <-- EXH // <-- EXH
val source = backupManager.sourceManager.get(manga.source) val source = backupManager.sourceManager.get(manga.source)
@@ -18,7 +18,7 @@ object MangaTypeAdapter {
value(it.originalTitle) value(it.originalTitle)
// SY <-- // SY <--
value(it.source) value(it.source)
value(it.viewer) value(it.viewer_flags)
value(it.chapter_flags) value(it.chapter_flags)
endArray() endArray()
} }
@@ -29,7 +29,7 @@ object MangaTypeAdapter {
manga.url = nextString() manga.url = nextString()
manga.title = nextString() manga.title = nextString()
manga.source = nextLong() manga.source = nextLong()
manga.viewer = nextInt() manga.viewer_flags = nextInt()
manga.chapter_flags = nextInt() manga.chapter_flags = nextInt()
endArray() endArray()
manga manga
@@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.data.cache
import android.content.Context import android.content.Context
import android.text.format.Formatter import android.text.format.Formatter
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import com.jakewharton.disklrucache.DiskLruCache import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@@ -15,10 +13,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.Response import okhttp3.Response
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@@ -48,14 +48,12 @@ class ChapterCache(private val context: Context) {
private val scope = CoroutineScope(Job() + Dispatchers.Main) private val scope = CoroutineScope(Job() + Dispatchers.Main)
/** Google Json class used for parsing JSON files. */ /** Google Json class used for parsing JSON files. */
private val gson: Gson by injectLazy() private val json: Json by injectLazy()
// --> EH // --> EH
private val prefs: PreferencesHelper by injectLazy() private val prefs: PreferencesHelper by injectLazy()
// <-- EH
/** Cache class used for cache management. */ /** Cache class used for cache management. */
// --> EH
private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong()) private var diskCache = setupDiskCache(prefs.cacheSize().get().toLong())
init { init {
@@ -73,7 +71,7 @@ class ChapterCache(private val context: Context) {
/** /**
* Returns directory of cache. * Returns directory of cache.
*/ */
val cacheDir: File private val cacheDir: File
get() = diskCache.directory get() = diskCache.directory
/** /**
@@ -100,43 +98,19 @@ class ChapterCache(private val context: Context) {
} }
// <-- EH // <-- 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. * Get page list from cache.
* *
* @param chapter the chapter. * @param chapter the chapter.
* @return an observable of the list of pages. * @return the list of pages.
*/ */
fun getPageListFromCache(chapter: Chapter): Observable<List<Page>> { fun getPageListFromCache(chapter: Chapter): List<Page> {
return Observable.fromCallable { // Get the key for the chapter.
// Get the key for the chapter. val key = DiskUtil.hashKeyForDisk(getKey(chapter))
val key = DiskUtil.hashKeyForDisk(getKey(chapter))
// Convert JSON string to list of objects. Throws an exception if snapshot is null // Convert JSON string to list of objects. Throws an exception if snapshot is null
diskCache.get(key).use { return diskCache.get(key).use {
gson.fromJson<List<Page>>(it.getString(0)) json.decodeFromString(it.getString(0))
}
} }
} }
@@ -148,7 +122,7 @@ class ChapterCache(private val context: Context) {
*/ */
fun putPageListToCache(chapter: Chapter, pages: List<Page>) { fun putPageListToCache(chapter: Chapter, pages: List<Page>) {
// Convert list of pages to json string. // 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). // Initialize the editor (edits the values for an entry).
var editor: DiskLruCache.Editor? = null 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 { private fun getKey(chapter: Chapter): String {
return "${chapter.manga_id}${chapter.url}" return "${chapter.manga_id}${chapter.url}"
} }
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.cache package eu.kanade.tachiyomi.data.cache
import android.content.Context import android.content.Context
import coil.imageLoader
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import java.io.File import java.io.File
@@ -99,6 +100,13 @@ class CoverCache(private val context: Context) {
} }
} }
/**
* Clear coil's memory cache.
*/
fun clearMemoryCache() {
context.imageLoader.memoryCache.clear()
}
private fun getCacheDir(dir: String): File { private fun getCacheDir(dir: String): File {
return context.getExternalFilesDir(dir) return context.getExternalFilesDir(dir)
?: File(context.filesDir, dir).also { it.mkdirs() } ?: File(context.filesDir, dir).also { it.mkdirs() }
@@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.data.coil
import coil.bitmap.BitmapPool
import coil.decode.DataSource
import coil.decode.Options
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.size.Size
import okio.buffer
import okio.source
import java.io.ByteArrayInputStream
import java.nio.ByteBuffer
class ByteBufferFetcher : Fetcher<ByteBuffer> {
override suspend fun fetch(pool: BitmapPool, data: ByteBuffer, size: Size, options: Options): FetchResult {
return SourceResult(
source = ByteArrayInputStream(data.array()).source().buffer(),
mimeType = null,
dataSource = DataSource.MEMORY
)
}
override fun key(data: ByteBuffer): String? = null
}
@@ -0,0 +1,172 @@
package eu.kanade.tachiyomi.data.coil
import coil.bitmap.BitmapPool
import coil.decode.DataSource
import coil.decode.Options
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.network.HttpException
import coil.request.get
import coil.size.Size
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okio.buffer
import okio.sink
import okio.source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.Date
/**
* Coil component that fetches [Manga] cover while using the cached file in disk when available.
*
* Available request parameter:
* - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true
*/
class MangaCoverFetcher : Fetcher<Manga> {
private val coverCache: CoverCache by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val defaultClient = Injekt.get<NetworkHelper>().coilClient
override fun key(data: Manga): String? {
if (data.thumbnail_url.isNullOrBlank()) return null
return data.thumbnail_url!!
}
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult {
// Use custom cover if exists
val useCustomCover = options.parameters[USE_CUSTOM_COVER] as? Boolean ?: true
val customCoverFile = coverCache.getCustomCoverFile(data)
if (useCustomCover && customCoverFile.exists()) {
return fileLoader(customCoverFile)
}
val cover = data.thumbnail_url
return when (getResourceType(cover)) {
Type.URL -> httpLoader(data, options)
Type.File -> fileLoader(data)
null -> error("Invalid image")
}
}
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult {
val coverFile = coverCache.getCoverFile(manga) ?: error("No cover specified")
// Use previously cached cover if exist
if (coverFile.exists() && options.diskCachePolicy.readEnabled) {
if (!manga.favorite) {
coverFile.setLastModified(Date().time)
}
return fileLoader(coverFile)
}
val (response, body) = awaitGetCall(manga, options)
if (!response.isSuccessful) {
body.close()
throw HttpException(response)
}
// Write to disk for future use
if (options.diskCachePolicy.writeEnabled) {
response.peekBody(Long.MAX_VALUE).source().use { input ->
val tmpFile = File(coverFile.absolutePath + "_tmp")
tmpFile.parentFile?.mkdirs()
tmpFile.sink().buffer().use { output ->
output.writeAll(input)
}
if (coverFile.exists()) {
coverFile.delete()
}
tmpFile.renameTo(coverFile)
}
}
return SourceResult(
source = body.source(),
mimeType = "image/*",
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
)
}
private suspend fun awaitGetCall(manga: Manga, options: Options): Pair<Response, ResponseBody> {
val call = getCall(manga, options)
val response = call.await()
return response to checkNotNull(response.body) { "Null response source" }
}
private fun getCall(manga: Manga, options: Options): Call {
val source = sourceManager.get(manga.source) as? HttpSource
val client = source?.client ?: defaultClient
val newClient = client.newBuilder().build()
val request = Request.Builder().url(manga.thumbnail_url!!).also {
if (source != null) {
it.headers(source.headers)
}
val networkRead = options.networkCachePolicy.readEnabled
val diskRead = options.diskCachePolicy.readEnabled
when {
!networkRead && diskRead -> {
it.cacheControl(CacheControl.FORCE_CACHE)
}
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
it.cacheControl(CacheControl.FORCE_NETWORK)
} else {
it.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
}
!networkRead && !diskRead -> {
// This causes the request to fail with a 504 Unsatisfiable Request.
it.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
}
}
}.build()
return newClient.newCall(request)
}
private fun fileLoader(manga: Manga): FetchResult {
return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://")))
}
private fun fileLoader(file: File): FetchResult {
return SourceResult(
source = file.source().buffer(),
mimeType = "image/*",
dataSource = DataSource.DISK
)
}
private fun getResourceType(cover: String?): Type? {
return when {
cover.isNullOrEmpty() -> null
cover.startsWith("http") || cover.startsWith("Custom-", true) -> Type.URL
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
else -> null
}
}
private enum class Type {
File, URL
}
companion object {
const val USE_CUSTOM_COVER = "use_custom_cover"
private val CACHE_CONTROL_FORCE_NETWORK_NO_CACHE = CacheControl.Builder().noCache().noStore().build()
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
}
}
@@ -21,9 +21,6 @@ import eu.kanade.tachiyomi.data.database.queries.HistoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries
import eu.kanade.tachiyomi.data.database.queries.MangaQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries
import eu.kanade.tachiyomi.data.database.queries.TrackQueries import eu.kanade.tachiyomi.data.database.queries.TrackQueries
import exh.md.similar.sql.mappers.SimilarTypeMapping
import exh.md.similar.sql.models.MangaSimilar
import exh.md.similar.sql.queries.SimilarQueries
import exh.merged.sql.mappers.MergedMangaTypeMapping import exh.merged.sql.mappers.MergedMangaTypeMapping
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.merged.sql.queries.MergedQueries import exh.merged.sql.queries.MergedQueries
@@ -42,7 +39,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
* This class provides operations to manage the database through its interfaces. * This class provides operations to manage the database through its interfaces.
*/ */
open class DatabaseHelper(context: Context) : open class DatabaseHelper(context: Context) :
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, SimilarQueries /* SY <-- */ { MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries /* SY <-- */ {
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context) private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
.name(DbOpenCallback.DATABASE_NAME) .name(DbOpenCallback.DATABASE_NAME)
@@ -62,7 +59,6 @@ open class DatabaseHelper(context: Context) :
.addTypeMapping(SearchTag::class.java, SearchTagTypeMapping()) .addTypeMapping(SearchTag::class.java, SearchTagTypeMapping())
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping()) .addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping()) .addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
.addTypeMapping(MangaSimilar::class.java, SimilarTypeMapping())
// SY <-- // SY <--
.build() .build()
@@ -8,7 +8,6 @@ import eu.kanade.tachiyomi.data.database.tables.HistoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import eu.kanade.tachiyomi.data.database.tables.TrackTable import eu.kanade.tachiyomi.data.database.tables.TrackTable
import exh.md.similar.sql.tables.SimilarTable
import exh.merged.sql.tables.MergedTable import exh.merged.sql.tables.MergedTable
import exh.metadata.sql.tables.SearchMetadataTable import exh.metadata.sql.tables.SearchMetadataTable
import exh.metadata.sql.tables.SearchTagTable import exh.metadata.sql.tables.SearchTagTable
@@ -25,7 +24,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/** /**
* Version of the database. * Version of the database.
*/ */
const val DATABASE_VERSION = /* SY --> */ 5 /* SY <-- */ const val DATABASE_VERSION = /* SY --> */ 7 /* SY <-- */
} }
override fun onCreate(db: SupportSQLiteDatabase) = with(db) { override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@@ -40,7 +39,6 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTagTable.createTableQuery) execSQL(SearchTagTable.createTableQuery)
execSQL(SearchTitleTable.createTableQuery) execSQL(SearchTitleTable.createTableQuery)
execSQL(MergedTable.createTableQuery) execSQL(MergedTable.createTableQuery)
execSQL(SimilarTable.createTableQuery)
// SY <-- // SY <--
// DB indexes // DB indexes
@@ -57,7 +55,6 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
execSQL(SearchTitleTable.createMangaIdIndexQuery) execSQL(SearchTitleTable.createMangaIdIndexQuery)
execSQL(SearchTitleTable.createTitleIndexQuery) execSQL(SearchTitleTable.createTitleIndexQuery)
execSQL(MergedTable.createIndexQuery) execSQL(MergedTable.createIndexQuery)
execSQL(SimilarTable.createMangaIdIndexQuery)
// SY <-- // SY <--
} }
@@ -74,9 +71,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
db.execSQL(MergedTable.createTableQuery) db.execSQL(MergedTable.createTableQuery)
db.execSQL(MergedTable.createIndexQuery) db.execSQL(MergedTable.createIndexQuery)
} }
if (oldVersion < 5) { /*if (oldVersion < 5) {
db.execSQL(SimilarTable.createTableQuery) db.execSQL(SimilarTable.createTableQuery)
db.execSQL(SimilarTable.createMangaIdIndexQuery) db.execSQL(SimilarTable.createMangaIdIndexQuery)
}*/
if (oldVersion < 6) {
db.execSQL(MangaTable.addFilteredScanlators)
}
if (oldVersion < 7) {
db.execSQL("DROP TABLE IF EXISTS manga_related")
} }
} }
@@ -18,6 +18,7 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFI
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FILTERED_SCANLATORS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
@@ -59,16 +60,17 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
COL_DESCRIPTION to obj.originalDescription, COL_DESCRIPTION to obj.originalDescription,
COL_GENRE to obj.originalGenre, COL_GENRE to obj.originalGenre,
COL_TITLE to obj.originalTitle, COL_TITLE to obj.originalTitle,
COL_STATUS to obj.originalStatus,
// SY <-- // SY <--
COL_STATUS to obj.status,
COL_THUMBNAIL_URL to obj.thumbnail_url, COL_THUMBNAIL_URL to obj.thumbnail_url,
COL_FAVORITE to obj.favorite, COL_FAVORITE to obj.favorite,
COL_LAST_UPDATE to obj.last_update, COL_LAST_UPDATE to obj.last_update,
COL_INITIALIZED to obj.initialized, COL_INITIALIZED to obj.initialized,
COL_VIEWER to obj.viewer, COL_VIEWER to obj.viewer_flags,
COL_CHAPTER_FLAGS to obj.chapter_flags, COL_CHAPTER_FLAGS to obj.chapter_flags,
COL_COVER_LAST_MODIFIED to obj.cover_last_modified, COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
COL_DATE_ADDED to obj.date_added COL_DATE_ADDED to obj.date_added,
COL_FILTERED_SCANLATORS to obj.filtered_scanlators
) )
} }
@@ -87,10 +89,11 @@ interface BaseMangaGetResolver {
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1 favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE)) last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1 initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
viewer = cursor.getInt(cursor.getColumnIndex(COL_VIEWER)) viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS)) chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED)) cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED)) date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
filtered_scanlators = cursor.getString(cursor.getColumnIndex(COL_FILTERED_SCANLATORS))
} }
} }
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
interface Manga : SManga { interface Manga : SManga {
@@ -15,18 +17,20 @@ interface Manga : SManga {
var date_added: Long var date_added: Long
var viewer: Int var viewer_flags: Int
var chapter_flags: Int var chapter_flags: Int
var cover_last_modified: Long var cover_last_modified: Long
var filtered_scanlators: String?
fun setChapterOrder(order: Int) { fun setChapterOrder(order: Int) {
setFlags(order, SORT_MASK) setChapterFlags(order, CHAPTER_SORT_MASK)
} }
fun sortDescending(): Boolean { fun sortDescending(): Boolean {
return chapter_flags and SORT_MASK == SORT_DESC return chapter_flags and CHAPTER_SORT_MASK == CHAPTER_SORT_DESC
} }
fun getGenres(): List<String>? { fun getGenres(): List<String>? {
@@ -39,60 +43,72 @@ interface Manga : SManga {
} }
// SY <-- // SY <--
private fun setFlags(flag: Int, mask: Int) { private fun setChapterFlags(flag: Int, mask: Int) {
chapter_flags = chapter_flags and mask.inv() or (flag and mask) chapter_flags = chapter_flags and mask.inv() or (flag and mask)
} }
private fun setViewerFlags(flag: Int, mask: Int) {
viewer_flags = viewer_flags and mask.inv() or (flag and mask)
}
// Used to display the chapter's title one way or another // Used to display the chapter's title one way or another
var displayMode: Int var displayMode: Int
get() = chapter_flags and DISPLAY_MASK get() = chapter_flags and CHAPTER_DISPLAY_MASK
set(mode) = setFlags(mode, DISPLAY_MASK) set(mode) = setChapterFlags(mode, CHAPTER_DISPLAY_MASK)
var readFilter: Int var readFilter: Int
get() = chapter_flags and READ_MASK get() = chapter_flags and CHAPTER_READ_MASK
set(filter) = setFlags(filter, READ_MASK) set(filter) = setChapterFlags(filter, CHAPTER_READ_MASK)
var downloadedFilter: Int var downloadedFilter: Int
get() = chapter_flags and DOWNLOADED_MASK get() = chapter_flags and CHAPTER_DOWNLOADED_MASK
set(filter) = setFlags(filter, DOWNLOADED_MASK) set(filter) = setChapterFlags(filter, CHAPTER_DOWNLOADED_MASK)
var bookmarkedFilter: Int var bookmarkedFilter: Int
get() = chapter_flags and BOOKMARKED_MASK get() = chapter_flags and CHAPTER_BOOKMARKED_MASK
set(filter) = setFlags(filter, BOOKMARKED_MASK) set(filter) = setChapterFlags(filter, CHAPTER_BOOKMARKED_MASK)
var sorting: Int var sorting: Int
get() = chapter_flags and SORTING_MASK get() = chapter_flags and CHAPTER_SORTING_MASK
set(sort) = setFlags(sort, SORTING_MASK) set(sort) = setChapterFlags(sort, CHAPTER_SORTING_MASK)
var readingModeType: Int
get() = viewer_flags and ReadingModeType.MASK
set(readingMode) = setViewerFlags(readingMode, ReadingModeType.MASK)
var orientationType: Int
get() = viewer_flags and OrientationType.MASK
set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK)
companion object { companion object {
const val SORT_DESC = 0x00000000
const val SORT_ASC = 0x00000001
const val SORT_MASK = 0x00000001
// Generic filter that does not filter anything // Generic filter that does not filter anything
const val SHOW_ALL = 0x00000000 const val SHOW_ALL = 0x00000000
const val SHOW_UNREAD = 0x00000002 const val CHAPTER_SORT_DESC = 0x00000000
const val SHOW_READ = 0x00000004 const val CHAPTER_SORT_ASC = 0x00000001
const val READ_MASK = 0x00000006 const val CHAPTER_SORT_MASK = 0x00000001
const val SHOW_DOWNLOADED = 0x00000008 const val CHAPTER_SHOW_UNREAD = 0x00000002
const val SHOW_NOT_DOWNLOADED = 0x00000010 const val CHAPTER_SHOW_READ = 0x00000004
const val DOWNLOADED_MASK = 0x00000018 const val CHAPTER_READ_MASK = 0x00000006
const val SHOW_BOOKMARKED = 0x00000020 const val CHAPTER_SHOW_DOWNLOADED = 0x00000008
const val SHOW_NOT_BOOKMARKED = 0x00000040 const val CHAPTER_SHOW_NOT_DOWNLOADED = 0x00000010
const val BOOKMARKED_MASK = 0x00000060 const val CHAPTER_DOWNLOADED_MASK = 0x00000018
const val SORTING_SOURCE = 0x00000000 const val CHAPTER_SHOW_BOOKMARKED = 0x00000020
const val SORTING_NUMBER = 0x00000100 const val CHAPTER_SHOW_NOT_BOOKMARKED = 0x00000040
const val SORTING_UPLOAD_DATE = 0x00000200 const val CHAPTER_BOOKMARKED_MASK = 0x00000060
const val SORTING_MASK = 0x00000300
const val DISPLAY_NAME = 0x00000000 const val CHAPTER_SORTING_SOURCE = 0x00000000
const val DISPLAY_NUMBER = 0x00100000 const val CHAPTER_SORTING_NUMBER = 0x00000100
const val DISPLAY_MASK = 0x00100000 const val CHAPTER_SORTING_UPLOAD_DATE = 0x00000200
const val CHAPTER_SORTING_MASK = 0x00000300
const val CHAPTER_DISPLAY_NAME = 0x00000000
const val CHAPTER_DISPLAY_NUMBER = 0x00100000
const val CHAPTER_DISPLAY_MASK = 0x00100000
fun create(source: Long): Manga = MangaImpl().apply { fun create(source: Long): Manga = MangaImpl().apply {
this.source = source this.source = source
@@ -40,9 +40,11 @@ open class MangaImpl : Manga {
override var genre: String? override var genre: String?
get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre get() = if (favorite) customMangaManager.getManga(this)?.genre ?: ogGenre else ogGenre
set(value) { ogGenre = value } 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 override var thumbnail_url: String? = null
@@ -54,12 +56,14 @@ open class MangaImpl : Manga {
override var initialized: Boolean = false override var initialized: Boolean = false
override var viewer: Int = 0 override var viewer_flags: Int = 0
override var chapter_flags: Int = 0 override var chapter_flags: Int = 0
override var cover_last_modified: Long = 0 override var cover_last_modified: Long = 0
override var filtered_scanlators: String? = null
// SY --> // SY -->
lateinit var ogTitle: String lateinit var ogTitle: String
private set private set
@@ -71,6 +75,8 @@ open class MangaImpl : Manga {
private set private set
var ogGenre: String? = null var ogGenre: String? = null
private set private set
var ogStatus: Int = 0
private set
// SY <-- // SY <--
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@@ -9,12 +9,13 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFilteredScanlatorsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver 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.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaViewerPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
@@ -102,20 +103,35 @@ interface MangaQueries : DbProvider {
.`object`(manga) .`object`(manga)
.withPutResolver(MangaMigrationPutResolver()) .withPutResolver(MangaMigrationPutResolver())
.prepare() .prepare()
fun updateMangaThumbnail(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaThumbnailPutResolver())
.prepare()
// SY <-- // SY <--
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare() fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare() fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
fun updateFlags(manga: Manga) = db.put() fun updateChapterFlags(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaFlagsPutResolver()) .withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare() .prepare()
fun updateFlags(mangas: List<Manga>) = db.put() fun updateChapterFlags(manga: List<Manga>) = db.put()
.objects(mangas) .objects(manga)
.withPutResolver(MangaFlagsPutResolver(true)) .withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags, true))
.prepare()
fun updateViewerFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare()
fun updateViewerFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags, true))
.prepare() .prepare()
fun updateLastUpdated(manga: Manga) = db.put() fun updateLastUpdated(manga: Manga) = db.put()
@@ -128,11 +144,6 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaFavoritePutResolver()) .withPutResolver(MangaFavoritePutResolver())
.prepare() .prepare()
fun updateMangaViewer(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaViewerPutResolver())
.prepare()
fun updateMangaTitle(manga: Manga) = db.put() fun updateMangaTitle(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaTitlePutResolver()) .withPutResolver(MangaTitlePutResolver())
@@ -143,6 +154,13 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaCoverLastModifiedPutResolver()) .withPutResolver(MangaCoverLastModifiedPutResolver())
.prepare() .prepare()
// SY -->
fun updateMangaFilteredScanlators(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFilteredScanlatorsPutResolver())
.prepare()
// SY <--
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare() fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare() fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
@@ -151,12 +169,38 @@ interface MangaQueries : DbProvider {
.byQuery( .byQuery(
DeleteQuery.builder() DeleteQuery.builder()
.table(MangaTable.TABLE) .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) .whereArgs(0)
.build() .build()
) )
.prepare() .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() fun deleteMangas() = db.delete()
.byQuery( .byQuery(
DeleteQuery.builder() DeleteQuery.builder()
@@ -195,6 +239,16 @@ interface MangaQueries : DbProvider {
) )
.prepare() .prepare()
fun getChapterFetchDateManga() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
RawQuery.builder()
.query(getChapterFetchDateMangaQuery())
.observesTables(MangaTable.TABLE)
.build()
)
.prepare()
// SY --> // SY -->
fun getMangaWithMetadata() = db.get() fun getMangaWithMetadata() = db.get()
.listOfObjects(Manga::class.java) .listOfObjects(Manga::class.java)
@@ -69,7 +69,7 @@ fun getReadMangaNotInLibraryQuery() =
SELECT ${Manga.TABLE}.* SELECT ${Manga.TABLE}.*
FROM ${Manga.TABLE} FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 0 AND ${Manga.COL_ID} IN( 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 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. * Query to get the categories for a manga.
*/ */
@@ -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
// [EXH]
class MangaFilteredScanlatorsPutResolver : 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_FILTERED_SCANLATORS} = ?")
.whereArgs(manga.filtered_scanlators)
.build()
fun mapToContentValues(manga: Manga) = contentValuesOf(
MangaTable.COL_FILTERED_SCANLATORS to manga.filtered_scanlators
)
}
@@ -8,8 +8,9 @@ import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import kotlin.reflect.KProperty1
class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolver<Manga>() { class MangaFlagsPutResolver(private val colName: String, private val fieldGetter: KProperty1<Manga, Int>, private val updateAll: Boolean = false) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga) val updateQuery = mapToUpdateQuery(manga)
@@ -37,6 +38,6 @@ class MangaFlagsPutResolver(private val updateAll: Boolean = false) : PutResolve
fun mapToContentValues(manga: Manga) = fun mapToContentValues(manga: Manga) =
contentValuesOf( contentValuesOf(
MangaTable.COL_CHAPTER_FLAGS to manga.chapter_flags colName to fieldGetter.get(manga)
) )
} }
@@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.data.database.resolvers package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver 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.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
import exh.util.nullIfZero
class MangaInfoPutResolver(val reset: Boolean = false) : PutResolver<Manga>() { 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_GENRE to manga.originalGenre,
MangaTable.COL_AUTHOR to manga.originalAuthor, MangaTable.COL_AUTHOR to manga.originalAuthor,
MangaTable.COL_ARTIST to manga.originalArtist, 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 { private fun resetToContentValues(manga: Manga) = contentValuesOf(
val splitter = "▒ ▒∩▒" MangaTable.COL_TITLE to manga.title.split(splitter).last(),
put(MangaTable.COL_TITLE, manga.title.split(splitter).last()) MangaTable.COL_GENRE to manga.genre?.split(splitter)?.lastOrNull(),
put(MangaTable.COL_GENRE, manga.genre?.split(splitter)?.lastOrNull()) MangaTable.COL_AUTHOR to manga.author?.split(splitter)?.lastOrNull(),
put(MangaTable.COL_AUTHOR, manga.author?.split(splitter)?.lastOrNull()) MangaTable.COL_ARTIST to manga.artist?.split(splitter)?.lastOrNull(),
put(MangaTable.COL_ARTIST, manga.artist?.split(splitter)?.lastOrNull()) MangaTable.COL_DESCRIPTION to manga.description?.split(splitter)?.lastOrNull(),
put(MangaTable.COL_DESCRIPTION, manga.description?.split(splitter)?.lastOrNull()) MangaTable.COL_STATUS to manga.status.nullIfZero()?.toString()?.split(splitter)?.lastOrNull()
)
companion object {
const val splitter = "▒ ▒∩▒"
} }
} }
@@ -30,6 +30,6 @@ class MangaMigrationPutResolver : PutResolver<Manga>() {
MangaTable.COL_DATE_ADDED to manga.date_added, MangaTable.COL_DATE_ADDED to manga.date_added,
MangaTable.COL_TITLE to manga.title, MangaTable.COL_TITLE to manga.title,
MangaTable.COL_CHAPTER_FLAGS to manga.chapter_flags, MangaTable.COL_CHAPTER_FLAGS to manga.chapter_flags,
MangaTable.COL_VIEWER to manga.viewer MangaTable.COL_VIEWER to manga.viewer_flags
) )
} }
@@ -9,7 +9,8 @@ import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaViewerPutResolver : PutResolver<Manga>() { // SY
class MangaThumbnailPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn { override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga) val updateQuery = mapToUpdateQuery(manga)
@@ -25,8 +26,7 @@ class MangaViewerPutResolver : PutResolver<Manga>() {
.whereArgs(manga.id) .whereArgs(manga.id)
.build() .build()
fun mapToContentValues(manga: Manga) = fun mapToContentValues(manga: Manga) = contentValuesOf(
contentValuesOf( MangaTable.COL_THUMBNAIL_URL to manga.thumbnail_url
MangaTable.COL_VIEWER to manga.viewer )
)
} }
@@ -40,6 +40,8 @@ object MangaTable {
// SY ->> // SY ->>
const val COL_READ = "read" const val COL_READ = "read"
const val COL_FILTERED_SCANLATORS = "filtered_scanlators"
// SY <-- // SY <--
const val COL_CATEGORY = "category" const val COL_CATEGORY = "category"
@@ -65,7 +67,8 @@ object MangaTable {
$COL_VIEWER INTEGER NOT NULL, $COL_VIEWER INTEGER NOT NULL,
$COL_CHAPTER_FLAGS INTEGER NOT NULL, $COL_CHAPTER_FLAGS INTEGER NOT NULL,
$COL_COVER_LAST_MODIFIED LONG NOT NULL, $COL_COVER_LAST_MODIFIED LONG NOT NULL,
$COL_DATE_ADDED LONG NOT NULL $COL_DATE_ADDED LONG NOT NULL,
$COL_FILTERED_SCANLATORS TEXT
)""" )"""
val createUrlIndexQuery: String val createUrlIndexQuery: String
@@ -90,4 +93,7 @@ object MangaTable {
"FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " + "FROM $TABLE INNER JOIN ${ChapterTable.TABLE} " +
"ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " + "ON $TABLE.$COL_ID = ${ChapterTable.TABLE}.${ChapterTable.COL_MANGA_ID} " +
"GROUP BY $TABLE.$COL_ID)" "GROUP BY $TABLE.$COL_ID)"
val addFilteredScanlators: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FILTERED_SCANLATORS TEXT"
} }
@@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.lang.launchIO
import rx.Observable import rx.Observable
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@@ -23,7 +24,7 @@ import uy.kohesive.injekt.injectLazy
* *
* @param context the application context. * @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 sourceManager: SourceManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
@@ -202,6 +203,15 @@ class DownloadManager(/* SY private */ val context: Context) {
deleteChapters(listOf(download.chapter), download.manga, download.source) deleteChapters(listOf(download.chapter), download.manga, download.source)
} }
fun deletePendingDownloads(vararg downloads: Download) {
val downloadsByManga = downloads.groupBy { it.manga.id }
downloadsByManga.map { entry ->
val manga = entry.value.first().manga
val source = entry.value.first().source
deleteChapters(entry.value.map { it.chapter }, manga, source)
}
}
/** /**
* Deletes the directories of a list of downloaded chapters. * Deletes the directories of a list of downloaded chapters.
* *
@@ -211,16 +221,16 @@ class DownloadManager(/* SY private */ val context: Context) {
*/ */
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> { fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
val filteredChapters = getChaptersToDelete(chapters) val filteredChapters = getChaptersToDelete(chapters)
launchIO {
removeFromDownloadQueue(filteredChapters)
removeFromDownloadQueue(filteredChapters) val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source)
chapterDirs.forEach { it.delete() }
val chapterDirs = provider.findChapterDirs(filteredChapters, manga, source) cache.removeChapters(filteredChapters, manga)
chapterDirs.forEach { it.delete() } if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty
cache.removeChapters(filteredChapters, manga) chapterDirs.firstOrNull()?.parentFile?.delete()
if (cache.getDownloadCount(manga) == 0) { // Delete manga directory if empty }
chapterDirs.firstOrNull()?.parentFile?.delete()
} }
return filteredChapters return filteredChapters
} }
@@ -262,7 +272,7 @@ class DownloadManager(/* SY private */ val context: Context) {
if (removeNonFavorite && !manga.favorite) { if (removeNonFavorite && !manga.favorite) {
val mangaFolder = provider.getMangaDir(manga, source) val mangaFolder = provider.getMangaDir(manga, source)
cleaned += 1 + (mangaFolder.listFiles()?.size ?: 0) cleaned += 1 + mangaFolder.listFiles().orEmpty().size
mangaFolder.delete() mangaFolder.delete()
cache.removeManga(manga) cache.removeManga(manga)
return cleaned return cleaned
@@ -283,8 +293,7 @@ class DownloadManager(/* SY private */ val context: Context) {
if (cache.getDownloadCount(manga) == 0) { if (cache.getDownloadCount(manga) == 0) {
val mangaFolder = provider.getMangaDir(manga, source) val mangaFolder = provider.getMangaDir(manga, source)
val size = mangaFolder.listFiles()?.size ?: 0 if (!mangaFolder.listFiles().isNullOrEmpty()) {
if (size == 0) {
mangaFolder.delete() mangaFolder.delete()
cache.removeManga(manga) cache.removeManga(manga)
} else { } else {
@@ -302,9 +311,11 @@ class DownloadManager(/* SY private */ val context: Context) {
* @param source the source of the manga. * @param source the source of the manga.
*/ */
fun deleteManga(manga: Manga, source: Source) { fun deleteManga(manga: Manga, source: Source) {
downloader.queue.remove(manga) launchIO {
provider.findMangaDir(manga, source)?.delete() downloader.queue.remove(manga)
cache.removeManga(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 { private val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) { context.notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) 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) { fun onProgressChange(download: Download) {
with(progressNotificationBuilder) { with(progressNotificationBuilder) {
// Check if first call.
if (!isDownloading) { if (!isDownloading) {
setSmallIcon(android.R.drawable.stat_sys_download) setSmallIcon(android.R.drawable.stat_sys_download)
setAutoCancel(false)
clearActions() clearActions()
// Open download manager when clicked // Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
@@ -114,6 +114,7 @@ internal class DownloadNotifier(private val context: Context) {
} }
setProgress(download.pages!!.size, download.downloadedImages, false) setProgress(download.pages!!.size, download.downloadedImages, false)
setOngoing(true)
show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS) show(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS)
} }
@@ -127,8 +128,8 @@ internal class DownloadNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.chapter_paused)) setContentTitle(context.getString(R.string.chapter_paused))
setContentText(context.getString(R.string.download_notifier_download_paused)) setContentText(context.getString(R.string.download_notifier_download_paused))
setSmallIcon(R.drawable.ic_pause_24dp) setSmallIcon(R.drawable.ic_pause_24dp)
setAutoCancel(false)
setProgress(0, 0, false) setProgress(0, 0, false)
setOngoing(false)
clearActions() clearActions()
// Open download manager when clicked // Open download manager when clicked
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) 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)) setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
clearActions() clearActions()
setAutoCancel(false)
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context)) setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
setProgress(0, 0, false) setProgress(0, 0, false)
@@ -53,8 +53,8 @@ class DownloadProvider(private val context: Context) {
return downloadsDir return downloadsDir
.createDirectory(getSourceDirName(source)) .createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga)) .createDirectory(getMangaDirName(manga))
} catch (e: NullPointerException) { } catch (e: Throwable) {
Timber.w(e) Timber.e(e, "Invalid download directory")
throw Exception(context.getString(R.string.invalid_download_dir)) 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. * @param source the source to query.
*/ */
fun findSourceDir(source: Source): UniFile? { 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? { fun findMangaDir(manga: Manga, source: Source): UniFile? {
val sourceDir = findSourceDir(source) 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? { fun findChapterDir(chapter: Chapter, manga: Manga, source: Source): UniFile? {
val mangaDir = findMangaDir(manga, source) val mangaDir = findMangaDir(manga, source)
return getValidChapterDirNames(chapter).asSequence() return getValidChapterDirNames(chapter).asSequence()
.mapNotNull { mangaDir?.findFile(it) ?: mangaDir?.findFile("$it.cbz") } .mapNotNull { mangaDir?.findFile(it, true) ?: mangaDir?.findFile("$it.cbz", true) }
.firstOrNull() .firstOrNull()
} }
@@ -123,14 +123,12 @@ class DownloadProvider(private val context: Context) {
source: Source source: Source
): List<UniFile> { ): List<UniFile> {
val mangaDir = findMangaDir(manga, source) ?: return emptyList() val mangaDir = findMangaDir(manga, source) ?: return emptyList()
return mangaDir.listFiles()!!.asList().filter { return mangaDir.listFiles().orEmpty().asList().filter {
( chapters.find { chp ->
chapters.find { chp -> getValidChapterDirNames(chp).any { dir ->
getValidChapterDirNames(chp).any { dir -> mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null
mangaDir.findFile(dir) ?: mangaDir.findFile("$dir.cbz") != null }
} } == null || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
} == null
) || it.name?.endsWith(Downloader.TMP_DIR_SUFFIX) == true
} }
} }
// SY <-- // SY <--
@@ -141,7 +139,7 @@ class DownloadProvider(private val context: Context) {
* @param source the source to query. * @param source the source to query.
*/ */
fun getSourceDirName(source: Source): String { 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( return listOf(
getChapterDirName(chapter), getChapterDirName(chapter),
// TODO: remove this
// Legacy chapter directory name used in v0.9.2 and before // Legacy chapter directory name used in v0.9.2 and before
DiskUtil.buildValidFilename(chapter.name) DiskUtil.buildValidFilename(chapter.name)
) )
@@ -1,60 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import android.content.ContentValues.TAG
import android.util.Log
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
open class FileFetcher(private val filePath: String = "") : DataFetcher<InputStream> {
private var data: InputStream? = null
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
loadFromFile(callback)
}
private fun loadFromFile(callback: DataFetcher.DataCallback<in InputStream>) {
loadFromFile(File(filePath), callback)
}
protected fun loadFromFile(file: File, callback: DataFetcher.DataCallback<in InputStream>) {
try {
data = FileInputStream(file)
} catch (e: FileNotFoundException) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Timber.d(e, "Failed to open file")
}
callback.onLoadFailed(e)
return
}
callback.onDataReady(data)
}
override fun cleanup() {
try {
data?.close()
} catch (e: IOException) {
// Ignored.
}
}
override fun cancel() {
// Do nothing.
}
override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}
override fun getDataSource(): DataSource {
return DataSource.LOCAL
}
}
@@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.data.DataFetcher
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import java.io.File
import java.io.InputStream
import java.lang.Exception
open class LibraryMangaCustomCoverFetcher(
private val manga: Manga,
private val coverCache: CoverCache
) : FileFetcher() {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
getCustomCoverFile()?.let {
loadFromFile(it, callback)
} ?: callback.onLoadFailed(Exception("Custom cover file not found"))
}
protected fun getCustomCoverFile(): File? {
return coverCache.getCustomCoverFile(manga).takeIf { it.exists() }
}
}
@@ -1,86 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.data.DataFetcher
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
/**
* A [DataFetcher] for loading a cover of a library manga.
* It tries to load the cover from our custom cache, and if it's not found, it fallbacks to network
* and copies the result to the cache.
*
* @param networkFetcher the network fetcher for this cover.
* @param manga the manga of the cover to load.
* @param file the file where this cover should be. It may exists or not.
*/
class LibraryMangaUrlFetcher(
private val networkFetcher: DataFetcher<InputStream>,
private val manga: Manga,
private val coverCache: CoverCache
) : LibraryMangaCustomCoverFetcher(manga, coverCache) {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
getCustomCoverFile()?.let {
loadFromFile(it, callback)
return
}
val cover = coverCache.getCoverFile(manga)
if (cover == null) {
callback.onLoadFailed(Exception("Null thumbnail url"))
return
}
if (!cover.exists()) {
networkFetcher.loadData(
priority,
object : DataFetcher.DataCallback<InputStream> {
override fun onDataReady(data: InputStream?) {
if (data != null) {
val tmpFile = File(cover.path + ".tmp")
try {
// Retrieve destination stream, create parent folders if needed.
val output = try {
tmpFile.outputStream()
} catch (e: FileNotFoundException) {
tmpFile.parentFile!!.mkdirs()
tmpFile.outputStream()
}
// Copy the file and rename to the original.
data.use { output.use { data.copyTo(output) } }
tmpFile.renameTo(cover)
loadFromFile(cover, callback)
} catch (e: Exception) {
tmpFile.delete()
callback.onLoadFailed(e)
}
} else {
callback.onLoadFailed(Exception("Null data"))
}
}
override fun onLoadFailed(e: Exception) {
callback.onLoadFailed(e)
}
}
)
} else {
loadFromFile(cover, callback)
}
}
override fun cleanup() {
super.cleanup()
networkFetcher.cleanup()
}
override fun cancel() {
super.cancel()
networkFetcher.cancel()
}
}
@@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import com.bumptech.glide.load.Key
import eu.kanade.tachiyomi.data.database.models.Manga
import java.security.MessageDigest
data class MangaThumbnail(val manga: Manga, val coverLastModified: Long) : Key {
val key = manga.url + coverLastModified
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(key.toByteArray(Key.CHARSET))
}
}
fun Manga.toMangaThumbnail() = MangaThumbnail(this, cover_last_modified)
@@ -1,134 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.Headers
import com.bumptech.glide.load.model.LazyHeaders
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.InputStream
/**
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
* Coupled with [LibraryMangaUrlFetcher], this class allows to implement the following flow:
*
* - Check in RAM LRU.
* - Check in disk LRU.
* - Check in this module.
* - Fetch from the network connection.
*
* @param context the application context.
*/
class MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, InputStream> {
/**
* Cover cache where persistent covers are stored.
*/
private val coverCache: CoverCache by injectLazy()
/**
* Source manager.
*/
private val sourceManager: SourceManager by injectLazy()
/**
* Default network client.
*/
private val defaultClient = Injekt.get<NetworkHelper>().client
/**
* Map where request headers are stored for a source.
*/
private val cachedHeaders = hashMapOf<Long, LazyHeaders>()
/**
* Factory class for creating [MangaThumbnailModelLoader] instances.
*/
class Factory : ModelLoaderFactory<MangaThumbnail, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MangaThumbnail, InputStream> {
return MangaThumbnailModelLoader()
}
override fun teardown() {}
}
override fun handles(model: MangaThumbnail): Boolean {
return true
}
/**
* Returns a fetcher for the given manga or null if the url is empty.
*
* @param mangaThumbnail the model.
* @param width the width of the view where the resource will be loaded.
* @param height the height of the view where the resource will be loaded.
*/
override fun buildLoadData(
mangaThumbnail: MangaThumbnail,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<InputStream>? {
val manga = mangaThumbnail.manga
val url = manga.thumbnail_url
if (url.isNullOrEmpty()) {
return if (!manga.favorite || manga.isLocal()) {
null
} else {
ModelLoader.LoadData(mangaThumbnail, LibraryMangaCustomCoverFetcher(manga, coverCache))
}
}
if (url.startsWith("http", true)) {
val source = sourceManager.get(manga.source) as? HttpSource
val glideUrl = GlideUrl(url, getHeaders(manga, source))
// Get the resource fetcher for this request url.
val networkFetcher = OkHttpStreamFetcher(source?.client ?: defaultClient, glideUrl)
if (!manga.favorite) {
return ModelLoader.LoadData(glideUrl, networkFetcher)
}
val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, coverCache)
// Return an instance of the fetcher providing the needed elements.
return ModelLoader.LoadData(mangaThumbnail, libraryFetcher)
} else {
// Return an instance of the fetcher providing the needed elements.
return ModelLoader.LoadData(mangaThumbnail, FileFetcher(url.removePrefix("file://")))
}
}
/**
* Returns the request headers for a source copying its OkHttp headers and caching them.
*
* @param manga the model.
*/
private fun getHeaders(manga: Manga, source: HttpSource?): Headers {
if (source == null) return LazyHeaders.DEFAULT
return cachedHeaders.getOrPut(manga.source) {
LazyHeaders.Builder().apply {
val nullStr: String? = null
setHeader("User-Agent", nullStr)
for ((key, value) in source.headers.toMultimap()) {
addHeader(key, value[0])
}
}.build()
}
}
}
@@ -1,72 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey
import java.io.IOException
import java.io.InputStream
class PassthroughModelLoader : ModelLoader<InputStream, InputStream> {
override fun buildLoadData(
model: InputStream,
width: Int,
height: Int,
options: Options
): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(ObjectKey(model), Fetcher(model))
}
override fun handles(model: InputStream): Boolean {
return true
}
class Fetcher(private val stream: InputStream) : DataFetcher<InputStream> {
override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}
override fun cleanup() {
try {
stream.close()
} catch (e: IOException) {
// Do nothing
}
}
override fun getDataSource(): DataSource {
return DataSource.LOCAL
}
override fun cancel() {
// Do nothing
}
override fun loadData(
priority: Priority,
callback: DataFetcher.DataCallback<in InputStream>
) {
callback.onDataReady(stream)
}
}
/**
* Factory class for creating [PassthroughModelLoader] instances.
*/
class Factory : ModelLoaderFactory<InputStream, InputStream> {
override fun build(
multiFactory: MultiModelLoaderFactory
): ModelLoader<InputStream, InputStream> {
return PassthroughModelLoader()
}
override fun teardown() {}
}
}
@@ -1,55 +0,0 @@
package eu.kanade.tachiyomi.data.glide
import android.content.Context
import android.graphics.drawable.Drawable
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.module.AppGlideModule
import com.bumptech.glide.request.RequestOptions
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.InputStream
/**
* Class used to update Glide module settings
*/
@GlideModule
class TachiGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
builder.setDefaultTransitionOptions(
Drawable::class.java,
DrawableTransitionOptions.withCrossFade()
)
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
registry.replace(
GlideUrl::class.java,
InputStream::class.java,
networkFactory
)
registry.append(
MangaThumbnail::class.java,
InputStream::class.java,
MangaThumbnailModelLoader.Factory()
)
registry.append(
InputStream::class.java,
InputStream::class.java,
PassthroughModelLoader.Factory()
)
}
}
@@ -8,7 +8,6 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.File import java.io.File
import java.util.Scanner
class CustomMangaManager(val context: Context) { class CustomMangaManager(val context: Context) {
@@ -23,7 +22,7 @@ class CustomMangaManager(val context: Context) {
val json = try { val json = try {
Json.decodeFromString<MangaList>( Json.decodeFromString<MangaList>(
Scanner(editJson).useDelimiter("\\Z").next() editJson.bufferedReader().use { it.readText() }
) )
} catch (e: Exception) { } catch (e: Exception) {
null null
@@ -32,30 +31,15 @@ class CustomMangaManager(val context: Context) {
val mangasJson = json.mangas ?: return mutableMapOf() val mangasJson = json.mangas ?: return mutableMapOf()
return mangasJson.mapNotNull { mangaJson -> return mangasJson.mapNotNull { mangaJson ->
val id = mangaJson.id ?: return@mapNotNull null val id = mangaJson.id ?: return@mapNotNull null
val manga = MangaImpl().apply { id to mangaJson.toManga()
this.id = id
title = mangaJson.title ?: ""
author = mangaJson.author
artist = mangaJson.artist
description = mangaJson.description
genre = mangaJson.genre?.joinToString(", ")
}
id to manga
}.toMap().toMutableMap() }.toMap().toMutableMap()
} }
fun saveMangaInfo(manga: MangaJson) { 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!!) customMangaMap.remove(manga.id!!)
} else { } else {
customMangaMap[manga.id!!] = MangaImpl().apply { customMangaMap[manga.id!!] = manga.toManga()
id = manga.id
title = manga.title ?: ""
author = manga.author
artist = manga.artist
description = manga.description
genre = manga.genre?.joinToString(", ")
}
} }
saveCustomInfo() saveCustomInfo()
} }
@@ -75,7 +59,8 @@ class CustomMangaManager(val context: Context) {
author, author,
artist, artist,
description, description,
genre?.split(", ") genre?.split(", "),
status
) )
} }
@@ -86,24 +71,23 @@ class CustomMangaManager(val context: Context) {
@Serializable @Serializable
data class MangaJson( data class MangaJson(
val id: Long? = null, var id: Long? = null,
val title: String? = null, val title: String? = null,
val author: String? = null, val author: String? = null,
val artist: String? = null, val artist: String? = null,
val description: 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 { fun toManga() = MangaImpl().apply {
if (this === other) return true id = this@MangaJson.id
if (javaClass != other?.javaClass) return false title = this@MangaJson.title ?: ""
other as MangaJson author = this@MangaJson.author
if (id != other.id) return false artist = this@MangaJson.artist
return true description = this@MangaJson.description
} genre = this@MangaJson.genre?.joinToString(", ")
status = this@MangaJson.status ?: 0
override fun hashCode(): Int {
return id.hashCode()
} }
} }
} }
@@ -8,7 +8,9 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.CHARGING
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -31,9 +33,9 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val preferences = Injekt.get<PreferencesHelper>() val preferences = Injekt.get<PreferencesHelper>()
val interval = prefInterval ?: preferences.libraryUpdateInterval().get() val interval = prefInterval ?: preferences.libraryUpdateInterval().get()
if (interval > 0) { if (interval > 0) {
val restrictions = preferences.libraryUpdateRestriction()!! val restrictions = preferences.libraryUpdateRestriction().get()
val acRestriction = "ac" in restrictions val acRestriction = CHARGING in restrictions
val wifiRestriction = if ("wifi" in restrictions) { val wifiRestriction = if (UNMETERED_NETWORK in restrictions) {
NetworkType.UNMETERED NetworkType.UNMETERED
} else { } else {
NetworkType.CONNECTED NetworkType.CONNECTED
@@ -6,20 +6,23 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.bumptech.glide.Glide import coil.imageLoader
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
@@ -76,7 +79,8 @@ class LibraryUpdateNotifier(private val context: Context) {
context.notificationManager.notify( context.notificationManager.notify(
Notifications.ID_LIBRARY_PROGRESS, Notifications.ID_LIBRARY_PROGRESS,
progressNotificationBuilder progressNotificationBuilder
.setContentTitle(title) .setContentTitle(title.chop(40))
.setContentText("($current/$total)")
.setProgress(total, current, false) .setProgress(total, current, false)
.build() .build()
) )
@@ -111,7 +115,7 @@ class LibraryUpdateNotifier(private val context: Context) {
setContentIntent(errorLogIntent) setContentIntent(errorLogIntent)
addAction( addAction(
R.drawable.ic_folder_24dp, R.drawable.ic_folder_24dp,
context.getString(R.string.action_open_log), context.getString(R.string.action_show_errors),
errorLogIntent errorLogIntent
) )
} }
@@ -166,14 +170,17 @@ class LibraryUpdateNotifier(private val context: Context) {
// Per-manga notification // Per-manga notification
if (!preferences.hideNotificationContent()) { if (!preferences.hideNotificationContent()) {
updates.forEach { (manga, chapters) -> launchUI {
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters)) updates.forEach { (manga, chapters) ->
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
}
} }
} }
} }
} }
private fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification { private suspend fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
val icon = getMangaIcon(manga)
return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setContentTitle(manga.title) setContentTitle(manga.title)
@@ -183,7 +190,6 @@ class LibraryUpdateNotifier(private val context: Context) {
setSmallIcon(R.drawable.ic_tachi) setSmallIcon(R.drawable.ic_tachi)
val icon = getMangaIcon(manga)
if (icon != null) { if (icon != null) {
setLargeIcon(icon) setLargeIcon(icon)
} }
@@ -227,23 +233,14 @@ class LibraryUpdateNotifier(private val context: Context) {
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS) context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
} }
private fun getMangaIcon(manga: Manga): Bitmap? { private suspend fun getMangaIcon(manga: Manga): Bitmap? {
return try { val request = ImageRequest.Builder(context)
Glide.with(context) .data(manga)
.asBitmap() .transformations(CircleCropTransformation())
.load(manga.toMangaThumbnail()) .size(NOTIF_ICON_SIZE)
.dontTransform() .build()
.centerCrop() val drawable = context.imageLoader.execute(request).drawable
.circleCrop() return (drawable as? BitmapDrawable)?.bitmap
.override(
NOTIF_ICON_SIZE,
NOTIF_ICON_SIZE
)
.submit()
.get()
} catch (e: Exception) {
null
}
} }
private fun getNewChaptersDescription(chapters: Array<Chapter>): String { private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
@@ -22,16 +22,18 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.library.LibraryGroup import eu.kanade.tachiyomi.ui.library.LibraryGroup
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.NoChaptersException import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
@@ -41,10 +43,11 @@ import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.isServiceRunning import eu.kanade.tachiyomi.util.system.isServiceRunning
import exh.md.utils.FollowStatus import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil 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.LIBRARY_UPDATE_EXCLUDED_SOURCES
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource import exh.source.getMainSource
import exh.source.isMdBasedSource
import exh.source.mangaDexSourceIds import exh.source.mangaDexSourceIds
import exh.util.executeOnIO import exh.util.executeOnIO
import exh.util.nullIfBlank import exh.util.nullIfBlank
@@ -88,6 +91,7 @@ class LibraryUpdateService(
private lateinit var notifier: LibraryUpdateNotifier private lateinit var notifier: LibraryUpdateNotifier
private lateinit var ioScope: CoroutineScope private lateinit var ioScope: CoroutineScope
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
private var updateJob: Job? = null private var updateJob: Job? = null
/** /**
@@ -109,6 +113,8 @@ class LibraryUpdateService(
companion object { companion object {
private var instance: LibraryUpdateService? = null
/** /**
* Key for category to update. * Key for category to update.
*/ */
@@ -147,7 +153,7 @@ class LibraryUpdateService(
* @return true if service newly started, false otherwise * @return true if service newly started, false otherwise
*/ */
fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS /* SY --> */, group: Int = LibraryGroup.BY_DEFAULT, groupExtra: String? = null /* SY <-- */): Boolean { fun start(context: Context, category: Category? = null, target: Target = Target.CHAPTERS /* SY --> */, group: Int = LibraryGroup.BY_DEFAULT, groupExtra: String? = null /* SY <-- */): Boolean {
if (!isRunning(context)) { return if (!isRunning(context)) {
val intent = Intent(context, LibraryUpdateService::class.java).apply { val intent = Intent(context, LibraryUpdateService::class.java).apply {
putExtra(KEY_TARGET, target) putExtra(KEY_TARGET, target)
category?.let { putExtra(KEY_CATEGORY, it.id) } category?.let { putExtra(KEY_CATEGORY, it.id) }
@@ -158,10 +164,11 @@ class LibraryUpdateService(
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
return true true
} else {
instance?.addMangaToQueue(category?.id ?: -1, group, groupExtra, target)
false
} }
return false
} }
/** /**
@@ -198,6 +205,9 @@ class LibraryUpdateService(
if (wakeLock.isHeld) { if (wakeLock.isHeld) {
wakeLock.release() wakeLock.release()
} }
if (instance == this) {
instance = null
}
super.onDestroy() super.onDestroy()
} }
@@ -221,23 +231,27 @@ class LibraryUpdateService(
val target = intent.getSerializableExtra(KEY_TARGET) as? Target val target = intent.getSerializableExtra(KEY_TARGET) as? Target
?: return START_NOT_STICKY ?: return START_NOT_STICKY
// Unsubscribe from any previous subscription if needed. instance = this
// Unsubscribe from any previous subscription if needed
updateJob?.cancel() updateJob?.cancel()
// Update favorite manga. Destroy service when completed or in case of an error. // Update favorite manga
val selectedScheme = preferences.libraryUpdatePrioritization().get() val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
val mangaList = getMangaToUpdate(intent, target) val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
.sortedWith(rankingScheme[selectedScheme]) 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 -> val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception) Timber.e(exception)
stopSelf(startId) stopSelf(startId)
} }
updateJob = ioScope.launch(handler) { updateJob = ioScope.launch(handler) {
when (target) { when (target) {
Target.CHAPTERS -> updateChapterList(mangaList) Target.CHAPTERS -> updateChapterList()
Target.COVERS -> updateCovers(mangaList) Target.COVERS -> updateCovers()
Target.TRACKING -> updateTrackings(mangaList) Target.TRACKING -> updateTrackings()
// SY --> // SY -->
Target.SYNC_FOLLOWS -> syncFollows() Target.SYNC_FOLLOWS -> syncFollows()
Target.PUSH_FAVORITES -> pushFavorites() Target.PUSH_FAVORITES -> pushFavorites()
@@ -250,36 +264,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. * @param target the target to update.
* @return a list of manga to update
*/ */
fun getMangaToUpdate(intent: Intent, target: Target): List<LibraryManga> { fun addMangaToQueue(categoryId: Int, group: Int, groupExtra: String?, target: Target) {
val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) val libraryManga = db.getLibraryMangas().executeAsBlocking()
// SY --> // SY -->
val group = intent.getIntExtra(KEY_GROUP, LibraryGroup.BY_DEFAULT)
val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get() val groupLibraryUpdateType = preferences.groupLibraryUpdateType().get()
// SY <-- // SY <--
var listToUpdate = if (categoryId != -1) { var listToUpdate = if (categoryId != -1) {
db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } libraryManga.filter { it.category == categoryId }
// SY --> // SY -->
} else if (group == LibraryGroup.BY_DEFAULT || groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.GLOBAL || (groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)) { } else if (group == LibraryGroup.BY_DEFAULT || groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.GLOBAL || (groupLibraryUpdateType == PreferenceValues.GroupLibraryMode.ALL_BUT_UNGROUPED && group == LibraryGroup.UNGROUPED)) {
val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
if (categoriesToUpdate.isNotEmpty()) { val listToInclude = if (categoriesToUpdate.isNotEmpty()) {
db.getLibraryMangas().executeAsBlocking() libraryManga.filter { it.category in categoriesToUpdate }
.filter { it.category in categoriesToUpdate }
.distinctBy { it.id }
} else { } 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 { } else {
val libraryManga = db.getLibraryMangas().executeAsBlocking().distinctBy { it.id }
when (group) { when (group) {
LibraryGroup.BY_TRACK_STATUS -> { LibraryGroup.BY_TRACK_STATUS -> {
val trackingExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1 val trackingExtra = groupExtra?.toIntOrNull() ?: -1
libraryManga.filter { libraryManga.filter {
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged }
val status: String = run { val status: String = run {
@@ -298,12 +316,12 @@ class LibraryUpdateService(
} }
} }
LibraryGroup.BY_SOURCE -> { LibraryGroup.BY_SOURCE -> {
val sourceExtra = intent.getStringExtra(KEY_GROUP_EXTRA).nullIfBlank() val sourceExtra = groupExtra.nullIfBlank()
val source = sourceManager.getCatalogueSources().find { it.name == sourceExtra } val source = sourceManager.getCatalogueSources().find { it.name == sourceExtra }
if (source != null) libraryManga.filter { it.source == source.id } else emptyList() if (source != null) libraryManga.filter { it.source == source.id } else emptyList()
} }
LibraryGroup.BY_STATUS -> { LibraryGroup.BY_STATUS -> {
val statusExtra = intent.getStringExtra(KEY_GROUP_EXTRA)?.toIntOrNull() ?: -1 val statusExtra = groupExtra?.toIntOrNull() ?: -1
libraryManga.filter { libraryManga.filter {
it.status == statusExtra it.status == statusExtra
} }
@@ -314,10 +332,13 @@ class LibraryUpdateService(
// SY <-- // SY <--
} }
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) { 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,12 +350,13 @@ class LibraryUpdateService(
* @param mangaToUpdate the list to update * @param mangaToUpdate the list to update
* @return an observable delivering the progress of each update. * @return an observable delivering the progress of each update.
*/ */
suspend fun updateChapterList(mangaToUpdate: List<LibraryManga>) { suspend fun updateChapterList() {
val semaphore = Semaphore(5) val semaphore = Semaphore(5)
val progressCount = AtomicInteger(0) val progressCount = AtomicInteger(0)
val newUpdates = mutableListOf<Pair<LibraryManga, Array<Chapter>>>() val newUpdates = mutableListOf<Pair<LibraryManga, Array<Chapter>>>()
val failedUpdates = mutableListOf<Pair<Manga, String?>>() val failedUpdates = mutableListOf<Pair<Manga, String?>>()
var hasDownloads = false var hasDownloads = false
val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
withIOContext { withIOContext {
mangaToUpdate.groupBy { it.source } mangaToUpdate.groupBy { it.source }
@@ -373,6 +395,10 @@ class LibraryUpdateService(
} }
failedUpdates.add(manga to errorMessage) failedUpdates.add(manga to errorMessage)
} }
if (preferences.autoUpdateTrackers()) {
updateTrackings(manga, loggedServices)
}
} }
} }
} }
@@ -442,7 +468,7 @@ class LibraryUpdateService(
Timber.e(exception) Timber.e(exception)
} }
ioScope.launch(handler) { ioScope.launch(handler) {
if (source is MangaDex && trackManager.mdList.isLogged) { if (source.isMdBasedSource() && trackManager.mdList.isLogged) {
val tracks = db.getTracks(manga).executeOnIO() val tracks = db.getTracks(manga).executeOnIO()
if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) { if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) {
var track = trackManager.mdList.createInitialTracker(manga) var track = trackManager.mdList.createInitialTracker(manga)
@@ -463,7 +489,7 @@ class LibraryUpdateService(
return syncChaptersWithSource(db, chapters, manga, source) return syncChaptersWithSource(db, chapters, manga, source)
} }
private suspend fun updateCovers(mangaToUpdate: List<LibraryManga>) { private suspend fun updateCovers() {
var progressCount = 0 var progressCount = 0
mangaToUpdate.forEach { manga -> mangaToUpdate.forEach { manga ->
@@ -489,6 +515,7 @@ class LibraryUpdateService(
} }
} }
coverCache.clearMemoryCache()
notifier.cancelProgressNotification() notifier.cancelProgressNotification()
} }
@@ -496,7 +523,7 @@ class LibraryUpdateService(
* Method that updates the metadata of the connected tracking services. It's called in a * 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. * 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 var progressCount = 0
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged }
@@ -509,27 +536,35 @@ class LibraryUpdateService(
notifier.showProgressNotification(manga, progressCount++, mangaToUpdate.size) notifier.showProgressNotification(manga, progressCount++, mangaToUpdate.size)
// Update the tracking details. // Update the tracking details.
db.getTracks(manga).executeAsBlocking() updateTrackings(manga, loggedServices)
.map { track -> }
supervisorScope {
async { notifier.cancelProgressNotification()
val service = trackManager.getService(track.sync_id) }
if (service != null && service in loggedServices) {
try { private suspend fun updateTrackings(manga: LibraryManga, loggedServices: List<TrackService>) {
val updatedTrack = service.refresh(track) db.getTracks(manga).executeAsBlocking()
db.insertTrack(updatedTrack).executeAsBlocking() .map { track ->
} catch (e: Throwable) { supervisorScope {
// Ignore errors and continue async {
Timber.e(e) val service = trackManager.getService(track.sync_id)
if (service != null && service in loggedServices) {
try {
val updatedTrack = service.refresh(track)
db.insertTrack(updatedTrack).executeAsBlocking()
if (service is UnattendedTrackService) {
syncChaptersWithTrackServiceTwoWay(db, db.getChapters(manga).executeAsBlocking(), track, service)
} }
} catch (e: Throwable) {
// Ignore errors and continue
Timber.e(e)
} }
} }
} }
} }
.awaitAll() }
} .awaitAll()
notifier.cancelProgressNotification()
} }
// SY --> // SY -->
@@ -539,11 +574,12 @@ class LibraryUpdateService(
private suspend fun syncFollows() { private suspend fun syncFollows() {
val count = AtomicInteger(0) val count = AtomicInteger(0)
val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return val mangaDex = MdUtil.getEnabledMangaDex(preferences, sourceManager) ?: return
val syncFollowStatusInts = preferences.mangadexSyncToLibraryIndexes().get().map { it.toInt() }
val size: Int val size: Int
mangaDex.fetchAllFollows(true) mangaDex.fetchAllFollows()
.filter { (_, metadata) -> .filter { (_, metadata) ->
metadata.follow_status == FollowStatus.RE_READING.int || metadata.follow_status == FollowStatus.READING.int syncFollowStatusInts.contains(metadata.followStatus)
} }
.also { size = it.size } .also { size = it.size }
.forEach { (networkManga, metadata) -> .forEach { (networkManga, metadata) ->
@@ -569,7 +605,7 @@ class LibraryUpdateService(
val id = db.insertManga(dbManga).executeOnIO().insertedId() val id = db.insertManga(dbManga).executeOnIO().insertedId()
if (id != null) { if (id != null) {
metadata.mangaId = id metadata.mangaId = id
db.insertFlatMetadata(metadata.flatten()).await() db.insertFlatMetadataAsync(metadata.flatten()).await()
} }
} }
@@ -26,7 +26,6 @@ import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.md.similar.SimilarUpdateService
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@@ -59,22 +58,22 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_SHARE_IMAGE -> ACTION_SHARE_IMAGE ->
shareImage( shareImage(
context, context,
intent.getStringExtra(EXTRA_FILE_LOCATION), intent.getStringExtra(EXTRA_FILE_LOCATION)!!,
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Delete image from path and dismiss notification // Delete image from path and dismiss notification
ACTION_DELETE_IMAGE -> ACTION_DELETE_IMAGE ->
deleteImage( deleteImage(
context, context,
intent.getStringExtra(EXTRA_FILE_LOCATION), intent.getStringExtra(EXTRA_FILE_LOCATION)!!,
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> ACTION_SHARE_BACKUP ->
shareFile( shareFile(
context, context,
intent.getParcelableExtra(EXTRA_URI), intent.getParcelableExtra(EXTRA_URI)!!,
if (intent.getBooleanExtra(EXTRA_IS_LEGACY_BACKUP, false)) "application/json" else "application/octet-stream+gzip", "application/x-protobuf+gzip",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
ACTION_CANCEL_RESTORE -> cancelRestore( ACTION_CANCEL_RESTORE -> cancelRestore(
@@ -107,13 +106,10 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_SHARE_CRASH_LOG -> ACTION_SHARE_CRASH_LOG ->
shareFile( shareFile(
context, context,
intent.getParcelableExtra(EXTRA_URI), intent.getParcelableExtra(EXTRA_URI)!!,
"text/plain", "text/plain",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
) )
// SY -->
ACTION_CANCEL_SIMILAR_UPDATE -> cancelSimilarUpdate(context)
// SY <--
} }
} }
@@ -255,18 +251,6 @@ class NotificationReceiver : BroadcastReceiver() {
} }
} }
// SY -->
/**
* Method called when user wants to stop a similar manga update
*
* @param context context of application
*/
private fun cancelSimilarUpdate(context: Context) {
SimilarUpdateService.stop(context)
Handler().post { dismissNotification(context, Notifications.ID_SIMILAR_PROGRESS) }
}
// SY <--
companion object { companion object {
private const val NAME = "NotificationReceiver" private const val NAME = "NotificationReceiver"
@@ -297,12 +281,6 @@ class NotificationReceiver : BroadcastReceiver() {
private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID" private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID"
private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID" private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID"
private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL" private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
private const val EXTRA_IS_LEGACY_BACKUP = "$ID.$NAME.EXTRA_IS_LEGACY_BACKUP"
// Sy -->
// Called to cancel similar manga update.
private const val ACTION_CANCEL_SIMILAR_UPDATE = "$ID.$NAME.CANCEL_SIMILAR_UPDATE"
// SY <--
/** /**
* Returns a [PendingIntent] that resumes the download of a chapter * Returns a [PendingIntent] that resumes the download of a chapter
@@ -515,11 +493,10 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification * @param notificationId id of notification
* @return [PendingIntent] * @return [PendingIntent]
*/ */
internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, isLegacyFormat: Boolean, notificationId: Int): PendingIntent { internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply { val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_SHARE_BACKUP action = ACTION_SHARE_BACKUP
putExtra(EXTRA_URI, uri) putExtra(EXTRA_URI, uri)
putExtra(EXTRA_IS_LEGACY_BACKUP, isLegacyFormat)
putExtra(EXTRA_NOTIFICATION_ID, notificationId) putExtra(EXTRA_NOTIFICATION_ID, notificationId)
} }
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
@@ -572,20 +549,5 @@ class NotificationReceiver : BroadcastReceiver() {
} }
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
// SY -->
/**
* Returns [PendingIntent] that starts a service which stops the similar update
*
* @param context context of application
* @return [PendingIntent]
*/
internal fun cancelSimilarUpdatePendingBroadcast(context: Context): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_CANCEL_SIMILAR_UPDATE
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
// SY <--
} }
} }
@@ -68,14 +68,11 @@ object Notifications {
const val CHANNEL_CRASH_LOGS = "crash_logs_channel" const val CHANNEL_CRASH_LOGS = "crash_logs_channel"
const val ID_CRASH_LOGS = -601 const val ID_CRASH_LOGS = -601
// SY -->
/** /**
* Notification channel and ids used for backup and restore. * Notification channel used for Incognito Mode
*/ */
const val CHANNEL_SIMILAR = "similar_channel" const val CHANNEL_INCOGNITO_MODE = "incognito_mode_channel"
const val ID_SIMILAR_PROGRESS = -901 const val ID_INCOGNITO_MODE = -701
const val ID_SIMILAR_COMPLETE = -902
// SY <--
private val deprecatedChannels = listOf( private val deprecatedChannels = listOf(
"downloader_channel", "downloader_channel",
@@ -165,12 +162,10 @@ object Notifications {
NotificationManager.IMPORTANCE_HIGH NotificationManager.IMPORTANCE_HIGH
), ),
NotificationChannel( NotificationChannel(
CHANNEL_SIMILAR, CHANNEL_INCOGNITO_MODE,
context.getString(R.string.similar_manga), context.getString(R.string.pref_incognito_mode),
NotificationManager.IMPORTANCE_LOW NotificationManager.IMPORTANCE_LOW
).apply { )
setShowBadge(false)
}
).forEach(context.notificationManager::createNotificationChannel) ).forEach(context.notificationManager::createNotificationChannel)
// Delete old notification channels // Delete old notification channels
@@ -13,17 +13,25 @@ object PreferenceKeys {
const val confirmExit = "pref_confirm_exit" const val confirmExit = "pref_confirm_exit"
const val hideBottomBar = "pref_hide_bottom_bar_on_scroll" const val hideBottomBarOnScroll = "pref_hide_bottom_bar_on_scroll"
const val rotation = "pref_rotation_type_key" const val showSideNavOnBottom = "pref_show_side_nav_on_bottom"
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 doubleTapAnimationSpeed = "pref_double_tap_anim_speed"
const val showPageNumber = "pref_show_page_number_key" 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" const val showReadingMode = "pref_show_reading_mode"
@@ -45,7 +53,11 @@ object PreferenceKeys {
const val colorFilterMode = "color_filter_mode" const val colorFilterMode = "color_filter_mode"
const val defaultViewer = "pref_default_viewer_key" const val grayscale = "pref_grayscale"
const val defaultReadingMode = "pref_default_reading_mode_key"
const val defaultOrientationType = "pref_default_orientation_type_key"
const val imageScaleType = "pref_image_scale_type_key" const val imageScaleType = "pref_image_scale_type_key"
@@ -73,6 +85,10 @@ object PreferenceKeys {
const val navigationModeWebtoon = "reader_navigation_mode_webtoon" 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 webtoonSidePadding = "webtoon_side_padding"
const val portraitColumns = "pref_library_columns_portrait_key" const val portraitColumns = "pref_library_columns_portrait_key"
@@ -85,6 +101,8 @@ object PreferenceKeys {
const val autoUpdateTrack = "pref_auto_update_manga_sync_key" const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
const val autoAddTrack = "pref_auto_add_track_key"
const val lastUsedSource = "last_catalogue_source" const val lastUsedSource = "last_catalogue_source"
const val lastUsedCategory = "last_used_category" const val lastUsedCategory = "last_used_category"
@@ -99,6 +117,8 @@ object PreferenceKeys {
const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key" const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key"
const val folderPerManga = "create_folder_per_manga"
const val numberOfBackups = "backup_slots" const val numberOfBackups = "backup_slots"
const val backupInterval = "backup_interval" const val backupInterval = "backup_interval"
@@ -114,6 +134,7 @@ object PreferenceKeys {
const val libraryUpdateRestriction = "library_update_restriction" const val libraryUpdateRestriction = "library_update_restriction"
const val libraryUpdateCategories = "library_update_categories" const val libraryUpdateCategories = "library_update_categories"
const val libraryUpdateCategoriesExclude = "library_update_categories_exclude"
const val libraryUpdatePrioritization = "library_update_prioritization" const val libraryUpdatePrioritization = "library_update_prioritization"
@@ -141,7 +162,7 @@ object PreferenceKeys {
const val startScreen = "start_screen" const val startScreen = "start_screen"
const val useBiometricLock = "use_biometric_lock" const val useAuthenticator = "use_biometric_lock"
const val lockAppAfter = "lock_app_after" const val lockAppAfter = "lock_app_after"
@@ -153,11 +174,14 @@ object PreferenceKeys {
const val autoUpdateMetadata = "auto_update_metadata" const val autoUpdateMetadata = "auto_update_metadata"
const val autoUpdateTrackers = "auto_update_trackers"
const val showLibraryUpdateErrors = "show_library_update_errors" const val showLibraryUpdateErrors = "show_library_update_errors"
const val downloadNew = "download_new" const val downloadNew = "download_new"
const val downloadNewCategories = "download_new_categories" const val downloadNewCategories = "download_new_categories"
const val downloadNewCategoriesExclude = "download_new_categories_exclude"
const val libraryDisplayMode = "pref_display_mode_library" const val libraryDisplayMode = "pref_display_mode_library"
@@ -175,6 +199,8 @@ object PreferenceKeys {
const val unreadBadge = "display_unread_badge" const val unreadBadge = "display_unread_badge"
const val localBadge = "display_local_badge"
const val categoryTabs = "display_category_tabs" const val categoryTabs = "display_category_tabs"
const val categoryNumberOfItems = "display_number_of_items" const val categoryNumberOfItems = "display_number_of_items"
@@ -183,7 +209,7 @@ object PreferenceKeys {
const val searchPinnedSourcesOnly = "search_pinned_sources_only" 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" const val defaultChapterFilterByRead = "default_chapter_filter_by_read"
@@ -307,11 +333,7 @@ object PreferenceKeys {
const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers" const val mangaDexForceLatestCovers = "manga_dex_force_latest_covers"
const val mangadexSimilarEnabled = "pref_related_show_tab_key" const val mangadexSyncToLibraryIndexes = "pref_mangadex_sync_to_library_indexes"
const val mangadexSimilarUpdateInterval = "related_update_interval"
const val mangadexSimilarOnlyOverWifi = "pref_simular_only_over_wifi_key"
const val preferredMangaDexId = "preferred_mangaDex_id" const val preferredMangaDexId = "preferred_mangaDex_id"
@@ -335,15 +357,31 @@ object PreferenceKeys {
const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders" const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders"
const val biometricTimeRanges = "biometric_time_ranges" const val authenticatorTimeRanges = "biometric_time_ranges"
const val sortTagsForLibrary = "sort_tags_for_library" const val sortTagsForLibrary = "sort_tags_for_library"
const val createLegacyBackup = "create_legacy_backup"
const val dontDeleteFromCategories = "dont_delete_from_categories" const val dontDeleteFromCategories = "dont_delete_from_categories"
const val extensionRepos = "extension_repos" 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"
const val readerBottomButtons = "reader_bottom_buttons"
const val bottomBarLabels = "pref_show_bottom_bar_labels"
const val hideUpdatesButton = "pref_hide_updates_button"
const val hideHistoryButton = "pref_hide_history_button"
const val pageLayout = "page_layout"
const val invertDoublePages = "invert_double_pages"
} }
@@ -1,10 +1,15 @@
package eu.kanade.tachiyomi.data.preference package eu.kanade.tachiyomi.data.preference
const val UNMETERED_NETWORK = "wifi"
const val CHARGING = "ac"
/** /**
* This class stores the values for the preferences in the application. * This class stores the values for the preferences in the application.
*/ */
object PreferenceValues { object PreferenceValues {
/* ktlint-disable experimental:enum-entry-name-case */
// Keys are lowercase to match legacy string values // Keys are lowercase to match legacy string values
enum class ThemeMode { enum class ThemeMode {
light, light,
@@ -16,17 +21,23 @@ object PreferenceValues {
enum class LightThemeVariant { enum class LightThemeVariant {
default, default,
blue, blue,
strawberrydaiquiri,
} }
// Keys are lowercase to match legacy string values // Keys are lowercase to match legacy string values
enum class DarkThemeVariant { enum class DarkThemeVariant {
default, default,
blue, blue,
amoled, greenapple,
red,
midnightdusk, midnightdusk,
amoled,
hotpink,
amoledblue,
red,
} }
/* ktlint-enable experimental:enum-entry-name-case */
enum class DisplayMode { enum class DisplayMode {
COMPACT_GRID, COMPACT_GRID,
COMFORTABLE_GRID, COMFORTABLE_GRID,
@@ -12,6 +12,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode import eu.kanade.tachiyomi.data.preference.PreferenceValues.DisplayMode
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@@ -22,7 +26,7 @@ import java.util.Locale
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values import eu.kanade.tachiyomi.data.preference.PreferenceValues 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()) block(get())
return asFlow() return asFlow()
.onEach { block(it) } .onEach { block(it) }
@@ -36,6 +40,11 @@ operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
set(get() - item) set(get() - item)
} }
fun Preference<Boolean>.toggle(): Boolean {
set(!get())
return get()
}
class PreferencesHelper(val context: Context) { class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@@ -57,9 +66,11 @@ class PreferencesHelper(val context: Context) {
fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false) fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false)
fun hideBottomBar() = flowPrefs.getBoolean(Keys.hideBottomBar, true) fun hideBottomBarOnScroll() = flowPrefs.getBoolean(Keys.hideBottomBarOnScroll, true)
fun useBiometricLock() = flowPrefs.getBoolean(Keys.useBiometricLock, false) fun showSideNavOnBottom() = flowPrefs.getBoolean(Keys.showSideNavOnBottom, false)
fun useAuthenticator() = flowPrefs.getBoolean(Keys.useAuthenticator, false)
fun lockAppAfter() = flowPrefs.getInt(Keys.lockAppAfter, 0) fun lockAppAfter() = flowPrefs.getInt(Keys.lockAppAfter, 0)
@@ -71,9 +82,9 @@ class PreferencesHelper(val context: Context) {
fun autoUpdateMetadata() = prefs.getBoolean(Keys.autoUpdateMetadata, false) fun autoUpdateMetadata() = prefs.getBoolean(Keys.autoUpdateMetadata, false)
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false) fun autoUpdateTrackers() = prefs.getBoolean(Keys.autoUpdateTrackers, false)
fun clear() = prefs.edit { clear() } fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false)
fun themeMode() = flowPrefs.getEnum(Keys.themeMode, Values.ThemeMode.system) fun themeMode() = flowPrefs.getEnum(Keys.themeMode, Values.ThemeMode.system)
@@ -81,15 +92,21 @@ class PreferencesHelper(val context: Context) {
fun themeDark() = flowPrefs.getEnum(Keys.themeDark, Values.DarkThemeVariant.default) fun themeDark() = flowPrefs.getEnum(Keys.themeDark, Values.DarkThemeVariant.default)
fun rotation() = flowPrefs.getInt(Keys.rotation, 1) fun pageTransitionsPager() = flowPrefs.getBoolean(Keys.enableTransitionsPager, true)
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true) fun pageTransitionsWebtoon() = flowPrefs.getBoolean(Keys.enableTransitionsWebtoon, true)
fun doubleTapAnimSpeed() = flowPrefs.getInt(Keys.doubleTapAnimationSpeed, 500) fun doubleTapAnimSpeed() = flowPrefs.getInt(Keys.doubleTapAnimationSpeed, 500)
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true) 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) fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
@@ -111,7 +128,11 @@ class PreferencesHelper(val context: Context) {
fun colorFilterMode() = flowPrefs.getInt(Keys.colorFilterMode, 0) fun colorFilterMode() = flowPrefs.getInt(Keys.colorFilterMode, 0)
fun defaultViewer() = prefs.getInt(Keys.defaultViewer, 2) fun grayscale() = flowPrefs.getBoolean(Keys.grayscale, false)
fun defaultReadingMode() = prefs.getInt(Keys.defaultReadingMode, ReadingModeType.RIGHT_TO_LEFT.flagValue)
fun defaultOrientationType() = prefs.getInt(Keys.defaultOrientationType, OrientationType.FREE.flagValue)
fun imageScaleType() = flowPrefs.getInt(Keys.imageScaleType, 1) fun imageScaleType() = flowPrefs.getInt(Keys.imageScaleType, 1)
@@ -143,6 +164,10 @@ class PreferencesHelper(val context: Context) {
fun navigationModeWebtoon() = flowPrefs.getInt(Keys.navigationModeWebtoon, 0) 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 portraitColumns() = flowPrefs.getInt(Keys.portraitColumns, 0)
fun landscapeColumns() = flowPrefs.getInt(Keys.landscapeColumns, 0) fun landscapeColumns() = flowPrefs.getInt(Keys.landscapeColumns, 0)
@@ -153,6 +178,8 @@ class PreferencesHelper(val context: Context) {
fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true) fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
fun autoAddTrack() = prefs.getBoolean(Keys.autoAddTrack, true)
fun lastUsedSource() = flowPrefs.getLong(Keys.lastUsedSource, -1) fun lastUsedSource() = flowPrefs.getLong(Keys.lastUsedSource, -1)
fun lastUsedCategory() = flowPrefs.getInt(Keys.lastUsedCategory, 0) fun lastUsedCategory() = flowPrefs.getInt(Keys.lastUsedCategory, 0)
@@ -189,6 +216,8 @@ class PreferencesHelper(val context: Context) {
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true) fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt(Keys.numberOfBackups, 1) fun numberOfBackups() = flowPrefs.getInt(Keys.numberOfBackups, 1)
fun backupInterval() = flowPrefs.getInt(Keys.backupInterval, 0) fun backupInterval() = flowPrefs.getInt(Keys.backupInterval, 0)
@@ -201,9 +230,10 @@ class PreferencesHelper(val context: Context) {
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24) fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
fun libraryUpdateRestriction() = prefs.getStringSet(Keys.libraryUpdateRestriction, setOf("wifi")) fun libraryUpdateRestriction() = flowPrefs.getStringSet(Keys.libraryUpdateRestriction, setOf(UNMETERED_NETWORK))
fun libraryUpdateCategories() = flowPrefs.getStringSet(Keys.libraryUpdateCategories, emptySet()) fun libraryUpdateCategories() = flowPrefs.getStringSet(Keys.libraryUpdateCategories, emptySet())
fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet(Keys.libraryUpdateCategoriesExclude, emptySet())
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0) fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
@@ -211,6 +241,8 @@ class PreferencesHelper(val context: Context) {
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false) fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
fun localBadge() = flowPrefs.getBoolean(Keys.localBadge, true)
fun downloadedOnly() = flowPrefs.getBoolean(Keys.downloadedOnly, false) fun downloadedOnly() = flowPrefs.getBoolean(Keys.downloadedOnly, false)
fun unreadBadge() = flowPrefs.getBoolean(Keys.unreadBadge, true) fun unreadBadge() = flowPrefs.getBoolean(Keys.unreadBadge, true)
@@ -254,6 +286,7 @@ class PreferencesHelper(val context: Context) {
fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false) fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false)
fun downloadNewCategories() = flowPrefs.getStringSet(Keys.downloadNewCategories, emptySet()) fun downloadNewCategories() = flowPrefs.getStringSet(Keys.downloadNewCategories, emptySet())
fun downloadNewCategoriesExclude() = flowPrefs.getStringSet(Keys.downloadNewCategoriesExclude, emptySet())
fun lang() = prefs.getString(Keys.lang, "") fun lang() = prefs.getString(Keys.lang, "")
@@ -267,7 +300,7 @@ class PreferencesHelper(val context: Context) {
fun trustedSignatures() = flowPrefs.getStringSet("trusted_signatures", emptySet()) 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", "") fun lastSearchQuerySearchSettings() = flowPrefs.getString("last_search_query", "")
@@ -277,16 +310,14 @@ class PreferencesHelper(val context: Context) {
fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL) fun filterChapterByBookmarked() = prefs.getInt(Keys.defaultChapterFilterByBookmarked, Manga.SHOW_ALL)
fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.SORTING_SOURCE) fun sortChapterBySourceOrNumber() = prefs.getInt(Keys.defaultChapterSortBySourceOrNumber, Manga.CHAPTER_SORTING_SOURCE)
fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.DISPLAY_NAME) fun displayChapterByNameOrNumber() = prefs.getInt(Keys.defaultChapterDisplayByNameOrNumber, Manga.CHAPTER_DISPLAY_NAME)
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.SORT_DESC) fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC)
fun incognitoMode() = flowPrefs.getBoolean(Keys.incognitoMode, false) fun incognitoMode() = flowPrefs.getBoolean(Keys.incognitoMode, false)
fun createLegacyBackup() = flowPrefs.getBoolean(Keys.createLegacyBackup, true)
fun setChapterSettingsDefault(manga: Manga) { fun setChapterSettingsDefault(manga: Manga) {
prefs.edit { prefs.edit {
putInt(Keys.defaultChapterFilterByRead, manga.readFilter) putInt(Keys.defaultChapterFilterByRead, manga.readFilter)
@@ -294,7 +325,7 @@ class PreferencesHelper(val context: Context) {
putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter) putInt(Keys.defaultChapterFilterByBookmarked, manga.bookmarkedFilter)
putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting) putInt(Keys.defaultChapterSortBySourceOrNumber, manga.sorting)
putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode) putInt(Keys.defaultChapterDisplayByNameOrNumber, manga.displayMode)
putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) Manga.SORT_DESC else Manga.SORT_ASC) putInt(Keys.defaultChapterSortByAscendingOrDescending, if (manga.sortDescending()) Manga.CHAPTER_SORT_DESC else Manga.CHAPTER_SORT_ASC)
} }
} }
@@ -419,13 +450,7 @@ class PreferencesHelper(val context: Context) {
fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0") fun preferredMangaDexId() = flowPrefs.getString(Keys.preferredMangaDexId, "0")
fun mangadexSimilarEnabled() = flowPrefs.getBoolean(Keys.mangadexSimilarEnabled, false) fun mangadexSyncToLibraryIndexes() = flowPrefs.getStringSet(Keys.mangadexSyncToLibraryIndexes, emptySet())
fun shownMangaDexSimilarAskDialog() = flowPrefs.getBoolean("shown_similar_ask_dialog", false)
fun mangadexSimilarOnlyOverWifi() = flowPrefs.getBoolean(Keys.mangadexSimilarOnlyOverWifi, true)
fun mangadexSimilarUpdateInterval() = flowPrefs.getInt(Keys.mangadexSimilarUpdateInterval, 2)
fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false) fun dataSaver() = flowPrefs.getBoolean(Keys.dataSaver, false)
@@ -447,7 +472,7 @@ class PreferencesHelper(val context: Context) {
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false) fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false)
fun biometricTimeRanges() = flowPrefs.getStringSet(Keys.biometricTimeRanges, mutableSetOf()) fun authenticatorTimeRanges() = flowPrefs.getStringSet(Keys.authenticatorTimeRanges, mutableSetOf())
fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf()) fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf())
@@ -455,5 +480,23 @@ class PreferencesHelper(val context: Context) {
fun extensionRepos() = flowPrefs.getStringSet(Keys.extensionRepos, emptySet()) 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)
fun readerBottomButtons() = flowPrefs.getStringSet(Keys.readerBottomButtons, ReaderBottomButton.BUTTONS_DEFAULTS)
fun bottomBarLabels() = flowPrefs.getBoolean(Keys.bottomBarLabels, true)
fun hideUpdatesButton() = flowPrefs.getBoolean(Keys.hideUpdatesButton, false)
fun hideHistoryButton() = flowPrefs.getBoolean(Keys.hideHistoryButton, false)
fun pageLayout() = flowPrefs.getInt(Keys.pageLayout, PagerConfig.PageLayout.SINGLE_PAGE)
fun invertDoublePages() = flowPrefs.getBoolean(Keys.invertDoublePages, false)
} }
@@ -0,0 +1,8 @@
package eu.kanade.tachiyomi.data.track
/**
* A TrackService that doesn't need explicit login.
*/
interface NoLoginTrackService {
fun loginNoop()
}
@@ -4,6 +4,7 @@ import android.content.Context
import eu.kanade.tachiyomi.data.track.anilist.Anilist import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.data.track.bangumi.Bangumi import eu.kanade.tachiyomi.data.track.bangumi.Bangumi
import eu.kanade.tachiyomi.data.track.kitsu.Kitsu import eu.kanade.tachiyomi.data.track.kitsu.Kitsu
import eu.kanade.tachiyomi.data.track.komga.Komga
import eu.kanade.tachiyomi.data.track.mdlist.MdList import eu.kanade.tachiyomi.data.track.mdlist.MdList
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList
import eu.kanade.tachiyomi.data.track.shikimori.Shikimori import eu.kanade.tachiyomi.data.track.shikimori.Shikimori
@@ -16,9 +17,10 @@ class TrackManager(context: Context) {
const val KITSU = 3 const val KITSU = 3
const val SHIKIMORI = 4 const val SHIKIMORI = 4
const val BANGUMI = 5 const val BANGUMI = 5
const val KOMGA = 6
// SY --> Mangadex from Neko // SY --> Mangadex from Neko
const val MDLIST = 6 const val MDLIST = 60
// SY <-- // SY <--
// SY --> // SY -->
@@ -44,7 +46,9 @@ class TrackManager(context: Context) {
val bangumi = Bangumi(context, BANGUMI) val bangumi = Bangumi(context, BANGUMI)
val services = listOf(mdList, myAnimeList, aniList, kitsu, shikimori, bangumi) val komga = Komga(context, KOMGA)
val services = listOf(mdList, myAnimeList, aniList, kitsu, shikimori, bangumi, komga)
fun getService(id: Int) = services.find { it.id == id } fun getService(id: Int) = services.find { it.id == id }
@@ -46,8 +46,6 @@ abstract class TrackService(val id: Int) {
abstract fun displayScore(track: Track): String abstract fun displayScore(track: Track): String
abstract suspend fun add(track: Track): Track
abstract suspend fun update(track: Track): Track abstract suspend fun update(track: Track): Track
abstract suspend fun bind(track: Track): Track abstract suspend fun bind(track: Track): Track
@@ -0,0 +1,21 @@
package eu.kanade.tachiyomi.data.track
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
/**
* An Unattended Track Service will never prompt the user to match a manga with the remote.
* It is expected that such Track Sercice can only work with specific sources and unique IDs.
*/
interface UnattendedTrackService {
/**
* This TrackService will only work with the sources that are accepted by this filter function.
*/
fun accept(source: Source): Boolean
/**
* match is similar to TrackService.search, but only return zero or one match.
*/
suspend fun match(manga: Manga): TrackSearch?
}
@@ -35,6 +35,8 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
private val api by lazy { AnilistApi(client, interceptor) } private val api by lazy { AnilistApi(client, interceptor) }
override val supportsReadingDates: Boolean = true
private val scorePreference = preferences.anilistScoreType() private val scorePreference = preferences.anilistScoreType()
init { init {
@@ -128,7 +130,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
} }
} }
override suspend fun add(track: Track): Track { private suspend fun add(track: Track): Track {
return api.addLibManga(track) return api.addLibManga(track)
} }
@@ -2,13 +2,18 @@ package eu.kanade.tachiyomi.data.track.anilist
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri 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.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptor
import eu.kanade.tachiyomi.network.jsonMime import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull import kotlinx.serialization.json.contentOrNull
@@ -23,15 +28,18 @@ import kotlinx.serialization.json.putJsonObject
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Calendar import java.util.Calendar
import java.util.concurrent.TimeUnit.MINUTES
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build() private val authClient = client.newBuilder()
.addInterceptor(interceptor)
.addInterceptor(RateLimitInterceptor(85, 1, MINUTES))
.build()
suspend fun addLibManga(track: Track): Track { suspend fun addLibManga(track: Track): Track {
return withIOContext { return withIOContext {
val query = val query = """
"""
|mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) { |mutation AddManga(${'$'}mangaId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus) {
|SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) { |SaveMediaListEntry (mediaId: ${'$'}mangaId, progress: ${'$'}progress, status: ${'$'}status) {
| id | id
@@ -65,10 +73,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun updateLibManga(track: Track): Track { suspend fun updateLibManga(track: Track): Track {
return withIOContext { return withIOContext {
val query = val query = """
""" |mutation UpdateManga(
|mutation UpdateManga(${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}score: Int) { |${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus,
|SaveMediaListEntry (id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, scoreRaw: ${'$'}score) { |${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput
|) {
|SaveMediaListEntry(
|id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status,
|scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt
|) {
|id |id
|status |status
|progress |progress
@@ -82,6 +95,8 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("progress", track.last_chapter_read) put("progress", track.last_chapter_read)
put("status", track.toAnilistStatus()) put("status", track.toAnilistStatus())
put("score", track.score.toInt()) 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))) authClient.newCall(POST(apiUrl, body = payload.toString().toRequestBody(jsonMime)))
@@ -92,8 +107,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun search(search: String): List<TrackSearch> { suspend fun search(search: String): List<TrackSearch> {
return withIOContext { return withIOContext {
val query = val query = """
"""
|query Search(${'$'}query: String) { |query Search(${'$'}query: String) {
|Page (perPage: 50) { |Page (perPage: 50) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) { |media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
@@ -143,8 +157,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun findLibManga(track: Track, userid: Int): Track? { suspend fun findLibManga(track: Track, userid: Int): Track? {
return withIOContext { return withIOContext {
val query = val query = """
"""
|query (${'$'}id: Int!, ${'$'}manga_id: Int!) { |query (${'$'}id: Int!, ${'$'}manga_id: Int!) {
|Page { |Page {
|mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) { |mediaList(userId: ${'$'}id, type: MANGA, mediaId: ${'$'}manga_id) {
@@ -152,6 +165,16 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|status |status
|scoreRaw: score(format: POINT_100) |scoreRaw: score(format: POINT_100)
|progress |progress
|startedAt {
|year
|month
|day
|}
|completedAt {
|year
|month
|day
|}
|media { |media {
|id |id
|title { |title {
@@ -209,8 +232,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
suspend fun getCurrentUser(): Pair<Int, String> { suspend fun getCurrentUser(): Pair<Int, String> {
return withIOContext { return withIOContext {
val query = val query = """
"""
|query User { |query User {
|Viewer { |Viewer {
|id |id
@@ -243,21 +265,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
private fun jsonToALManga(struct: JsonObject): ALManga { 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( return ALManga(
struct["id"]!!.jsonPrimitive.int, struct["id"]!!.jsonPrimitive.int,
struct["title"]!!.jsonObject["romaji"]!!.jsonPrimitive.content, struct["title"]!!.jsonObject["romaji"]!!.jsonPrimitive.content,
@@ -265,7 +272,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
struct["description"]!!.jsonPrimitive.contentOrNull, struct["description"]!!.jsonPrimitive.contentOrNull,
struct["type"]!!.jsonPrimitive.content, struct["type"]!!.jsonPrimitive.content,
struct["status"]!!.jsonPrimitive.contentOrNull ?: "", struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
date, parseDate(struct, "startDate"),
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0 struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0
) )
} }
@@ -276,10 +283,44 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
struct["status"]!!.jsonPrimitive.content, struct["status"]!!.jsonPrimitive.content,
struct["scoreRaw"]!!.jsonPrimitive.int, struct["scoreRaw"]!!.jsonPrimitive.int,
struct["progress"]!!.jsonPrimitive.int, struct["progress"]!!.jsonPrimitive.int,
parseDate(struct, "startedAt"),
parseDate(struct, "completedAt"),
jsonToALManga(struct["media"]!!.jsonObject) 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 { companion object {
private const val clientId = "385" private const val clientId = "385"
private const val apiUrl = "https://graphql.anilist.co/" private const val apiUrl = "https://graphql.anilist.co/"
@@ -44,6 +44,8 @@ data class ALUserManga(
val list_status: String, val list_status: String,
val score_raw: Int, val score_raw: Int,
val chapters_read: Int, val chapters_read: Int,
val start_date_fuzzy: Long,
val completed_date_fuzzy: Long,
val manga: ALManga val manga: ALManga
) { ) {
@@ -51,6 +53,8 @@ data class ALUserManga(
media_id = manga.media_id media_id = manga.media_id
status = toTrackStatus() status = toTrackStatus()
score = score_raw.toFloat() score = score_raw.toFloat()
started_reading_date = start_date_fuzzy
finished_reading_date = completed_date_fuzzy
last_chapter_read = chapters_read last_chapter_read = chapters_read
library_id = this@ALUserManga.library_id library_id = this@ALUserManga.library_id
total_chapters = manga.total_chapters total_chapters = manga.total_chapters
@@ -31,7 +31,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString() return track.score.toInt().toString()
} }
override suspend fun add(track: Track): Track { private suspend fun add(track: Track): Track {
return api.addLibManga(track) return api.addLibManga(track)
} }
@@ -45,8 +45,10 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
return if (remoteTrack != null && statusTrack != null) { return if (remoteTrack != null && statusTrack != null) {
track.copyPersonalFrom(remoteTrack) track.copyPersonalFrom(remoteTrack)
track.library_id = remoteTrack.library_id track.library_id = remoteTrack.library_id
track.status = remoteTrack.status track.status = statusTrack.status
track.last_chapter_read = remoteTrack.last_chapter_read track.score = statusTrack.score
track.last_chapter_read = statusTrack.last_chapter_read
track.total_chapters = remoteTrack.total_chapters
refresh(track) refresh(track)
} else { } else {
// Set default fields if it's not found in the list // 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!!) track.copyPersonalFrom(remoteStatusTrack!!)
api.findLibManga(track)?.let { remoteTrack -> api.findLibManga(track)?.let { remoteTrack ->
track.total_chapters = remoteTrack.total_chapters track.total_chapters = remoteTrack.total_chapters
track.status = remoteTrack.status
} }
return track return track
} }
@@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
@@ -46,6 +47,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
return withIOContext { return withIOContext {
// read status update // read status update
val sbody = FormBody.Builder() val sbody = FormBody.Builder()
.add("rating", track.score.toInt().toString())
.add("status", track.toBangumiStatus()) .add("status", track.toBangumiStatus())
.build() .build()
authClient.newCall(POST("$apiUrl/collection/${track.media_id}/update", body = sbody)) 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 { 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 { return TrackSearch.create(TrackManager.BANGUMI).apply {
media_id = obj["id"]!!.jsonPrimitive.int media_id = obj["id"]!!.jsonPrimitive.int
title = obj["name_cn"]!!.jsonPrimitive.content title = obj["name_cn"]!!.jsonPrimitive.content
cover_url = obj["images"]!!.jsonObject["common"]!!.jsonPrimitive.content cover_url = coverUrl
summary = obj["name"]!!.jsonPrimitive.content summary = obj["name"]!!.jsonPrimitive.content
tracking_url = obj["url"]!!.jsonPrimitive.content tracking_url = obj["url"]!!.jsonPrimitive.content
total_chapters = totalChapters
} }
} }
@@ -119,14 +133,21 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
.build() .build()
// TODO: get user readed chapter here // TODO: get user readed chapter here
authClient.newCall(requestUserRead) var response = authClient.newCall(requestUserRead).await()
.await() var responseBody = response.body?.string().orEmpty()
.parseAs<Collection>() if (responseBody.isEmpty()) {
.let { throw Exception("Null Response")
}
if (responseBody.contains("\"code\":400")) {
null
} else {
json.decodeFromString<Collection>(responseBody).let {
track.status = it.status?.id!! track.status = it.status?.id!!
track.last_chapter_read = it.ep_status!! track.last_chapter_read = it.ep_status!!
track.score = it.rating!!
track track
} }
}
} }
} }
@@ -8,7 +8,7 @@ data class Collection(
val comment: String? = "", val comment: String? = "",
val ep_status: Int? = 0, val ep_status: Int? = 0,
val lasttouch: Int? = 0, val lasttouch: Int? = 0,
val rating: Int? = 0, val rating: Float? = 0f,
val status: Status? = Status(), val status: Status? = Status(),
val tag: List<String?>? = listOf(), val tag: List<String?>? = listOf(),
val user: User? = User(), val user: User? = User(),
@@ -67,7 +67,7 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
return df.format(track.score) return df.format(track.score)
} }
override suspend fun add(track: Track): Track { private suspend fun add(track: Track): Track {
return api.addLibManga(track, getUserId()) return api.addLibManga(track, getUserId())
} }
@@ -0,0 +1,95 @@
package eu.kanade.tachiyomi.data.track.komga
import android.content.Context
import android.graphics.Color
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.NoLoginTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.UnattendedTrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import okhttp3.Dns
import okhttp3.OkHttpClient
class Komga(private val context: Context, id: Int) : TrackService(id), UnattendedTrackService, NoLoginTrackService {
companion object {
const val UNREAD = 1
const val READING = 2
const val COMPLETED = 3
const val ACCEPTED_SOURCE = "eu.kanade.tachiyomi.extension.all.komga.Komga"
}
override val client: OkHttpClient =
networkService.client.newBuilder()
.dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
.build()
val api by lazy { KomgaApi(client) }
@StringRes
override fun nameRes() = R.string.tracker_komga
override fun getLogo() = R.drawable.ic_tracker_komga
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
override fun getStatus(status: Int): String = with(context) {
when (status) {
UNREAD -> getString(R.string.unread)
READING -> getString(R.string.currently_reading)
COMPLETED -> getString(R.string.completed)
else -> ""
}
}
override fun getCompletionStatus(): Int = COMPLETED
override fun getScoreList(): List<String> = emptyList()
override fun displayScore(track: Track): String = ""
override suspend fun update(track: Track): Track {
return api.updateProgress(track)
}
override suspend fun bind(track: Track): Track {
return track
}
override suspend fun search(query: String): List<TrackSearch> {
TODO("Not yet implemented: search")
}
override suspend fun refresh(track: Track): Track {
val remoteTrack = api.getTrackSearch(track.tracking_url)!!
track.copyPersonalFrom(remoteTrack)
track.total_chapters = remoteTrack.total_chapters
return track
}
override suspend fun login(username: String, password: String) {
saveCredentials("user", "pass")
}
// TrackService.isLogged works by checking that credentials are saved.
// By saving dummy, unused credentials, we can activate the tracker simply by login/logout
override fun loginNoop() {
saveCredentials("user", "pass")
}
override fun accept(source: Source): Boolean = source::class.qualifiedName == ACCEPTED_SOURCE
override suspend fun match(manga: Manga): TrackSearch? =
try {
api.getTrackSearch(manga.url)
} catch (e: Exception) {
null
}
}
@@ -0,0 +1,84 @@
package eu.kanade.tachiyomi.data.track.komga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
const val READLIST_API = "/api/v1/readlists"
class KomgaApi(private val client: OkHttpClient) {
private val json: Json by injectLazy()
suspend fun getTrackSearch(url: String): TrackSearch =
withIOContext {
try {
val track = if (url.contains(READLIST_API)) {
client.newCall(GET(url))
.await()
.parseAs<ReadListDto>()
.toTrack()
} else {
client.newCall(GET(url))
.await()
.parseAs<SeriesDto>()
.toTrack()
}
val progress = client
.newCall(GET("$url/read-progress/tachiyomi"))
.await()
.parseAs<ReadProgressDto>()
track.apply {
cover_url = "$url/thumbnail"
tracking_url = url
total_chapters = progress.booksCount
status = when (progress.booksCount) {
progress.booksUnreadCount -> Komga.UNREAD
progress.booksReadCount -> Komga.COMPLETED
else -> Komga.READING
}
last_chapter_read = progress.lastReadContinuousIndex
}
} catch (e: Exception) {
Timber.w(e, "Could not get item: $url")
throw e
}
}
suspend fun updateProgress(track: Track): Track {
val progress = ReadProgressUpdateDto(track.last_chapter_read)
val payload = json.encodeToString(progress)
client.newCall(
Request.Builder()
.url("${track.tracking_url}/read-progress/tachiyomi")
.put(payload.toRequestBody("application/json".toMediaType()))
.build()
)
.await()
return getTrackSearch(track.tracking_url)
}
private fun SeriesDto.toTrack(): TrackSearch = TrackSearch.create(TrackManager.KOMGA).also {
it.title = metadata.title
it.summary = metadata.summary
it.publishing_status = metadata.status
}
private fun ReadListDto.toTrack(): TrackSearch = TrackSearch.create(TrackManager.KOMGA).also {
it.title = name
}
}
@@ -0,0 +1,83 @@
package eu.kanade.tachiyomi.data.track.komga
import kotlinx.serialization.Serializable
@Serializable
data class SeriesDto(
val id: String,
val libraryId: String,
val name: String,
val created: String?,
val lastModified: String?,
val fileLastModified: String,
val booksCount: Int,
val booksReadCount: Int,
val booksUnreadCount: Int,
val booksInProgressCount: Int,
val metadata: SeriesMetadataDto,
val booksMetadata: BookMetadataAggregationDto
)
@Serializable
data class SeriesMetadataDto(
val status: String,
val created: String?,
val lastModified: String?,
val title: String,
val titleSort: String,
val summary: String,
val summaryLock: Boolean,
val readingDirection: String,
val readingDirectionLock: Boolean,
val publisher: String,
val publisherLock: Boolean,
val ageRating: Int?,
val ageRatingLock: Boolean,
val language: String,
val languageLock: Boolean,
val genres: Set<String>,
val genresLock: Boolean,
val tags: Set<String>,
val tagsLock: Boolean
)
@Serializable
data class BookMetadataAggregationDto(
val authors: List<AuthorDto> = emptyList(),
val releaseDate: String?,
val summary: String,
val summaryNumber: String,
val created: String,
val lastModified: String
)
@Serializable
data class AuthorDto(
val name: String,
val role: String
)
@Serializable
data class ReadProgressUpdateDto(
val lastBookRead: Int,
)
@Serializable
data class ReadListDto(
val id: String,
val name: String,
val bookIds: List<String>,
val createdDate: String,
val lastModifiedDate: String,
val filtered: Boolean
)
@Serializable
data class ReadProgressDto(
val booksCount: Int,
val booksReadCount: Int,
val booksUnreadCount: Int,
val booksInProgressCount: Int,
val lastReadContinuousIndex: Int,
)
@@ -9,17 +9,19 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.util.lang.awaitSingle import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
import exh.md.utils.FollowStatus import exh.md.utils.FollowStatus
import exh.md.utils.MdUtil import exh.md.utils.MdUtil
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MdList(private val context: Context, id: Int) : TrackService(id) { class MdList(private val context: Context, id: Int) : TrackService(id) {
private val mdex by lazy { MdUtil.getEnabledMangaDex() } private val mdex by lazy { MdUtil.getEnabledMangaDex(Injekt.get()) }
@StringRes @StringRes
override fun nameRes(): Int = R.string.mdlist override fun nameRes(): Int = R.string.mdlist
@@ -43,83 +45,88 @@ class MdList(private val context: Context, id: Int) : TrackService(id) {
override fun displayScore(track: Track) = track.score.toInt().toString() override fun displayScore(track: Track) = track.score.toInt().toString()
override suspend fun add(track: Track): Track = update(track)
override suspend fun update(track: Track): 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 remoteTrack = mdex.fetchTrackingInfo(track.tracking_url)
val followStatus = FollowStatus.fromInt(track.status) val followStatus = FollowStatus.fromInt(track.status)
// this updates the follow status in the metadata // this updates the follow status in the metadata
// allow follow status to update // allow follow status to update
if (remoteTrack.status != followStatus.int) { if (remoteTrack.status != followStatus.int) {
mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus) if (mdex.updateFollowStatus(MdUtil.getMangaId(track.tracking_url), followStatus)) {
remoteTrack.status = followStatus.int remoteTrack.status = followStatus.int
// db.insertFlatMetadataAsync(mangaMetadata.flatten()).await() } else {
} track.status = remoteTrack.status
}
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()
} }
mdex.updateReadingProgress(track) /*if (track.score.toInt() > 0) {
} else if (track.last_chapter_read != 0) { mdex.updateRating(track)
// When followStatus has been changed to unfollowed 0 out read chapters since dex does }
track.last_chapter_read = 0
// 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 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 { override suspend fun refresh(track: Track): Track {
val mdex = mdex ?: throw MangaDexNotFoundException() return withIOContext {
val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track) val mdex = mdex ?: throw MangaDexNotFoundException()
track.copyPersonalFrom(remoteTrack) val (remoteTrack, mangaMetadata) = mdex.getTrackingAndMangaInfo(track)
if (track.total_chapters == 0 && mangaMetadata.status == SManga.COMPLETED) { track.copyPersonalFrom(remoteTrack)
track.total_chapters = mangaMetadata.maxChapterNumber ?: 0 /*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 { fun createInitialTracker(dbManga: Manga, mdManga: Manga = dbManga): Track {
val track = Track.create(TrackManager.MDLIST) return Track.create(TrackManager.MDLIST).apply {
track.manga_id = dbManga.id!! manga_id = dbManga.id!!
track.status = FollowStatus.UNFOLLOWED.int status = FollowStatus.UNFOLLOWED.int
track.tracking_url = MdUtil.baseUrl + mdManga.url tracking_url = MdUtil.baseUrl + mdManga.url
track.title = mdManga.title title = mdManga.title
return track }
} }
override suspend fun search(query: String): List<TrackSearch> { override suspend fun search(query: String): List<TrackSearch> {
val mdex = mdex ?: throw MangaDexNotFoundException() return withIOContext {
return mdex.fetchSearchManga(0, query, mdex.getFilterList()) val mdex = mdex ?: throw MangaDexNotFoundException()
.flatMap { page -> mdex.fetchSearchManga(0, query, mdex.getFilterList())
runAsObservable({ .flatMap { page ->
page.mangas.map { runAsObservable({
toTrackSearch(mdex.getMangaDetails(it.toMangaInfo())) page.mangas.map {
} toTrackSearch(mdex.getMangaDetails(it.toMangaInfo()))
}) }
} })
.awaitSingle() }
.awaitSingle()
}
} }
private fun toTrackSearch(mangaInfo: MangaInfo): TrackSearch = TrackSearch.create(TrackManager.MDLIST).apply { private fun toTrackSearch(mangaInfo: MangaInfo): TrackSearch = TrackSearch.create(TrackManager.MDLIST).apply {
@@ -66,7 +66,7 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString() return track.score.toInt().toString()
} }
override suspend fun add(track: Track): Track { private suspend fun add(track: Track): Track {
track.status = READING track.status = READING
track.score = 0F track.score = 0F
return api.updateItem(track) return api.updateItem(track)
@@ -11,9 +11,6 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
private val json: Json by injectLazy() private val json: Json by injectLazy()
private var oauth: OAuth? = null 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 { override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request() val originalRequest = chain.request()
@@ -24,21 +21,19 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
if (oauth == null) { if (oauth == null) {
oauth = myanimelist.loadOAuth() oauth = myanimelist.loadOAuth()
} }
// Refresh access token if null or expired. // Refresh access token if expired
if (oauth!!.isExpired()) { if (oauth != null && oauth!!.isExpired()) {
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use { chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!.refresh_token)).use {
if (it.isSuccessful) { if (it.isSuccessful) {
setAuth(json.decodeFromString(it.body!!.string())) setAuth(json.decodeFromString(it.body!!.string()))
} }
} }
} }
// Throw on null auth.
if (oauth == null) { if (oauth == null) {
throw Exception("No authentication token") 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() val authRequest = originalRequest.newBuilder()
.addHeader("Authorization", "Bearer ${oauth!!.access_token}") .addHeader("Authorization", "Bearer ${oauth!!.access_token}")
.build() .build()
@@ -7,8 +7,9 @@ data class OAuth(
val refresh_token: String, val refresh_token: String,
val access_token: String, val access_token: String,
val token_type: String, val token_type: String,
val created_at: Long = System.currentTimeMillis(),
val expires_in: Long val expires_in: Long
) { ) {
fun isExpired() = System.currentTimeMillis() > expires_in fun isExpired() = System.currentTimeMillis() > created_at + (expires_in * 1000)
} }
@@ -40,7 +40,7 @@ class Shikimori(private val context: Context, id: Int) : TrackService(id) {
return track.score.toInt().toString() return track.score.toInt().toString()
} }
override suspend fun add(track: Track): Track { private suspend fun add(track: Track): Track {
return api.addLibManga(track, getUsername()) return api.addLibManga(track, getUsername())
} }
@@ -1,9 +1,6 @@
package eu.kanade.tachiyomi.data.updater package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType import androidx.work.NetworkType
@@ -11,52 +8,26 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters 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.data.updater.github.GithubUpdateChecker
import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class UpdaterJob(private val context: Context, workerParams: WorkerParameters) : class UpdaterJob(private val context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) { Worker(context, workerParams) {
override fun doWork(): Result { override fun doWork() = runBlocking {
return runBlocking { try {
try { val result = GithubUpdateChecker().checkForUpdate()
val result = GithubUpdateChecker().checkForUpdate()
if (result is UpdateResult.NewUpdate<*>) { if (result is UpdateResult.NewUpdate<*>) {
val url = result.release.downloadLink UpdaterNotifier(context).promptUpdate(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()
} }
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 { companion object {
private const val TAG = "UpdateChecker" private const val TAG = "UpdateChecker"
@@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.data.updater package eu.kanade.tachiyomi.data.updater
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@@ -28,6 +30,27 @@ internal class UpdaterNotifier(private val context: Context) {
context.notificationManager.notify(id, build()) 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. * Call when apk download starts.
* *
@@ -63,19 +86,20 @@ internal class UpdaterNotifier(private val context: Context) {
* @param uri path location of apk. * @param uri path location of apk.
*/ */
fun onDownloadFinished(uri: Uri) { fun onDownloadFinished(uri: Uri) {
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
with(notificationBuilder) { with(notificationBuilder) {
setContentText(context.getString(R.string.update_check_notification_download_complete)) setContentText(context.getString(R.string.update_check_notification_download_complete))
setSmallIcon(android.R.drawable.stat_sys_download_done) setSmallIcon(android.R.drawable.stat_sys_download_done)
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
// Install action setContentIntent(installIntent)
setContentIntent(NotificationHandler.installApkPendingActivity(context, uri))
clearActions()
addAction( addAction(
R.drawable.ic_system_update_alt_white_24dp, R.drawable.ic_system_update_alt_white_24dp,
context.getString(R.string.action_install), context.getString(R.string.action_install),
NotificationHandler.installApkPendingActivity(context, uri) installIntent
) )
// Cancel action
addAction( addAction(
R.drawable.ic_close_24dp, R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel), context.getString(R.string.action_cancel),
@@ -96,13 +120,13 @@ internal class UpdaterNotifier(private val context: Context) {
setSmallIcon(android.R.drawable.stat_sys_warning) setSmallIcon(android.R.drawable.stat_sys_warning)
setOnlyAlertOnce(false) setOnlyAlertOnce(false)
setProgress(0, 0, false) setProgress(0, 0, false)
// Retry action
clearActions()
addAction( addAction(
R.drawable.ic_refresh_24dp, R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry), context.getString(R.string.action_retry),
UpdaterService.downloadApkPendingService(context, url) UpdaterService.downloadApkPendingService(context, url)
) )
// Cancel action
addAction( addAction(
R.drawable.ic_close_24dp, R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel), context.getString(R.string.action_cancel),
@@ -32,8 +32,8 @@ class GithubUpdateChecker {
.parseAs<GithubRelease>() .parseAs<GithubRelease>()
.let { .let {
// Check if latest version is different from current version // Check if latest version is different from current version
if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) { if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) {
GithubUpdateResult.NewUpdate(it) GithubUpdateResult.NewUpdate(it)
} else { } else {
GithubUpdateResult.NoNewUpdate() GithubUpdateResult.NoNewUpdate()
} }
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.elvishew.xlog.XLog
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper 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.source.SourceManager
import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import exh.log.xLogD
import exh.source.BlacklistedSources import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID import exh.source.EXH_SOURCE_ID
@@ -156,15 +156,15 @@ class ExtensionManager(
// EXH --> // EXH -->
private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> { private fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> {
val blacklistEnabled = preferences.enableSourceBlacklist().get() val blacklistEnabled = preferences.enableSourceBlacklist().get()
return filter { return filterNot { extension ->
if (it.isBlacklisted(blacklistEnabled)) { extension.isBlacklisted(blacklistEnabled)
XLog.tag("ExtensionManager").d("Removing blacklisted extension: (name: %s, pkgName: %s)!", it.name, it.pkgName) .also {
false if (it) this@ExtensionManager.xLogD("Removing blacklisted extension: (name: %s, pkgName: %s)!", extension.name, extension.pkgName)
} else true }
} }
} }
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 return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
} }
// EXH <-- // EXH <--
@@ -333,7 +333,7 @@ class ExtensionManager(
private fun registerNewExtension(extension: Extension.Installed) { private fun registerNewExtension(extension: Extension.Installed) {
// SY --> // SY -->
if (extension.isBlacklisted()) { 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 return
} }
// SY <-- // SY <--
@@ -351,7 +351,7 @@ class ExtensionManager(
private fun registerUpdatedExtension(extension: Extension.Installed) { private fun registerUpdatedExtension(extension: Extension.Installed) {
// SY --> // SY -->
if (extension.isBlacklisted()) { 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 return
} }
// SY <-- // SY <--
@@ -32,7 +32,7 @@ internal class ExtensionGithubApi {
.let { parseResponse(it) } .let { parseResponse(it) }
} /* SY --> */ + preferences.extensionRepos().get().flatMap { repoPath -> } /* SY --> */ + preferences.extensionRepos().get().flatMap { repoPath ->
val url = "$BASE_URL$repoPath/repo/" val url = "$BASE_URL$repoPath/repo/"
networkService.client networkService.client
.newCall(GET("${url}index.min.json")) .newCall(GET("${url}index.min.json"))
.await() .await()
.parseAs<JsonArray>() .parseAs<JsonArray>()
@@ -163,7 +163,7 @@ internal object ExtensionLoader {
else -> throw Exception("Unknown source class type! ${obj.javaClass}") else -> throw Exception("Unknown source class type! ${obj.javaClass}")
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Timber.e(e, "Extension load error: $extName.") Timber.e(e, "Extension load error: $extName ($it)")
return LoadResult.Error(e) return LoadResult.Error(e)
} }
} }
@@ -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()
)
@@ -1,16 +1,16 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import coil.util.CoilUtils
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import okhttp3.Cache import okhttp3.Cache
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.net.InetAddress
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) { /* SY --> */ open /* SY <-- */ class NetworkHelper(context: Context) {
@@ -21,46 +21,34 @@ import java.util.concurrent.TimeUnit
private val cacheSize = 5L * 1024 * 1024 // 5 MiB private val cacheSize = 5L * 1024 * 1024 // 5 MiB
/* SY --> */ open /* SY <-- */ val cookieManager = AndroidCookieJar() /* SY --> */ open /* SY <-- */val cookieManager = AndroidCookieJar()
/* SY --> */ open /* SY <-- */ val client by lazy { private val baseClientBuilder: OkHttpClient.Builder
val builder = OkHttpClient.Builder() get() {
.cookieJar(cookieManager) val builder = OkHttpClient.Builder()
.cache(Cache(cacheDir, cacheSize)) .cookieJar(cookieManager)
.connectTimeout(30, TimeUnit.SECONDS) .connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(UserAgentInterceptor()) .addInterceptor(UserAgentInterceptor())
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply { val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.HEADERS level = HttpLoggingInterceptor.Level.HEADERS
}
builder.addInterceptor(httpLoggingInterceptor)
} }
builder.addInterceptor(httpLoggingInterceptor)
when (preferences.dohProvider()) {
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
PREF_DOH_GOOGLE -> builder.dohGoogle()
}
return builder
} }
if (preferences.enableDoh()) { /* SY --> */ open /* SY <-- */val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
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()
)
}
builder.build() val coilClient by lazy { baseClientBuilder.cache(CoilUtils.createDefaultCache(context)).build() }
}
/* SY --> */ open /* SY <-- */val cloudflareClient by lazy { /* SY --> */ open /* SY <-- */val cloudflareClient by lazy {
client.newBuilder() client.newBuilder()
@@ -9,6 +9,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.closeQuietly
import rx.Observable import rx.Observable
import rx.Producer import rx.Producer
import rx.Subscription import rx.Subscription
@@ -70,7 +71,9 @@ suspend fun Call.await(): Response {
return return
} }
continuation.resume(response) continuation.resume(response) {
response.body?.closeQuietly()
}
} }
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
@@ -1,14 +1,14 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network.interceptor
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.webkit.WebSettings import android.webkit.WebSettings
import android.webkit.WebView import android.webkit.WebView
import android.widget.Toast import android.widget.Toast
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.WebViewClientCompat import eu.kanade.tachiyomi.util.system.WebViewClientCompat
@@ -114,10 +114,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
latch.countDown() latch.countDown()
} }
// HTTP error codes are only received since M if (url == origRequestUrl && !challengeFound) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
url == origRequestUrl && !challengeFound
) {
// The first request didn't return the challenge, abort. // The first request didn't return the challenge, abort.
latch.countDown() latch.countDown()
} }
@@ -156,6 +153,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
webView?.stopLoading() webView?.stopLoading()
webView?.destroy() webView?.destroy()
webView = null
} }
// Throw exception if we failed to bypass Cloudflare // Throw exception if we failed to bypass Cloudflare
@@ -171,6 +169,6 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
companion object { companion object {
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare") 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,58 @@
package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.TimeUnit
/**
* An OkHttp interceptor that handles rate limiting.
*
* Examples:
*
* permits = 5, period = 1, unit = seconds => 5 requests per second
* permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes
*
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
class RateLimitInterceptor(
private val permits: Int,
private val period: Long = 1,
private val unit: TimeUnit = TimeUnit.SECONDS
) : Interceptor {
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
override fun intercept(chain: Interceptor.Chain): Response {
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}
@@ -0,0 +1,65 @@
package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.Response
import java.util.concurrent.TimeUnit
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
* httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
class SpecificHostRateLimitInterceptor(
private val httpUrl: HttpUrl,
private val permits: Int,
private val period: Long = 1,
private val unit: TimeUnit = TimeUnit.SECONDS
) : Interceptor {
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
private val host = httpUrl.host
override fun intercept(chain: Interceptor.Chain): Response {
if (chain.request().url.host != host) {
return chain.proceed(chain.request())
}
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}
@@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network.interceptor
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.Interceptor import okhttp3.Interceptor

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