Compare commits

...

466 Commits

Author SHA1 Message Date
a6ac2fbc9a Release v0.13.0 2022-01-31 15:32:08 -05:00
3da8677e32 Disable update/download warnings for stable release 2022-01-31 15:28:14 -05:00
4d0d7d5ad6 Weblate translations (#6494)
Co-authored-by: Ahmad Azwar Annas <ahmadazw2@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Allen Sam <allenaizen@pm.me>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Garutmaan Garuda <garutmaangaruda@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@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: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: altinat <poiiiii4yy@gmail.com>
Co-authored-by: jdkdklsls dkdkdk <accshared420@gmail.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/es/
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/ml/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ne/
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/sa/
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/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: Ahmad Azwar Annas <ahmadazw2@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Allen Sam <allenaizen@pm.me>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Garutmaan Garuda <garutmaangaruda@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: altinat <poiiiii4yy@gmail.com>
Co-authored-by: jdkdklsls dkdkdk <accshared420@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2022-01-31 15:28:05 -05:00
8c4ece4b2d Fix selection state appearance in clear database list (fixes #6526) 2022-01-31 15:01:01 -05:00
bf3bb8a378 Remove more formatting span types in SearchView (maybe fixes #6495) 2022-01-30 10:51:49 -05:00
cf5e60f8eb MangaSummaryView: Fix incomplete description on tablet ui (#6518) 2022-01-30 10:47:53 -05:00
7de707c60a Avoid invalid unset default browser (fixes #6520) 2022-01-30 10:46:46 -05:00
5cd11ad8c3 Fix bottom nav showing on resume when action mode is active (#6514) 2022-01-29 13:55:33 -05:00
6bba52a2b6 Always try to use default browser when opening custom tabs on all Android versions 2022-01-29 10:12:46 -05:00
54b476df4e [skip ci] add note about issue taking in contribution guide 2022-01-28 10:20:51 -05:00
a68f123594 TachiyomiAppBarLayout: Use insetter to handle inset (#6506)
This requires adding the status bar foreground drawing logic since the parent
class wouldn't know the inset changes anymore.
2022-01-28 10:14:13 -05:00
08ad4f96b9 Tweaked Yin & Yang theme a little (#6507) 2022-01-27 08:24:32 -05:00
77a3acf5cc Fix search inputs accepting formatted text (#6501)
* Fix Global and extension search input accepts formatted text #6495

* Code change as requested because of performance issue

* code changes as requested

* minor code changes
2022-01-26 23:37:26 -05:00
dea585e69b add extra space before error in log description (#6505)
to make it easier to read if the log reader using word wrap
2022-01-26 23:29:38 -05:00
879dacfba6 Copy source ID to clipboard when long pressing source in migrate list (closes #6479) 2022-01-26 23:00:43 -05:00
b459234ddc Try to show more relevant exception messages when failing to restore a backup 2022-01-26 22:43:27 -05:00
76d2c676fd Discard backup file if it fails to be created properly (e.g. fails validation) 2022-01-26 22:32:06 -05:00
d5015d37e1 Show error toast if empty URI is passed when trying to create/restore a backup 2022-01-26 22:31:28 -05:00
1b71e4cee7 Write job failure exceptions to error log 2022-01-26 22:21:01 -05:00
18ef5c6ff9 Update to AGP 7.1.0 2022-01-25 22:49:50 -05:00
35e0561950 Replace custom download dialog buttons with MaterialButtons 2022-01-25 08:41:15 -05:00
adab8e3ed8 Allow choosing browser apps from WebView even when extension deep links are verified in Android 12+ 2022-01-24 09:26:08 -05:00
89dbb4d300 Avoid migration failing if previous source doesn't exist 2022-01-23 17:21:23 -05:00
e3f3686b8a Allow Samsung devices on Android 12+ to use dynamic theme
Since it seems to work fine, regardless of what the Material Components library seems to dictate.
2022-01-22 14:53:12 -05:00
9984e983b4 Fix tab underline in chapter settings sheet 2022-01-22 14:43:11 -05:00
4ebe67ef53 Spacing adjustments in reader settings sheet 2022-01-22 14:39:05 -05:00
1a11d4153e Fix solid background behind text selection UI in dialogs 2022-01-22 14:38:18 -05:00
cd7cf3583e fix: handle Komga tracks during manga migration (#6463)
* fix: handle Komga tracks during manga migration

closes #6354

* refactor: remove Komga direct reference
2022-01-22 14:25:05 -05:00
66a180bc36 Add ability to open FAQ and Guide, and Changelog in extension repository (#6469) 2022-01-22 14:17:43 -05:00
eb06667455 Weblate translations (#6429)
Co-authored-by: Ahmad Azwar Annas <ahmadazw2@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Arun <arun007@pm.me>
Co-authored-by: AsyJAIZ <2007andrylavr@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Colin Tirion <grotehoed@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: I. Musthafa <i.musthafa66@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Johkum <jacobomur@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Manu <manularrosa96@mailbox.org>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nabin Dhakal <nabin6707@gmail.com>
Co-authored-by: Niko Strijbol <strijbol.niko@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Sergio Gomez Damas <sergio.gomezdamas@gmail.com>
Co-authored-by: Soare Robert Daniel <soare.robert.daniel@protonmail.com>
Co-authored-by: StirCrazy <camigames252@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Yahya Kerba <yahyakerba97@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: altinat <poiiiii4yy@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/eu/
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/gl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/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: Ahmad Azwar Annas <ahmadazw2@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Arun <arun007@pm.me>
Co-authored-by: AsyJAIZ <2007andrylavr@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Colin Tirion <grotehoed@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <alchemillatruth@purelymail.com>
Co-authored-by: Eugene <e.shlyapkin99@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: I. Musthafa <i.musthafa66@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Johkum <jacobomur@gmail.com>
Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Manu <manularrosa96@mailbox.org>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nabin Dhakal <nabin6707@gmail.com>
Co-authored-by: Niko Strijbol <strijbol.niko@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Sergio Gomez Damas <sergio.gomezdamas@gmail.com>
Co-authored-by: Soare Robert Daniel <soare.robert.daniel@protonmail.com>
Co-authored-by: StirCrazy <camigames252@gmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Yahya Kerba <yahyakerba97@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: altinat <poiiiii4yy@gmail.com>
Co-authored-by: ssantos <ssantos@web.de>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2022-01-22 13:26:46 -05:00
0ff8966a27 Fix pages not being serializable for chapter cache (fixes #6483) 2022-01-20 17:48:25 -05:00
2cc6794db5 Revert "Update core-splashscreen (#6471)"
This reverts commit 46ec655db5.

This broke the background color in Android 12+.
2022-01-18 22:51:43 -05:00
0cb4094dd9 Update dependencies 2022-01-18 18:03:16 -05:00
edd213343b Remove some dead code 2022-01-18 17:54:17 -05:00
46ec655db5 Update core-splashscreen (#6471) 2022-01-18 17:51:14 -05:00
769efd9d06 HistoryController: Drop first search event (#6465)
Removes unnecessary data set changes when entering history screen
2022-01-14 22:25:05 -05:00
49cb3b6aa7 MangaInfo: Don't apply appbar padding on tablet ui (#6464) 2022-01-14 22:24:55 -05:00
8ad98b67d2 Change library list item title style (#6457) 2022-01-09 12:10:01 -05:00
8a8f1d3205 Update crop border shortcut state when reading mode or manga set (fixes #6441) 2022-01-09 10:53:06 -05:00
4a27f0546c Fix bottom nav being visible when resuming app (fixes #6012) 2022-01-09 10:26:51 -05:00
727a7e4b2d Change Toolbar to MaterialToolbar (#6456) 2022-01-09 09:55:16 -05:00
2b5e8241ab Fix more crashes 2022-01-08 16:23:55 -05:00
3dc4fd8dd1 Make tracker status wording/ordering more consistent 2022-01-08 15:49:39 -05:00
375a27a93d Add new manga statuses
To be exposed in extension-lib 1.3
2022-01-08 15:39:45 -05:00
544387d1a0 Avoid reader crash 2022-01-08 15:06:44 -05:00
cb8120d38f Update to Conductor 3.1.2 2022-01-08 15:01:42 -05:00
78a261f5d3 Reduce stutter when entering Browse screen (#6435)
* More coil

* ExtensionController: Drop first text change event

* Browse-Source: Remove unnecessary load

* ExtensionPresenter: Increase debounce timeout

To avoid heavy list reload during first enter animation
2022-01-08 12:55:22 -05:00
b8f7653fb2 Use material components on reader error views (#6447)
* Use material components on reader error views

* Adjust image loading behavior

Don't set automatic background color right away and keep show progress indicator
until the page image is fully loaded.
2022-01-08 12:53:20 -05:00
e0d2a01bc8 Add DoH abbreviation to preference title so it's searchable 2022-01-06 22:57:22 -05:00
560be9f553 Remove clutter in main (#6437) 2022-01-06 22:54:51 -05:00
47723042c5 Fallback to preference title if dialog title isn't set 2022-01-06 22:53:54 -05:00
d04d676d2f URL encode Kitsu search queries (fixes #5712) 2022-01-05 17:43:11 -05:00
3435636ca0 Replace use-experimental Kotlin compiler flags with opt-in 2022-01-05 17:43:11 -05:00
2e1572d7cc fix crash in ReaderActivity (#6439) 2022-01-04 21:29:14 -05:00
938339690e Custom Cloudflare failure exception to avoid user-facing "java.lang.Exception" text 2022-01-02 17:57:20 -05:00
dbb2c523c1 Avoid crashes in tracker interceptor errors 2022-01-02 17:56:49 -05:00
0b9d436753 Fix some crashes 2022-01-02 11:25:35 -05:00
2d03f3ce1e Add QuickJS dependency to eventually replace Duktape 2022-01-02 11:25:23 -05:00
c4a476d0d2 Handle renaming existing downloaded CBZ chapters on update 2022-01-01 15:22:03 -05:00
5122aed332 Weblate translations (#6352)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Andre Rahardjo <zertozzf@gmail.com>
Co-authored-by: Aviv Ben Ami <avivbenami@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Hossain Rizbi <rsajib387@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Italian Translator <nekoxtranslator@etlgr.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jagadeesh Vijay Varma <jagadeeshvarma.b@gmail.com>
Co-authored-by: Jan Obst <macek04@volny.cz>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Neterskian <neterskian@gmail.com>
Co-authored-by: Nikola Perović <nikolaperovicccc@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rikishaaa <jebote90@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Viktoria PETROVA <victoriaapetrova@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: f3rr31 <5920873@disroot.org>
Co-authored-by: stevenlele <stevenlele@126.com>
Co-authored-by: typek52 <typek52@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: Тимур <tucirs@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
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/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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/eu/
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/he/
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/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/sk/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/te/
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Andre Rahardjo <zertozzf@gmail.com>
Co-authored-by: Aviv Ben Ami <avivbenami@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: Hossain Rizbi <rsajib387@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Italian Translator <nekoxtranslator@etlgr.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jagadeesh Vijay Varma <jagadeeshvarma.b@gmail.com>
Co-authored-by: Jan Obst <macek04@volny.cz>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Neterskian <neterskian@gmail.com>
Co-authored-by: Nikola Perović <nikolaperovicccc@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rikishaaa <jebote90@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Unai <uesandi@gmail.com>
Co-authored-by: Viktoria PETROVA <victoriaapetrova@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: f3rr31 <5920873@disroot.org>
Co-authored-by: stevenlele <stevenlele@126.com>
Co-authored-by: typek52 <typek52@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: Тимур <tucirs@gmail.com>
2022-01-01 14:57:22 -05:00
5336c5b46e Add compress to CBZ on download (#6360) 2022-01-01 14:46:43 -05:00
22615f5981 Fixes descriptive notification message for errors (#6413)
* Fixes descriptive notification message for errors
Fixes #6401

* Fixes descriptive notification message for errors
Fixes #6401
2022-01-01 13:13:44 -05:00
bdf4b4b679 Add AppInfo functions to replace BuildConfig usages in extensions 2022-01-01 11:30:18 -05:00
548e300c4b Remove unused Nsfw annotation
Extensions now purely declare it through the Gradle config
2022-01-01 10:51:27 -05:00
8a5d8c96ef Remove explicit option to store downloads in app data folder
App data is typically deleted during app uninstallation, which some users are unaware of. The folder is also inaccessible externally by default in Android 11+, which is also annoying to users.
2022-01-01 10:44:27 -05:00
78c2631b6f Move preference extension functions to utils 2021-12-31 16:32:24 -05:00
7c246ffc71 Add link to troubleshooting guide in library update error log file 2021-12-31 15:16:44 -05:00
8bb85753cc Update versions and about libraries plugins 2021-12-31 14:40:12 -05:00
abfdde28ef Swallow observable errors instead of crashing 2021-12-31 13:21:32 -05:00
9801f1edfa Update analytics dependencies 2021-12-31 13:21:31 -05:00
fc3a200a63 Fix Crash while trying to search in Settings (#6397)
* Fix Crash while trying to search in Settings

* Use already provided categories
2021-12-31 13:21:05 -05:00
6a00658119 Update wording of pref_remove_after_marked_as_read (#6418) 2021-12-31 13:20:21 -05:00
353485054e Fix some crashes 2021-12-28 16:53:35 -05:00
800583b5e2 Actually Fix #6341 (#6392) 2021-12-26 15:45:29 -05:00
2db2b7348d Fix crash for bound intListPreferences 2021-12-26 15:44:34 -05:00
f3718257f5 Reduce redundancy in some preference declarations
The remaining ones could also be converted to FlowPreferences for this, but it's not really necessary.
2021-12-26 12:44:38 -05:00
5500762acd Update "Library updates restrictions" wording (#6371)
* Update "Library updates restrictions" wording

Co-Authored-By: OncePunchedMan <64155117+OncePunchedMan@users.noreply.github.com>
Co-Authored-By: nicki <72807749+curche@users.noreply.github.com>
Co-Authored-By: Soitora <simon.mattila@protonmail.com>
Co-Authored-By: FourTOne5 <59261191+FourTOne5@users.noreply.github.com>

* Update strings.xml

Co-Authored-By: loocool2 <36128021+loocool2@users.noreply.github.com>

Co-authored-by: OncePunchedMan <64155117+OncePunchedMan@users.noreply.github.com>
Co-authored-by: nicki <72807749+curche@users.noreply.github.com>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: FourTOne5 <59261191+FourTOne5@users.noreply.github.com>
Co-authored-by: loocool2 <36128021+loocool2@users.noreply.github.com>
2021-12-26 11:49:27 -05:00
4c8f5e1f7a Use animation to hide/show fab (#6385) 2021-12-26 11:38:35 -05:00
733cf99bb4 Fix incorrect locale name casing in extension details (fixes #6391)
Also closing https://github.com/tachiyomiorg/tachiyomi-extensions/issues/10007 since multisource extensions aren't really a thing anymore.
2021-12-26 11:35:28 -05:00
58c2f22120 Truncate MAL search queries to first 64 characters (closes #6314)
Is it worth telling the user? ¯\_(ツ)_/¯
2021-12-26 11:35:28 -05:00
42accebeca Case insensitive sort in extension list. (#6375)
* Sort Extension irrespective of it's name's case.

* Avoid creating unnecessary strings

Co-Authored-By: arkon <arkon@users.noreply.github.com>

Co-authored-by: arkon <arkon@users.noreply.github.com>
2021-12-24 14:55:11 -05:00
1c5c370c12 Avoid unnecessary string creation when sorting 2021-12-24 10:26:24 -05:00
448645d83a Don't recompute constant device info 2021-12-24 10:25:02 -05:00
09b6a3b41e Rename night theme color files too 2021-12-24 09:55:22 -05:00
74206d60ce Rename theme color value files so they are not scattered (#6384) 2021-12-24 09:53:10 -05:00
c3a0de7fab Update dependencies 2021-12-24 09:45:56 -05:00
7edf7a434f Avoid crash on Samsung devices on Android 12
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-12-24 09:42:45 -05:00
b701821550 Handle potentially missing sources list in extensions JSON
Can happen in:
- Unofficial repos
- If the inspector breaks
2021-12-24 09:32:44 -05:00
d022bf2673 Fix Global Search ignoring incognito mode when setting last used source. (#6374) 2021-12-24 09:32:19 -05:00
7eed8c440c Fix readded chapters polluting 'Updates' tab. (#6377) 2021-12-24 09:28:48 -05:00
1ab12e380a Fix #6341 (#6383) 2021-12-24 09:28:23 -05:00
728e14e8e4 TachiyomiCoordinatorLayout: Remove app bar lift mechanism for view pager (#6379)
This is a follow up to 845e061382
...jk i actually forgot about it
2021-12-24 09:27:57 -05:00
8aa402526a Fix #6366 (#6372) 2021-12-21 09:35:00 -05:00
4793ee4786 Update some wording in Delete Chapters preference. (#6365) 2021-12-19 09:09:48 -05:00
a09d6c0470 Better Upload Date to not have a single blank upload date. (#6358) 2021-12-18 14:47:07 -05:00
9e83130bd8 Add Better Extension Search (#6359)
Add support to searching with source name, id and baseUrl for a extension's sources.
2021-12-18 14:46:45 -05:00
2ed01af723 Action toolbar adjustments (#6353)
* Pair ActionToolbar with ActionMode

This makes ActionToolbar an activity object that can be configured in the
similar way as ActionMode

* Remove action toolbar workaround now that it stays in activity layout

5924

* Set status bar color when action mode is active

6256

* Adjust fab show timing after action mode finished

* Adjust action toolbar layout and animation

Default corner size and use bottom sheet animation

6069

* Adjust action toolbar layout on large screen

Right half of the screen
2021-12-18 14:16:26 -05:00
afc80d6a7c Adjust global update preference wording and visibility 2021-12-17 14:14:48 -05:00
532a1b1aba Address some IDE warnings 2021-12-17 12:30:41 -05:00
65062b4bcb Remove library update order setting
This doesn't make sense to have if the intention is to have reasonably sized global updates. Opting to remove it to remove complexity instead.
2021-12-17 12:29:54 -05:00
c16206d816 [skip ci] update workflow actions 2021-12-17 10:47:55 -05:00
185283f864 [skip ci] fix thread locking workflow to actually be daily 2021-12-17 10:40:52 -05:00
7d1f5c7383 Hide irrelevant settings if global update isn't enabled 2021-12-17 10:14:04 -05:00
945afc71ef Refactor dependant preference visibility flows 2021-12-17 10:11:07 -05:00
818fe50f77 Combine global update item restrictions 2021-12-17 09:57:37 -05:00
6fddad7a77 Add option for library update only update completely read manga (#6323)
* Add option for library update only update completely read manga

only check manga for updates if there is no unread chapter

* sum
2021-12-17 09:40:54 -05:00
38d131be37 Workaround cleanup (#6350)
* Remove material-components workaround that was fixed upstream

* Remove unused toolbar workaround

* Fix cover dialog navigation icon
2021-12-17 09:32:42 -05:00
aeff846e1f Update dependencies 2021-12-16 23:00:04 -05:00
6b52fc1e2d Use elevated overlay on reader menu (#6347) 2021-12-16 22:57:31 -05:00
0671b530ba Update to Kotlin 1.6.10 2021-12-15 17:45:33 -05:00
207f9c26ae Add link to privacy policy 2021-12-15 17:45:33 -05:00
6367ce5e5e sfix some colors and going back to original pink-ish color (#6344) 2021-12-13 11:09:36 -05:00
ba1a2e9942 Remove Gson dependencies
All official extensions no longer use Gson and Kotson
2021-12-12 18:06:04 -05:00
7f998ecdbd Revert download notification icon changes 2021-12-12 11:14:47 -05:00
ecd5414287 Move custom brightness slider to top of filter sheet (closes #6205)
Brightness should be modified more often than the color filter. Since this will always be visible even when the sheet is half expanded, you have a better idea of its effect.
2021-12-11 14:07:18 -05:00
6107f5f3d2 Refactor backup restore process to stop relying on file extension 2021-12-11 13:53:49 -05:00
13afa9f476 Show version name in new update notification 2021-12-11 13:09:16 -05:00
cd87c7e88e Don't preselect any options in library remove manga/downloads dialog (closes #6333)
Since apparently people don't read and either option is considered destructive to different people.
2021-12-11 13:05:13 -05:00
ed4dea8686 Update notification icons
Although no recent version of Android even shows these....
2021-12-11 12:59:05 -05:00
808177f8c9 Add download action to New Chapters Notification (#6336) 2021-12-11 12:51:30 -05:00
aed51251b3 Update AGP and Gradle 2021-12-11 11:24:58 -05:00
1c2730163d Apply dialog theme to Material Alert Dialog Theme (#6340) 2021-12-11 10:16:00 -05:00
0de86dfe6f Fix back button having wrong tint in Toolbar (#6339) 2021-12-11 08:44:05 -05:00
7a1b99be46 Tweak Midnight Dusk colors (#6327)
* tweak midnight dusk colors

* more tweaking and change pure black dark mode option visibility

* revert changing pure black dark mode option visibility

* change tertiary color on light theme to match dark theme
2021-12-09 18:00:43 -05:00
9b64b0139c Check if dynamic colors are available using official API 2021-12-05 11:37:03 -05:00
0a6160d7cf Add sui support (#6318)
* Update shizuku api version

* SettingsAdvanced Controller: Verify if Sui is available
2021-12-05 11:19:37 -05:00
e51a6d332e SourcePreferencesController: Also call onBindEditText listener set by extension (#6310) 2021-12-04 10:52:52 -05:00
a9d2741e6a Automatically set tracker as completed after reading the last chapter (#6289)
* Automatically set tracker as completed after reading the last chapter.

* use integer value in comparison

* also set `started_reading` date

* don't use source manga's status

* remove useless line
2021-12-04 09:59:39 -05:00
12bd7268d2 Remove unnecessary tab style 2021-12-04 09:58:41 -05:00
be0a23d9ad Tabbed bottom sheet adjustments (#6309)
* SimpleNavigationView: Don't set background and elevation

* Add divider for tabs in bottom sheet
2021-12-04 09:57:29 -05:00
458a0e608a Apply elevation overlay to colored navbar (#6308) 2021-12-04 09:57:02 -05:00
32f3a50def Update dependencies 2021-12-02 23:09:38 -05:00
7de4226d80 [skip ci] Update gradle-command-action 2021-11-28 19:08:42 -05:00
6a39c8fc13 Avoid loading available extensions list if it seems too small 2021-11-28 18:29:22 -05:00
dc39669321 Use default snackbar styles (fixes unreadable text)
Sorry AMOLED users, you'll just have to deal with the brief light snackbars.
2021-11-28 15:30:51 -05:00
be4f27028c Throw exceptions if some of the deprecated source methods are used 2021-11-28 15:27:21 -05:00
60e73e2d1f Allow loading extension-lib 1.3
(Which doesn't actually exist yet, but will at some point after the next major release)
2021-11-28 14:55:03 -05:00
e8f284d377 Add convenience extension functions for rate limit interceptors
To be included in extension-lib 1.3 as a replacement for the lib that's currently compiled in tachiyomi-extensions.
2021-11-28 14:41:46 -05:00
3ea3b0bf2e Add UnmeteredSource interface
To be included in extension-lib 1.3 (or whatever it's going to be). This applies to sources like Komga or Lanragi, where large numbers of update/download aren't of concern since they're (usually) self-hosted.
2021-11-28 14:41:03 -05:00
e1a43d2e7d Update dependencies 2021-11-28 14:24:43 -05:00
2e918fe1d6 [skip ci] update issue-moderator-action 2021-11-27 22:33:52 -05:00
601309c7cc Weblate translations (#6179)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Anup Pandey <pandeyanup58@gmail.com>
Co-authored-by: Arunava Ghosh <senseiarunava@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Guerilla Girl <tnfgdqzx@guerrillamail.info>
Co-authored-by: Haithem Djabi <zabiz9632@gmail.com>
Co-authored-by: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: HeavenShadow <heavenshadow@outlook.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Italian Translator <nekoxtranslator@etlgr.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Milo Ivir <mail@milotype.de>
Co-authored-by: Nepx <anandabaskara@outlook.com>
Co-authored-by: Neterskian <neterskian@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pavka <pavel-mosein@yandex.ru>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Raven Neil Ocampo <ocamporavenneil@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Tom de Groot <ikbeniemanddieheet@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: anenasa <anenasaa@yahoo.com>
Co-authored-by: pepe1987 <killyourpepe@gmail.com>
Co-authored-by: ricardofontao2000 <up201806317@fe.up.pt>
Co-authored-by: sebastians17 <sebastians117.ss@gmail.com>
Co-authored-by: swastik <smokexd676@gmail.com>
Co-authored-by: zm-dksg <zemariadekonincksg@gmail.com>
Co-authored-by: Újvári Marcell <mmarci72@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/bn/
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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/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/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/tl/
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Anup Pandey <pandeyanup58@gmail.com>
Co-authored-by: Arunava Ghosh <senseiarunava@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Guerilla Girl <tnfgdqzx@guerrillamail.info>
Co-authored-by: Haithem Djabi <zabiz9632@gmail.com>
Co-authored-by: HaruSasaki <aiqusubaru@gmail.com>
Co-authored-by: HeavenShadow <heavenshadow@outlook.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Italian Translator <nekoxtranslator@etlgr.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Milo Ivir <mail@milotype.de>
Co-authored-by: Nepx <anandabaskara@outlook.com>
Co-authored-by: Neterskian <neterskian@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pavka <pavel-mosein@yandex.ru>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Raven Neil Ocampo <ocamporavenneil@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: Tom de Groot <ikbeniemanddieheet@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: anenasa <anenasaa@yahoo.com>
Co-authored-by: pepe1987 <killyourpepe@gmail.com>
Co-authored-by: ricardofontao2000 <up201806317@fe.up.pt>
Co-authored-by: sebastians17 <sebastians117.ss@gmail.com>
Co-authored-by: swastik <smokexd676@gmail.com>
Co-authored-by: zm-dksg <zemariadekonincksg@gmail.com>
Co-authored-by: Újvári Marcell <mmarci72@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2021-11-27 13:27:51 -05:00
10ddeeb799 Fix local source cover changing (#6252)
* fix local source cover changing

* Remove unnecessary check for `exists()`

* fix for when there is no thumbnail_url in the db
2021-11-27 12:49:26 -05:00
3463d6c752 MinMaxNumberPicker: Set IME input to use number only (#6286)
* MinMaxNumberPicker: Set IME input to use number only

* MinMaxNumberPicker: Auto disable keyboard input when needed
2021-11-27 12:48:49 -05:00
8acce011b5 fix MAL search novel filter (#6279) 2021-11-25 12:55:52 -05:00
fe9ea50356 Change Tako dark elevation overlay color (#6255) 2021-11-24 17:38:23 -05:00
e6f29ae57f Tweak Teal & Turquoise color for new M3 (#6268)
* Tweak Teal & Turquoise color for new M3

* capitalize

* tweak elevationOverlayColor

* more tweak
2021-11-24 17:38:17 -05:00
6cfd2c510b Fix crash in clear database screen (fixes #6271) 2021-11-24 17:34:39 -05:00
430ff80198 Add tertiary badge in appearance preview (closes #5867) 2021-11-19 17:39:30 -05:00
230fa76d57 Add divider under extension details header 2021-11-19 16:42:28 -05:00
46a4b0e0b6 Partially migrate LocalSource to 1.x methods 2021-11-19 16:42:19 -05:00
5b3cadb7a8 Clean up some tablet dimension values 2021-11-19 14:06:43 -05:00
3153071a8a Update to Conductor 3.1.1 2021-11-19 14:06:11 -05:00
bba7372556 Add ability to clear cookies per-extension (closes #3153) 2021-11-19 11:28:59 -05:00
9fe1a7e2ae Add feature to clear database manga by source (#6241)
* Implement feature to selectively clear manga from database based on it's source

* Code cleanup and refactoring
2021-11-19 11:24:46 -05:00
98822a39d9 Option to clear chapter cache when MainActivity is closed (closes #5651) 2021-11-19 10:50:52 -05:00
a2c830b908 Tweak app theme preference selection (closes #5866) 2021-11-19 10:35:48 -05:00
bdef2cfdfb Replace Resume FAB reveal animation with container transform (#6250) 2021-11-19 10:16:39 -05:00
f229a5e2ec Tweak relative date function (#6249)
* Tweak relative date function

* Cleanup
2021-11-19 10:05:39 -05:00
845e061382 Reinstate elevation overlay (#6243)
* Theme default elevation overlay

* Fix app bar elevation overlay

Elevation overlay is disabled when tabs are visible

* Remove custom elevation overlay in tracking sheet item

* upsi
2021-11-18 10:47:24 -05:00
e7d4eb1ae3 Rework the Library icon for the third time (#6237)
Power outtage made me lose an hour of work sadg
2021-11-18 10:44:56 -05:00
b4ba56bfb4 Update dependencies 2021-11-18 10:42:09 -05:00
25784d1fe5 Clean up ActionMode styles 2021-11-15 10:48:07 -05:00
619eca7a51 Use outlined cards in tracker search 2021-11-15 10:45:04 -05:00
f3d85655a0 Adjust manga genre chip style 2021-11-15 10:44:52 -05:00
9600675677 Adjust CardView styles 2021-11-15 10:11:47 -05:00
ce8a759192 Fix colorFilterActive in Tako theme 2021-11-15 10:11:16 -05:00
88bc0bf613 Adapt App Themes to M3 Color System (#6230)
* Adapt Default theme to M3 color system

* Adapt Dynamic theme to M3 color system

* Adapt Midnight Dusk theme to M3 color system

* Adapt Strawberry Daiquiri theme to M3 color system

* Adapt Yotsuba theme to M3 color system

* Adapt Tako theme to M3 color system

* Adapt Green Apple theme to M3 color system

* Adapt Teal & Turquoise theme to M3 color system

* Adapt Yin & Yang theme to M3 color system

* Remove old theme colors

* Yotsuba theme adjustments

Co-authored-by: ztimms73 <vp1984tanki@gmail.com>

* Green Apple theme adjustments

Co-authored-by: Soitora <10836780+Soitora@users.noreply.github.com>

* Tako theme adjustments

* Midnight Dusk theme adjustments

* Use colorSurfaceVariant for colorControlHighlight

* Nits

Co-authored-by: ztimms73 <vp1984tanki@gmail.com>
Co-authored-by: Soitora <10836780+Soitora@users.noreply.github.com>
2021-11-15 09:53:57 -05:00
b508e4208a Fix library animation lag (#6233) 2021-11-15 09:52:07 -05:00
c74d8cf499 Fix overflowed action toolbar items 2021-11-15 09:51:02 -05:00
a34c2b082f Remove outline from download queue items 2021-11-15 09:50:53 -05:00
ad49a02879 Address some Android lint warnings 2021-11-14 11:16:18 -05:00
e985ffc690 Remove custom tab indicator style
M3 TabLayout has the same style built-in.
2021-11-14 10:52:21 -05:00
6cbb02f02d Adapt M3 Typography (#6228) 2021-11-14 10:23:44 -05:00
c0d0ff66b6 Fix "Check for updates" not working due to time cooldown (#6232)
* Fix "Check for updates" not working due to time cooldown

* Update AppUpdateChecker.kt

Co-authored-by: arkon <arkon@users.noreply.github.com>
2021-11-14 10:23:12 -05:00
1e4d7f8c6e Only allow digits in custom download range dialog (closes #6220) 2021-11-13 10:14:55 -05:00
a8a761aa5f Initial pass of Material 3 styling
Adjustments/fixes to follow.
2021-11-13 10:08:01 -05:00
41952f0215 Added tabletUI option: "Automatic" (#6208)
* added automatic tablet ui option; useful for foldables

* set automatic as default, rename setting

* remove redundant checks

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>

* remove redundant checks

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>

* fix defaultValue

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
2021-11-11 16:32:28 -05:00
bfcc883f01 Update dependencies 2021-11-11 16:25:45 -05:00
39722055f5 Don't auto-download chapters if excluded but no categories selected (fixes #6126) 2021-11-11 16:25:38 -05:00
f85dfa90b8 Remove legacy blue theme
Causing too many theming issues/complexity. This will make the Material 3 transition easier.
2021-11-10 09:15:13 -05:00
0a4163d236 Default to only updating non-completed manga 2021-11-10 09:09:29 -05:00
78de11a9e3 Fix SwipeRefresh initial position in MangaController (#6211)
* Lower position of swipe refresh

* Tweak existing code that sets swipe refresh position
2021-11-07 11:58:45 -05:00
d2fc6d9f44 Use sw720dp for tablet UI threshold 2021-11-07 11:55:18 -05:00
abf31f4a79 Fix cutoff ripple for extension install cancel button 2021-11-07 09:49:40 -05:00
f28dd4f4de Disable some unnecessary build features 2021-11-07 09:20:13 -05:00
55b64899f5 Update dependencies 2021-11-07 09:20:00 -05:00
d4aeeadb26 Avoid crashing when notification channels can't be created/deleted
For example, the application may be launched from a service, where channels cannot be deleted.
2021-11-07 09:11:41 -05:00
7ce0110158 Disable updates badge by default 2021-11-03 09:21:01 -04:00
7c1e55eb7f Update metadata in same scope as the rest of library update (fixes #5702, probably) 2021-11-02 17:27:25 -04:00
27542bc81d Fix crash when updating library whithout manga to update (#6181) 2021-10-31 14:25:32 -04:00
9ebbfb2d90 Clean up local source chapter name cleaning (closes #5969) 2021-10-30 18:36:23 -04:00
701b1ee744 Fix bottom nav sometimes appearing within navbar area 2021-10-30 18:03:29 -04:00
0edc981cd2 Move app and extension update notifications to new channels/group (closes #6168) 2021-10-30 17:42:06 -04:00
da5942b398 Remove unused fast scroll bubble drawable and accidentally committed file 2021-10-30 13:14:34 -04:00
709de81814 Move unread chapters badge setting to General section 2021-10-30 12:48:37 -04:00
90b312a56e Extension "Update all" button (#6171)
Disabled for legacy installer
2021-10-30 12:34:26 -04:00
459759bfe5 Add badge to bottom bar Updates tab indicating how many unread chapter updates are available (#5620)
Co-authored-by: arkon <arkon@users.noreply.github.com>
2021-10-30 12:32:51 -04:00
00817aacfe Remove translations of non-translatable strings 2021-10-30 12:25:18 -04:00
e306eb0874 Weblate translations (#6118)
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Areen Anwar <areenanwar66@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@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: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Neterskian <neterskian@gmail.com>
Co-authored-by: Nin Gun <luis_noxer@hotmail.es>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rafi <ziomeq65@gmail.com>
Co-authored-by: Resshi <jyxo@centrum.cz>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: jTnqr <juliuspanjul25@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: sebastians17 <sebastians117.ss@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/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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_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/sdh/
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/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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Areen Anwar <areenanwar66@gmail.com>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@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: KasukeLp <kasukelp23@yahoo.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: Neterskian <neterskian@gmail.com>
Co-authored-by: Nin Gun <luis_noxer@hotmail.es>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rafi <ziomeq65@gmail.com>
Co-authored-by: Resshi <jyxo@centrum.cz>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: jTnqr <juliuspanjul25@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: sebastians17 <sebastians117.ss@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2021-10-30 12:22:17 -04:00
33a02b47d5 Hide cutout toggle button if fullscreen is off (#6150) 2021-10-30 12:19:15 -04:00
f0a5557e60 Fix storing covers for local manga (#6127)
getCoverFile only returns a cover if it already exists, meaning
the block of code to write it from the inputstream never executes.
If getCoverFile returns null, then use previous behaviour of setting
it to cover.jpg so that if the file doesn't exist it's created
2021-10-30 12:16:29 -04:00
58a871c8cc Allow manga titles to update from source if they are not in library (#6177)
The previous rationale for not allowing manga titles to update (at all) was that it would be confusing for users if a manga's title arbitrarily changed when the source changed it. Presumably, users would care less about this arbitrary change for manga that is not in library, so this provides a path for getting a manga's title updated, and prevents incorrect titles from persisting in the DB for manga that get title updates but aren't in library.
2021-10-30 12:15:48 -04:00
4f56071786 Validate backup during creation 2021-10-28 22:40:53 -04:00
f8b2c79aef Update dependencies 2021-10-28 22:40:53 -04:00
8f00d34b0b Change zoom from 3x to 5x (#6164)
Because some people actually read images with really small text

*I hope they have some extreme high quality pictures else I don't know how they read the raster images*
2021-10-27 23:33:36 -04:00
6129519e5a More sensical string for 'pref_hide_threshold' (#6157) 2021-10-24 10:21:13 -04:00
593091a5e3 Sync view state with controller on activity resume
Hopefully fixes some weird states where the bottom nav shows up when it shouldn't.
2021-10-23 17:30:56 -04:00
22ed163c8f Fix what's new link in dialog 2021-10-23 17:29:27 -04:00
93e2b88d41 Minor cleanup 2021-10-23 17:29:13 -04:00
7cd54dc8f0 Launch the download warning toast in the UI thread
Co-authored-by: jobobby04 <jobobby04@users.noreply.github.com>
2021-10-23 17:05:34 -04:00
ccd7c8df53 Fix double increment when updating covers (#6138)
* Fix double increment when updating covers

* Remove redundant block
2021-10-20 20:10:55 -04:00
5b3bd3f470 Remove jcenter usage (closes #4387) 2021-10-18 12:39:41 -04:00
bf1b7f44b6 Change tablet UI threshold to w720dp (closes #6054)
Was previously sw720dp. Now matches J2K.
2021-10-18 11:03:27 -04:00
538dd60580 Adjust update/download warnings
- Uses toasts now
- Adjusted wording to emphasize effect on sources
- Download warning has a different threshold (15 chapters per source, vs. 60 entries per source for library update)
2021-10-18 10:46:07 -04:00
f453236840 [6068] - Use semibold text for Browse items primary text (#6087)
* Changing the text on source title (browse items) to semibold and fixing the color of the subtitle on global search

* Updated history_item, updates_item and global_search to not have bold titles

* Changing global_search_controller_card

* Changing back history_item
2021-10-18 10:05:54 -04:00
bfe7aa1ed2 Minor cleanup 2021-10-18 10:02:25 -04:00
9e2ef82902 Remove global update intervals below 12 hours, add every 3 day interval
Users with smaller libraries have a lower change of getting updates frequently. "Power users" are actively hurting sources by updating frequently.
2021-10-18 10:02:18 -04:00
9352e249ee Make tapping library update and backup restore error notifications open log 2021-10-18 09:58:35 -04:00
3800065230 Fix crashing when clicking the search icon (#6128) 2021-10-18 09:57:52 -04:00
ebc2c4f73a Remove paused text when resuming downloads with info hidden (fixes #6119) 2021-10-16 17:40:56 -04:00
f057440cc1 Use natural ordering when sorting by chapter numbers (fixes #6121) 2021-10-16 17:37:51 -04:00
506f9cfca8 Weblate translations (#6047)
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: Archie <careless.archie@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@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: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Jozef Hollý <j2.00ghz@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Matey Krastev <matey_krastev2@abv.bg>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shippo <shiposhouyou@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: jTnqr <juliuspanjul25@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: swastik <smokexd676@gmail.com>
Co-authored-by: turhv jbufv <turhvjbufv@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bg/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/he/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
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/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/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: Archie <careless.archie@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: KasukeLp <kasukelp23@yahoo.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: Matey Krastev <matey_krastev2@abv.bg>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Shippo <shiposhouyou@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Swyter <swyterzone@gmail.com>
Co-authored-by: jTnqr <juliuspanjul25@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: swastik <smokexd676@gmail.com>
Co-authored-by: turhv jbufv <turhvjbufv@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2021-10-16 12:05:39 -04:00
8a70c3353f Change global update unmetered connection restriction to connected to Wi-Fi (closes #6117) 2021-10-16 11:10:05 -04:00
3d8f123e05 Add notification action to open GitHub release page
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-10-16 10:42:14 -04:00
a8c8f15e07 Update dependencies 2021-10-16 10:21:25 -04:00
21e647017b Rename app updating classes
So I stop confusing it for updaters of other things.
2021-10-16 10:21:15 -04:00
2a1bb3dc27 Fix reappearing indicator in the History tab (#6105) 2021-10-15 22:14:45 -04:00
55a3094a65 Fix AppBar lift state when snapped (#6103)
status bar foreground alpha is now handled separately
2021-10-15 22:09:19 -04:00
b4490e209b Fix inset not applying when in landscape (#6104)
Reverts inset change from #5997, was changed due to it not changing the padding when using setPadding
2021-10-15 22:08:37 -04:00
9aa676333c Use default source filters in global search (fixes #5583)
Based on 45fbd9d2f5
2021-10-15 22:06:16 -04:00
71b23e57ff [skip ci] Avoid building PRs that only affect Markdown docs or string translations 2021-10-13 09:29:07 -04:00
2c76bc99fc Add ability to copy a genre/tag to clipboard by long-pressing it's chip (#6084)
* Allow copying a genre by long-pressing it's chip

* Make chip click listeners nullable, and only attach if not-null
2021-10-13 09:23:59 -04:00
bb06895145 Fix MangaController fast scroller position (#6090) 2021-10-13 09:23:46 -04:00
684965f3e5 MangaController optimizations (#6089)
* MangaController: Fix ignored stable ids

* MangaController: Replace notifyDataSetChanged

* ChaptersSettingsSheet: Optimizations
2021-10-13 09:23:38 -04:00
e621f4e2fa Add migration to add "all" to enabled langauges
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-10-13 09:18:43 -04:00
028ea57232 Update missing chapters warning
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-10-13 09:17:26 -04:00
718fa25c10 Bump queue warning threshold to 60
Aligns with J2K.
2021-10-13 09:16:19 -04:00
90c9f28818 Update AGP 2021-10-13 09:15:46 -04:00
cb9c5a35cb Minor cleanup 2021-10-13 09:13:38 -04:00
fadaefeaef Handle HTTP 403 responses with CloudflareInterceptor
Based on 8d34da591e

Co-authored-by: jmir1 <jmir1@users.noreply.github.com>
2021-10-11 16:12:26 -04:00
b17b882a3b Adjust update/download warning threshold 2021-10-11 16:08:32 -04:00
f0f3afd5f1 Fix issues with NSFW sources setting (#6085) 2021-10-11 13:51:20 -04:00
42026b49bf Allow hiding "All" section in extensions list (closes #6081) 2021-10-11 10:29:08 -04:00
151193c4c3 Reword missing chapters warning (closes #6074) 2021-10-11 10:22:31 -04:00
3448751e0e Fix crash when deleting last item in library (#6079) 2021-10-11 10:17:01 -04:00
aae011ed83 Use SwitchMaterial for preferences too (closes #5724) 2021-10-09 17:47:04 -04:00
c95a269460 Use single layout for grid badges 2021-10-09 17:26:53 -04:00
98c0e5271f Reword badge titles 2021-10-09 17:16:01 -04:00
f343131802 Require authentication to toggle showing NSFW sources 2021-10-09 17:10:04 -04:00
ea34ba53b9 Allow searching for multiple extensions at once (closes #5922) 2021-10-09 16:59:18 -04:00
b8d8cf19d9 Add some info about automatic backups 2021-10-09 16:48:20 -04:00
c9be4093e7 Smaller font size for reader page indicator (closes #6071) 2021-10-09 16:48:02 -04:00
082eef708f Add warnings when library and download queues are considered large (closes #5950)
Arbitrarily set at a size of 100 for now. We could adjust this in the future as appropriate if needed.
2021-10-09 14:55:21 -04:00
9106fc5b94 Grouped chapter download list by source (#5575) 2021-10-09 11:41:45 -04:00
918502742d [5893] - Implemented Language Badge (#6050)
* Implemented language badge on library items

* Added left margin for better viewing the badge on list view

* Adjusting borders on badges, cleaning string interpolation and cleaning code

* Improving readability on Holders and removing unused background and text on grid items
2021-10-09 11:10:36 -04:00
f32f1eeaa5 Manga description adjustments (#6011)
* Manga description adjustments

- Animated state changes
- Adjust scrim position to fully show 2 lines when shrunk
- Set minLines to avoid scrim hiding oneliner

* Change icon and adjust animation

* Revert fancy scrim animation
2021-10-09 11:02:45 -04:00
2d1404d155 Fix Local and Other lang sources showing up in seperate Other Categories (#6024)
* group LocalSource and Other lang source together

* use better kotlin syntactic sugar

* add lang "other" to local

* remove duplicate LocalSource entries in Browse

* linting

* revert unnecessary linting

was a manually adding change

* Revert previous two commit but not the most recent

This reverts commit 30250f2f82fc5e38a1b30c7b55c445efec23a114
This also reversts commit 359ed5a8cda91577216b593a4138280e971e0126

* better way to avoid duplicate Local Sources

* more linting by plugin

* `""` lang is no longer used anywhere
2021-10-09 11:01:22 -04:00
a56997e98c Hide slider tooltip label everywhere 2021-10-09 10:44:08 -04:00
ef918078d1 Update dependencies 2021-10-09 10:40:30 -04:00
7e61900cf5 Add new build type for weekly preview (#6067)
This adds new build type for minified non-debuggable preview builds.
"debugFull" is removed and "debug" will be unminified.

**It means preview build action needs to be updated to build "standardPreview"**
2021-10-09 10:28:43 -04:00
e98f90b099 [6059] - Pending downloads count on Download queue screen (#6064)
* Updating the download queue label to account for pending downloads even on paused state

* changing separator

* Created observer to update the TitleBar of the controller to reflect pending downloads

* Reverting changes from MoreController that were made in an another commit

* Refactoring updateTitle method
2021-10-07 22:13:35 -04:00
2e127dff1f Replace Timber with Square Logcat and make logging configurable (#6062)
* Replace Timber with Square Logcat

* Configurable logger
2021-10-07 22:12:55 -04:00
828db19e02 [5753] - Add pending downloads count on Download queue (#6049)
* Updating the download queue label to account for pending downloads even on paused state

* changing separator
2021-10-06 22:03:07 -04:00
99aa3f5713 Weblate translations (#6046)
Co-authored-by: AHmed HarBy <themagic1093@gmail.com>
Co-authored-by: Afiq Nazrie <afnazrie@gmail.com>
Co-authored-by: Ainārs Lapkovskis <ainarslapkovskis@gmail.com>
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Druvvaldis <druvvaldisr@gmail.com>
Co-authored-by: Emerson Nunes <emerson.nunes.ds@gmail.com>
Co-authored-by: Emma Jane Bonestell <EmmaJaneBonestell@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Fernando Sanchez <cheeze.sprinkels@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Francesco Zanella <franzghosts@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hassay Ádám Tamás <hassayadam@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 <jakubfabijan@tuta.io>
Co-authored-by: Junan Chk <junanchakma2000@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kaleb <kalebcarvalho1@gmail.com>
Co-authored-by: Krishna Chand <krishna_chand67@naver.com>
Co-authored-by: LoneHash <sameepsk2@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Miguel Alexandro Manzano Guerra <kuro_eis@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nishant Bodkhe <nishantbodkhe44@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redy Apriyadi <redy.apriyadi@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Sieg Jaeger <zekerett@gmail.com>
Co-authored-by: Steven Pedroza <stevenpedroza56@gmail.com>
Co-authored-by: Temporary Person <TemporaryPerson@protonmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: ZiomaleQ <r.partyka30@gmail.com>
Co-authored-by: crackheadakira <lasn.mine@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: julptk <julptk8@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: phannhanhn201 <phannhanhn201@gmail.com>
Co-authored-by: rytis sertvytis <knysliukas2002@gmail.com>
Co-authored-by: soplatnik <jestapom@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: พรหมชัย ชูแสง <promchai2sin@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/aii/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/lt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/mr/
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/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/
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/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: AHmed HarBy <themagic1093@gmail.com>
Co-authored-by: Afiq Nazrie <afnazrie@gmail.com>
Co-authored-by: Ainārs Lapkovskis <ainarslapkovskis@gmail.com>
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Druvvaldis <druvvaldisr@gmail.com>
Co-authored-by: Emerson Nunes <emerson.nunes.ds@gmail.com>
Co-authored-by: Emma Jane Bonestell <EmmaJaneBonestell@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Fernando Sanchez <cheeze.sprinkels@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Francesco Zanella <franzghosts@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hassay Ádám Tamás <hassayadam@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 <jakubfabijan@tuta.io>
Co-authored-by: Junan Chk <junanchakma2000@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kaleb <kalebcarvalho1@gmail.com>
Co-authored-by: Krishna Chand <krishna_chand67@naver.com>
Co-authored-by: LoneHash <sameepsk2@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Miguel Alexandro Manzano Guerra <kuro_eis@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nishant Bodkhe <nishantbodkhe44@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redy Apriyadi <redy.apriyadi@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Sieg Jaeger <zekerett@gmail.com>
Co-authored-by: Steven Pedroza <stevenpedroza56@gmail.com>
Co-authored-by: Temporary Person <TemporaryPerson@protonmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: ZiomaleQ <r.partyka30@gmail.com>
Co-authored-by: crackheadakira <lasn.mine@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: julptk <julptk8@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: phannhanhn201 <phannhanhn201@gmail.com>
Co-authored-by: rytis sertvytis <knysliukas2002@gmail.com>
Co-authored-by: soplatnik <jestapom@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: พรหมชัย ชูแสง <promchai2sin@gmail.com>
2021-10-05 11:09:05 -04:00
1a568e2961 Fix stuck display mode for when not using per category setting (#6044) 2021-10-05 11:03:51 -04:00
e863e8c64b Adjust Wi-Fi connection check (related to #6038) 2021-10-04 17:06:24 -04:00
f5b591430c Release v0.12.3 2021-10-04 15:55:06 -04:00
8cfaf8eb51 Weblate translations (#5913)
Co-authored-by: AHmed HarBy <themagic1093@gmail.com>
Co-authored-by: Ainārs Lapkovskis <ainarslapkovskis@gmail.com>
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Druvvaldis <druvvaldisr@gmail.com>
Co-authored-by: Emerson Nunes <emerson.nunes.ds@gmail.com>
Co-authored-by: Emma Jane Bonestell <EmmaJaneBonestell@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Fernando Sanchez <cheeze.sprinkels@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Francesco Zanella <franzghosts@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hassay Ádám Tamás <hassayadam@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 <jakubfabijan@tuta.io>
Co-authored-by: Junan Chk <junanchakma2000@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kaleb <kalebcarvalho1@gmail.com>
Co-authored-by: Krishna Chand <krishna_chand67@naver.com>
Co-authored-by: LoneHash <sameepsk2@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Miguel Alexandro Manzano Guerra <kuro_eis@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nishant Bodkhe <nishantbodkhe44@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redy Apriyadi <redy.apriyadi@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Sieg Jaeger <zekerett@gmail.com>
Co-authored-by: Steven Pedroza <stevenpedroza56@gmail.com>
Co-authored-by: Temporary Person <TemporaryPerson@protonmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: ZiomaleQ <r.partyka30@gmail.com>
Co-authored-by: crackheadakira <lasn.mine@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: julptk <julptk8@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: phannhanhn201 <phannhanhn201@gmail.com>
Co-authored-by: rytis sertvytis <knysliukas2002@gmail.com>
Co-authored-by: soplatnik <jestapom@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: พรหมชัย ชูแสง <promchai2sin@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/aii/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/lt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/lv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/mr/
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/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/
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/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: AHmed HarBy <themagic1093@gmail.com>
Co-authored-by: Ainārs Lapkovskis <ainarslapkovskis@gmail.com>
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: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Blue <bluestuffish@gmail.com>
Co-authored-by: Christian Elbrianno <christian.elbrianno41@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Druvvaldis <druvvaldisr@gmail.com>
Co-authored-by: Emerson Nunes <emerson.nunes.ds@gmail.com>
Co-authored-by: Emma Jane Bonestell <EmmaJaneBonestell@gmail.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Fernando Sanchez <cheeze.sprinkels@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Francesco Zanella <franzghosts@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hassay Ádám Tamás <hassayadam@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 <jakubfabijan@tuta.io>
Co-authored-by: Junan Chk <junanchakma2000@gmail.com>
Co-authored-by: K. Sz. Bence <tudi20@protonmail.com>
Co-authored-by: Kaleb <kalebcarvalho1@gmail.com>
Co-authored-by: Krishna Chand <krishna_chand67@naver.com>
Co-authored-by: LoneHash <sameepsk2@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@gmail.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: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Miguel Alexandro Manzano Guerra <kuro_eis@hotmail.com>
Co-authored-by: Milo Ivir <mail@milotype.de>
Co-authored-by: Nishant Bodkhe <nishantbodkhe44@gmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pedro <pedro-mediavilla@hotmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Redy Apriyadi <redy.apriyadi@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Sieg Jaeger <zekerett@gmail.com>
Co-authored-by: Steven Pedroza <stevenpedroza56@gmail.com>
Co-authored-by: Temporary Person <TemporaryPerson@protonmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: ZiomaleQ <r.partyka30@gmail.com>
Co-authored-by: crackheadakira <lasn.mine@gmail.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: julptk <julptk8@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: phannhanhn201 <phannhanhn201@gmail.com>
Co-authored-by: rytis sertvytis <knysliukas2002@gmail.com>
Co-authored-by: soplatnik <jestapom@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: พรหมชัย ชูแสง <promchai2sin@gmail.com>
2021-10-04 15:41:54 -04:00
675c0cefc3 Fix crash in single-page chapters 2021-10-04 11:06:23 -04:00
1a52385b78 Formatting 2021-10-04 10:50:13 -04:00
372e500590 Remove extra padding when using list with Per Category setting (#5997)
* Remove padding when using list with Per Category setting (fixes #5636)

* Add view type to RecyclerViewPagerAdapter

Correctly this time (ノ◕ヮ◕)ノ*:・゚✧

* Minor tweaks
2021-10-04 10:41:20 -04:00
cc1a317439 enable "ALL" in Browse by default (#6023)
some extensions, including self-hosted ones, have the "ALL" label and
sometimes users get confused with not having enabled "ALL" after
installing new extensions
2021-10-03 15:40:51 -04:00
6d650518a1 App-wide typography adjustments (#5931)
* Manga detail

Also adjust chapter item layout to accommodate bigger
display/font size

* Library

* Updates

* History

* Browse

* Preferences

* Button

* Navigation view

* category-download

* Google Sans

* Reader

* Chips

* Revert "Google Sans"

This reverts commit 5dd4c41f

* Misc

* Cleanups

* Section header text appearance

* Increase library manga title size

* Revert "Increase library manga title size"

This reverts commit 474be913

* Increase section header letter spacing

* Derps
2021-10-03 12:32:04 -04:00
7940117577 Sort and remove duplicates in genres (#6021)
* Sort and remove duplicates in genres

Co-authored-by: ivaniskandar <12537387+ivaniskandar@users.noreply.github.com>

* Remove Sort and filter out blank genre

Co-authored-by: ivaniskandar <12537387+ivaniskandar@users.noreply.github.com>
2021-10-03 12:19:37 -04:00
b0f87fdd21 LicensesController: Move item init to IO thread (#6020) 2021-10-03 12:00:00 -04:00
dc92ffed87 Switch to Material Slider in color filter settings 2021-10-03 11:58:52 -04:00
4af578e310 Apply navigation bar insets to fast scroller and settings search list (#6015) 2021-10-03 11:28:20 -04:00
e22825d818 Check if wifi is connected rather than enabled while downloading. (#5967)
* Fixxy Wixxy

* Downgrade check from Android S to Android Q
2021-10-03 11:27:56 -04:00
e2da6259e7 Update AboutLib plugin 2021-10-03 11:14:56 -04:00
d149017c60 Switch to Material Slider for reader seekbar
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-10-03 11:14:49 -04:00
afc400121b Update dependencies 2021-10-01 18:28:02 -04:00
ef993515c6 Fix MangaController toolbar title showing when editing category (#6005) 2021-10-01 17:52:06 -04:00
edb1d21ddc Don't bury sort menu in overflow in Migrate screen 2021-10-01 17:41:14 -04:00
ba8abd94a8 Ability to order sources by library count when migrating (#6000)
* order sources by library count when migrating (closes #4703)

* Use plain menu instead of full-on sheet
2021-10-01 17:37:43 -04:00
c6d4e4c15f Move extensions enabled languages on top (closes #5694) (#5998) 2021-10-01 09:15:04 -04:00
09f0ac866f Fix incorrect appbar lift state when opening MangaController in hidden state (#5990) 2021-10-01 09:13:00 -04:00
7ed25704d6 Add chapter bookmarking feature to Updates screen (#5984) 2021-10-01 08:11:31 -04:00
2196dac63e Fix variable name in isOnline (#5991) 2021-10-01 08:09:46 -04:00
c8f70efded ReaderActivity: Block focus on viewer (#5996) 2021-10-01 08:09:36 -04:00
ea97488670 Revert parseAs inline function change
Some people sometimes get compile issues?
2021-09-30 17:52:07 -04:00
c2255b0a0f Mark installer names as non-translatable 2021-09-25 21:08:31 -04:00
f754b081ce Use data class to parse extensions list 2021-09-25 14:57:54 -04:00
07771cb5e4 Update kotlinx.serialization 2021-09-25 14:41:48 -04:00
690d8e43ae Show message in migrate screen if library is empty 2021-09-25 14:41:35 -04:00
82f14a7d59 Hide soft keyboard after submitting search query throughout app (#5837)
* Clear focus from SearchView when submitting a search query in BrowseSourceController

* Revert "Clear focus from SearchView when submitting a search query"

* Implement SearchView focus clearing in Tachiyomi's subclass to enable feature throughout app

* Add support for keyboard Enter key

Pressing enter on a keyboard (when using the emulator for example) now also submits the query
2021-09-25 14:32:19 -04:00
b284384f0a Implement new extension install methods (#5904)
* Implement new extension install methods

* Fixes

* Resolve feedback

* Keep pending status when waiting to install

* Cancellable installation

* Remove auto error now that we have cancellable job
2021-09-25 14:31:52 -04:00
1ae0d1b5d0 Reattach after slight delay instead on every db update (#5956) 2021-09-23 18:45:55 -04:00
9de08c8166 Update dependencies 2021-09-20 14:33:35 -04:00
a2d007f2a9 Toolbar and bottom nav scroll snap (#5915) 2021-09-18 16:41:23 -04:00
774f818bbb Fix setting search re-animating on activity recreation (fixes #5882) 2021-09-18 16:28:58 -04:00
0ec7121b8f Adjust snackbar durations (closes #5932) 2021-09-18 16:17:07 -04:00
d7d46f4447 Minor cleanup 2021-09-18 16:13:14 -04:00
45fad147bf Remove spaces at end of line before removing multiple new lines (#5928) 2021-09-18 15:16:03 -04:00
3664195c71 rewrite getFormat the kotlin way (#5930) 2021-09-18 15:15:38 -04:00
fce3cd00a1 Remove setting to disable update error notifications and split out notification channel
Users can exclude things from updating if needed, or disable the notification channel from system settings.
2021-09-17 19:14:30 -04:00
33b3be0d0e Move extension app info button
Aligns with TachiyomiJ2K.
2021-09-16 17:57:41 -04:00
cfd1b4a6c6 Fix toolbar title alpha (#5910) 2021-09-16 17:39:13 -04:00
d45fefd6f0 handle maxNumberSort from API (#5917) 2021-09-16 17:37:42 -04:00
f125ab01ee Change how the bottom navigation is hidden (#5823)
* Change how the bottom navigation is hidden

Modifies the translationY instead of the height.

* Cleanups
2021-09-16 17:37:17 -04:00
be001d090c [skip ci] Update issue closer to ignore myanimelist (#5911)
Not sure if there's any limitation for the regex but this will ignore myanimelist strings, in practice.
2021-09-14 11:50:21 -04:00
971d8a7e40 Allow preferences to multi-line (#5905) 2021-09-13 18:39:14 -04:00
a2cf210a52 Unify NSFW flagging for sources/extensions
Since multisource extensions are no longer a thing, we now simply rely on the flag at the extension level, i.e. the per-Source/SourceFactory `@Nsfw` annotation is no longer checked.
We'll have to remove all of the annotation usages from the existing sources, which will also effectively break the setting for older versions of the app.
2021-09-13 17:49:58 -04:00
3eec207166 Release v0.12.2 2021-09-13 15:10:41 -04:00
b5d83bdb56 Weblate translations (#5852)
Co-authored-by: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Ahmed gamal <12355.ahmedgamal.com@gmail.com>
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: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.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: Long Nguyễn Khánh <khanhlong17112000@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Maciej Sładkiewicz <krecio555@gmail.com>
Co-authored-by: Madddog1997 <madddog1997@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Nguyễn Thanh Bình <sikea0801@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Scoop <Scoo0p@yandex.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tymofii Lytvynenko <till.svit@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/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/es/
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/it/
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/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/sk/
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/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: Adolfo Jayme Barrientos <fitojb@ubuntu.com>
Co-authored-by: Ahmed gamal <12355.ahmedgamal.com@gmail.com>
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: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Forqen <krecio555@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: Long Nguyễn Khánh <khanhlong17112000@gmail.com>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Madddog1997 <madddog1997@gmail.com>
Co-authored-by: Marco Santos <enum.scima@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Nguyễn Thanh Bình <sikea0801@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Scoop <Scoo0p@yandex.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tymofii Lytvynenko <till.svit@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
2021-09-13 15:05:24 -04:00
2c495c4119 Don't count "other" as a language. (#5901) 2021-09-13 15:05:12 -04:00
7c72d6cb7c Fix scroller getting dragged incorrectly in RTL (fixes #5496) 2021-09-12 13:08:21 -04:00
8362bf0886 Don't show option to delete chapters for local manga (closes #5243) 2021-09-12 13:01:53 -04:00
1a8155c45b Add link to help translate in about 2021-09-12 12:52:19 -04:00
3f2f946019 Update ExtensionPresenter.kt (#5895) 2021-09-12 09:34:37 -04:00
2c14a8dee1 Minor cleanup for download delete exclusion 2021-09-11 18:39:34 -04:00
917a283bd1 Fix manga info expand button background 2021-09-11 18:36:49 -04:00
3e403d5ab3 Opt out of WebView metrics and disable Google Safe Browsing
cf. https://developer.android.com/guide/webapps/managing-webview
2021-09-11 18:29:55 -04:00
746d35b52b Reuse reader's image view in MangaFullCoverDialog (#5824)
* MangaFullCoverDialog: Support animated drawable

* Scaled zoom duration

* Wrap reader's image view to be reused in MangaFullCoverDialog

* Cleanups

* Forgot animated stuff for webtoon view

* Cleanups

* Oopsie

* Cleanups

* Consistent max scale for SubsamplingScaleImageView

The max scale will be obtained from the default scale times 3 for
consistent 3x zoom scale.
2021-09-11 18:28:54 -04:00
9a7a03e327 Change ProtoNumber of Backup Models for History and Source to a non-zero digit (#5849)
* Change ProtoNumber of Backup Models for History and Source to non-zero

Changed BackupHistory url and BackupSource name properties

* Provide backwards compatibility to current proto backups

- Added data class for zero-based protoNumber
- Restore both 'new' proto and old ones by mapping old to 'new' proto format
- Thanks to @jobobby04  for providing the initial solution.

* Fix on createBackup missing parameter for brokenSource

* Fix issues on build

* Fix missing import on FullBackupRestore
2021-09-11 18:10:10 -04:00
a051079c6a Allow exclusion on Delete After Read per category (#5857)
* Added the exclude category from delete after being read

* Stopped it from adding a wildcard to the import

* Placed the remove after read to the download manager
2021-09-11 18:09:24 -04:00
7b3c18bb97 Less hacky way to make sure bottom action toolbar doesn't scroll down (#5871)
* Less hacky way to make sure bottom action toolbar doesn't scroll down

* Fix action toolbar overlapping on landscape

* Disable app bar transparency when ActionMode is present
2021-09-11 10:22:01 -04:00
52daf3d58c During migration, only do MangaController replacement if previous controller is also MangaController (#5869)
If previous controller is instead a MigrationController/other, push the new MangaController onto the stack instead
2021-09-11 10:21:12 -04:00
f41bde5ee1 MangaController: Fix listeners cancelled when pushing new controller within (#5883) 2021-09-11 10:20:52 -04:00
6151318ac1 use chapter_number instead of ordinal index for syncChaptersWithTrackServiceTwoWay (#5846)
use v2 api for Komga tracker for series
2021-09-09 21:07:16 -04:00
b45c322729 MangaController: Title fixes (#5879)
* MangaController: Move toolbar's TextView reference to ElevationAppBarLayout

* MangaController: Update title alpha earlier when exiting
2021-09-09 21:05:41 -04:00
b00e8768dc Disable action mode status bar guard (#5872) 2021-09-09 21:03:53 -04:00
156feb6e8e Use "isOnline" utils in DownloadService (#5863)
* Use isOnline

* when -> if/else
2021-09-06 12:31:03 -04:00
e942b8a402 Read from streams for local source manga details and legacy backups 2021-09-06 11:54:00 -04:00
abdb67a123 Remove the remaining MotionLayout (#5854)
* Remove the remaining MotionLayout

* Use ImageButton instead of Blank View to handle taps in dead area

And some tweaks
2021-09-06 11:46:38 -04:00
ee20787c5e Retain GLUtil.maxTextureSize 2021-09-05 14:34:54 -04:00
ec4e631760 Clean up some companion object usages 2021-09-05 14:34:29 -04:00
02b430a5bf Skip bookmark check when cancelling downloads (#5853)
* Skip bookmark check when cancelling downloads

* DownloadManager: simplified filteredChapters declaration

* Completed documentation of DownloadManager's deleteChapters()
2021-09-04 22:43:56 -04:00
7878053df2 Fix crash in settings search (fixes #5855) 2021-09-04 22:31:25 -04:00
12a593c3c6 Ensure all fields in new migrated manga are persisted (fixes #5848) 2021-09-04 19:05:43 -04:00
6b1f130750 Adjust padding of themes preference 2021-09-04 18:55:51 -04:00
bde4c0a648 Avoid multiline library badges
Related to #5725
2021-09-04 18:51:30 -04:00
5ae4621da1 Queue tracking updates when offline (closes #1497)
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-09-04 16:37:35 -04:00
5ea8d0546e Fix chapters getting deleted when marking as unread from library (fixes #5755) 2021-09-04 15:29:13 -04:00
8a064c118f Minor cleanup 2021-09-04 15:27:37 -04:00
2f91c27df2 Don't allow focus on reader containers (closes #5727) 2021-09-04 15:23:00 -04:00
763bd54707 Hide language tag when only one language is used (#5834)
* Hide lang tag when only one lang used

* Comment the code

Can't be too useless and do nothing, Ghostbear practically wrote the entire PR for me

* Exclude 'all' from counting as a language

Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com>

* Use existing Preferences directly from Presenter

* Replace regex with an existing value

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
2021-09-04 15:04:40 -04:00
0ea3cc7ce4 Weblate translations (#5670)
Co-authored-by: Ahmed gamal <12355.ahmedgamal.com@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gianna E <giannela.e@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: IRFAN SHADIK <irfanshadikofficial@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: KIRA <belguareh1@gmail.com>
Co-authored-by: Karlo Orioli <korioli1@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@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: Marian Leontiev <leontievmarian@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Nikita Epifanov <nikgreens@protonmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Sebastian Skoczek <sebastians17@onet.pl>
Co-authored-by: Sebs11_B <chevabermudezcastillo@gmail.com>
Co-authored-by: Shippo <shiposhouyou@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tmp341 <tmp341@gmail.com>
Co-authored-by: Tymofii Lytvynenko <till.svit@gmail.com>
Co-authored-by: Wise <phxwise@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: anenasa <anenasaa@yahoo.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 신민준 <sinmin70@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/be/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
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/es/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es_419/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ko/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
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/
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: Ahmed gamal <12355.ahmedgamal.com@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alessandro Jean <alessandrojean@gmail.com>
Co-authored-by: Alifian Caesar <alifiancaesar@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Gianna E <giannela.e@gmail.com>
Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: IRFAN SHADIK <irfanshadikofficial@gmail.com>
Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Co-authored-by: Jendrej <ejjendrej@gmail.com>
Co-authored-by: Jetspectre <jetspectre1@gmail.com>
Co-authored-by: KIRA <belguareh1@gmail.com>
Co-authored-by: Karlo Orioli <korioli1@gmail.com>
Co-authored-by: Luna Jernberg <droidbittin@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: Marian Leontiev <leontievmarian@gmail.com>
Co-authored-by: Matyáš Caras <contact@hernikplays.cz>
Co-authored-by: Nikita Epifanov <nikgreens@protonmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Ric <rikku.debec@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Sebastian Skoczek <sebastians17@onet.pl>
Co-authored-by: Sebs11_B <chevabermudezcastillo@gmail.com>
Co-authored-by: Shippo <shiposhouyou@gmail.com>
Co-authored-by: Shjosan <shjosan@kakmix.co>
Co-authored-by: Tmp341 <tmp341@gmail.com>
Co-authored-by: Tymofii Lytvynenko <till.svit@gmail.com>
Co-authored-by: Wise <phxwise@gmail.com>
Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Co-authored-by: Zero O <godarms2010@live.com>
Co-authored-by: anenasa <anenasaa@yahoo.com>
Co-authored-by: dmswd <Bmswad1@gmail.com>
Co-authored-by: monolifed <monolifed@protonmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 신민준 <sinmin70@gmail.com>
2021-09-04 12:05:25 -04:00
0de3558ab3 Retain scroll position when selecting app theme preference
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-09-04 11:58:44 -04:00
069f4e12d8 Rearrange themes, rename "Blue" to "Legacy Blue" 2021-09-04 11:50:00 -04:00
ae4dfc9956 Reword advanced tablet UI setting 2021-09-04 11:22:17 -04:00
ee711dc0fb Edit mangas' Categories in Library using TriState list (#5422)
* Use QuadState Categories to edit mangas in Library

Add updateMangasToCategories to build build correct Categories list for
  each manga using Common and Mix list
Update QuadState Multi-Choice to either Action or Display List
  Display list would have different state sequece from Action
  Uncheck-> Indeterminate (only if initial so)-> Check

fixup manga categories logic as Windows and push request comments

* fixup: Use QuadStateTextView.State enum

Update function to use  QuadStateTextView.State enum that missed in last change

* fixup: missing closing bracket and type cast

Co-authored-by: quangkieu <quangkieu1993@gmail.com>
2021-09-04 11:13:19 -04:00
c316e7faab Migrate to flow version of ReactiveNetwork 2021-09-04 10:38:12 -04:00
7083b3d912 Don't show update progress notifications if job isn't active anymore (closes #5844) 2021-09-04 10:24:55 -04:00
2d3a1b6a9e Update dependencies 2021-09-04 10:09:33 -04:00
0df23ab878 Tablet UI override (#5830)
* Tablet UI override

* Tablet UI advanced pref
2021-09-04 10:06:56 -04:00
7ed8de2ef4 Remove autoSizeText (#5850)
Apparently it produces unexpected results in combination with 'wrap_content'.
2021-09-04 10:05:24 -04:00
d935e22f0d Add status icons to manga info (#5832)
* Add icons to manga status

* Slightly better formatting

Mixed in with a dose of syntactic sugar

* Remove unnecessary lines

I think they are, at least

* Change according to review comments

- Fix forgotten Tablet code removal
- Change 'android:background' to 'app:srcCompat'

* Adjust size of icon

Smaller and more fitting to the environment
2021-09-02 17:57:54 -04:00
0e26abf7a6 Use ShapeableImageView for rounded thumbnails instead of Coil transformations 2021-08-31 22:35:52 -04:00
59aef13200 Improve placement of manga title section (#5796)
Also makes content expand logically through the help of constraint barriers.
2021-08-31 17:53:37 -04:00
9d1f6c4416 Update Material Components 2021-08-31 17:51:24 -04:00
b9f7660a91 Added a getting started guide action for when the library is empty
Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
2021-08-31 17:51:15 -04:00
18b5250ed1 Fix MangaController's loading view initial position (#5827)
Make sure the loading view is hidden before updating the offset.
2021-08-31 17:49:57 -04:00
f683f21ee2 Trim line breaks in manga info only when collapsed (#5818)
* don't trim newlines if summary expanded

* move description trim logic to separate function

* logic error oops

* let's try something

* fix bug on first load

makes it so that, description text is trimmed when entering manga from
library

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

Co-authored-by: Andreas <andreas.everos@gmail.com>
2021-08-31 17:44:33 -04:00
bd033db84c Fix animated image detection (#5826) 2021-08-31 17:43:29 -04:00
ab036312a4 Handle small cover better (#5815) 2021-08-29 11:13:48 -04:00
634da15191 Update kitsu to not show "null" for empty descriptions
(cherry picked from commit e6ea530532523ed033fd232fcea2da8f17b973f7)
2021-08-29 10:03:40 -04:00
cea1720ea0 Make appearance settings searchable (fixes #5814) 2021-08-29 09:53:36 -04:00
3f2f542265 Fix divider color in AMOLED mode (fixes #5778) 2021-08-28 17:41:36 -04:00
b77edb2b5b Fix crash when tapping title of "App theme" preference 2021-08-28 17:31:30 -04:00
1b699bb814 Fix reader action sheet not opening 2021-08-28 17:24:42 -04:00
333c035fed Clean up reader action sheet layout 2021-08-28 17:22:41 -04:00
ce29914c56 Update build release wording 2021-08-28 17:22:28 -04:00
70e5361146 Update save icon 2021-08-28 16:59:44 -04:00
e7d6dfff53 Replace MotionLayout with full screen dialog (#5806)
* Remove MotionLayout and add full screen dialog for enlarged cover

* Address some of the review comments
2021-08-28 16:53:59 -04:00
eebfad5a95 Register TachiyomiImageDecoder after built-in Coil decoders
Not sure if this is related to #5702.
2021-08-28 12:29:11 -04:00
77c0a93ac6 Tweak theme preference item UI 2021-08-28 12:28:15 -04:00
63a3e126b3 Rename Layout category to Navigation 2021-08-28 12:08:11 -04:00
3ea84cf0ce Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled (#5801)
* Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled

Tested with Gboard only.

* Revert "Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled"

This reverts commit 068399db

* Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text inputs when incognito is enabled

Source preference is not affected.

* Source preference stuff
2021-08-28 12:06:29 -04:00
7fa80ae556 Only update chapter/viewer flags for library manga instead of everything (addresses #5809) 2021-08-28 12:02:08 -04:00
925f71af15 Clean up track button changes 2021-08-28 11:19:38 -04:00
c666dd623d Tracking: replace tick with button (#5768)
* make check only visible after selecting an item

* replace tick with button and send to bottom

* fixed button visibility

* grey btn out

* resolving some bits

* removing the tick from appbar

* remove useless lines, appl insetter
2021-08-28 11:13:09 -04:00
2cd8733212 change Track.last_chapter_read to Float (#5802)
each TrackService can convert it to Int if decimal chapters are not supported
2021-08-28 10:37:45 -04:00
4b2a9bc621 Clean up imports 2021-08-27 16:45:59 -04:00
12a9d0575d Use more Compat utilities (#5786)
* Use ActivityCompat.recreate

* Use more KTX extensions

* Use PackageInfoCompat.getLongVersionCode

* Remove unnecessary compat usages
2021-08-27 16:33:12 -04:00
edcfa28b0b Tweak theme preference item UI (closes #5805) 2021-08-27 16:25:03 -04:00
3155829994 Replace Wi-Fi connection check with WifiManager
Previous implementation couldn't detect Wi-Fi connection while using a VPN.
2021-08-27 15:41:47 -04:00
d25707554e Fix shadow behind the Expand Info icon (#5804) 2021-08-27 15:34:41 -04:00
38df44ef4b Fix crash caused by missing line in #5794 (#5803)
Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com>

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
2021-08-27 14:05:09 -04:00
df683375b1 Apply system animation scale to parts of Tachiyomi that don't respect it by default (#5794)
* Add initial code for scaling animations, apply scale to reader nav overlay

* Rename extension function, apply system animator scale to ActionToolbar

* Apply system animator scale to expanding manga cover animation

* Apply system animator scale to image crossfade (also disables animated covers when browsing)

* Add documentation, make MotionScene Transition comment a bit more clear

* Disable animated covers in MangaInfoHeaderAdapter if animator duration scale is 0

* Disable animated covers in Library if animator duration scale is 0

* Convert loadAny listener to extension function
2021-08-27 08:44:09 -04:00
cc3cbbc4bb Update Kotlin and Kotlinter 2021-08-26 22:13:53 -04:00
6922394b8e Replace NetworkInfo with NetworkCapabilities (#5785) 2021-08-26 22:09:40 -04:00
24fd82d773 Use NotificationChannelCompat utilities (#5781) 2021-08-26 22:08:27 -04:00
57aefcd917 New manga info expander (#5771)
* Replace "More" with Arrows

We used to have arrows but it was set away from the description which took a lot of space.

It was changed to "More" text, but with the recent design changes I think it'd look better to get a mix between them both.

* Properly align icons

Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com>

* Expand support to Tablets

Get it... expand... hehe 😎

* Fix expanded width

Also fixes so that the constraint for the toggleLess is dependant on the right thing

* Give info toggles its own space

Uses its own margin now to push info rather than just being attached as a info margin.

* Remove weird duplicates I did not add

I did not add these but I don't see a reason to keep dupes

* Add bottom scrim

* Change to centered arrow under info

Anyone wanna experiment/build on top off then feel free to tweak

* Add background glow to icon for contrast

Co-Authored-By: Andreas <6576096+ghostbear@users.noreply.github.com>

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
Co-authored-by: Andreas <andreas.everos@gmail.com>
2021-08-26 22:07:30 -04:00
b3854ad382 Fix reader crash on Android 9 (#5789)
* Fix failed reader context creation on v28

* Re-apply the reader styles manually after overriding night mode

This commit replaces the ThemeCompat.rebase() call since the private API used is
in dark greylist max target P, thus making it unreachable.

* Revert "Fix failed reader context creation on v28"

This reverts commit 6e2104d7
2021-08-25 17:27:34 -04:00
5f5fc77877 Fix toolbar text color in light blue theme 2021-08-23 17:31:16 -04:00
0493e77cff Split out appearance settings from general section 2021-08-23 12:24:30 -04:00
6240fe1dfc Update app theme preference UI
Heavily influenced by TachiyomiJ2K.
2021-08-23 12:11:13 -04:00
beb7f90908 Make nav overlay non-clickable (maybe fixes #5727) 2021-08-22 19:25:40 -04:00
a3917972b4 Update deprecated Android Gradle DSL calls 2021-08-22 18:05:18 -04:00
7094fef37f Update tracker services logo layout (closes #5625) 2021-08-22 16:48:08 -04:00
0f41e56a24 [skip ci] Move acknowledgements to bottom of issue templates 2021-08-22 15:45:11 -04:00
52b283283f Revert "Hardcode bottom nav height (fixes #5698)"
This reverts commit ebb15bf96c.
2021-08-22 15:40:13 -04:00
ebb15bf96c Hardcode bottom nav height (fixes #5698)
This shouldn't be an issue since the spec defines the height to always be 56dp anyway.
2021-08-22 14:17:28 -04:00
6c527d52fb Use custom tabs instead of browser (closes #5754) 2021-08-22 14:16:54 -04:00
b8ea57e097 Minor cleanup 2021-08-22 14:00:07 -04:00
909aed4262 Fix blue background under action mode text selection handlers in blue theme 2021-08-22 12:25:19 -04:00
4d2fff9538 Update release workflow to handle multiple ABI variants 2021-08-22 12:15:53 -04:00
9a45983f17 Update dependencies 2021-08-22 11:48:57 -04:00
11926014da Bold author and artist fields (#5770) 2021-08-22 11:07:18 -04:00
72002c13d6 Tweak MangaInfoHeader (#5766)
Make margin between transparent toolbar and cover more match 1.x
Fixes from when view was redone with MotionLayout
2021-08-21 19:09:19 -04:00
6ed767ae84 Move PR template 2021-08-21 19:08:38 -04:00
3826b307f7 Add a much more clean design to Chips (#5765)
Based on the default chips and what is seen on J2K/Neko
2021-08-21 18:23:46 -04:00
887b157056 Add haptic feedback to reader page slider (#5763) 2021-08-21 18:05:57 -04:00
d36dd39743 Add a Pull Request template (#5764) 2021-08-21 18:05:44 -04:00
dd008bc13a Adjust blue theme 2021-08-21 18:05:08 -04:00
50b282f58b update Anilist tracking title during refresh (#5760)
Co-authored-by: Andreas <andreas.everos@gmail.com>

Co-authored-by: Andreas <andreas.everos@gmail.com>
2021-08-21 10:51:20 -04:00
f8a7efbce7 Update jsoup 2021-08-20 22:42:21 -04:00
7d2caeb270 Minor cleanup 2021-08-20 22:42:16 -04:00
708e71a35a Use user preferred title language in Anilist (#5758)
* Use user preferred title language in Anilist

Since Anilist is only used by authenticated users, the title language
can be set using the `userPreferred` field (defaults to romaji)

Changed wherever `title>romaji` was being used. Shouldn't have missed
any. `userPreferred` is also available for Staff and Character but not
relevant to Tachiyomi for now.

Users might need to go Logout and log back in on Anilist to see the
change. Actual setting can be found at https://anilist.co/settings/media

closes https://github.com/tachiyomiorg/tachiyomi/issues/5757

* correct title in anilist model

indicates the fact that userPreferred title is used

* convert forgotten `type` to `format` as well

leads to NPE when using `findLibManga`.
missed one query in https://github.com/tachiyomiorg/tachiyomi/pull/5741
2021-08-20 18:20:04 -04:00
4eaccc966e Hide reader progress indicator right away (#5750)
The image will be drawn over it so the animation won't be visible anyway
2021-08-19 18:12:31 -04:00
3670d649b8 Make default category translatable (#5751)
* Make default category translatable

* Replace duplicate strings with common one
2021-08-19 18:10:43 -04:00
90ab04e81d Require authentication-confirmation to change biometric lock settings (#5695)
* Requires authentication-confirmation to change biometric lock settings

* Prevent double authentications on older APIs when confirming settings changes

* Use new AuthPrompt API for app lock

With this commit, the app lock will only explicitly require Class 2 biometrics
or screen lock credential. Class 3 biometrics are guaranteed to meet Class 2
requirements thus will also be used when available.

* Use extension toast
2021-08-19 18:10:07 -04:00
26b8df5354 Partial revert 914b686c8e (#5749)
Didn't mean to remove this line, this fixes resuming to read downloaded chapter.
2021-08-19 11:53:26 -04:00
11a8046c5f PagerPageHolder: Move chooseBackground call to IO thread (#5737)
* ImageUtil.chooseBackground: Use built-in decoder

* PagerPageHolder: Move chooseBackground call to IO thread

Also move stuffs and reuse image stream as bytes
2021-08-19 09:15:45 -04:00
da16110e1c Edge-to-edge manga details view (#5613)
* Prepare for edge-to-edge MangaController

* Fix derpy liftToScroll with our own implementation

* Edge-to-edge MangaController

Except when legacy blue theme is used.

* Save app bar lift state for controller backstack

* Fix expanded cover position after the view recycled

* Handle overlap changes when incognito mode disabled

* Tablet fixes

* Revert "Handle overlap changes when incognito mode disabled"

This reverts commit 1f492449

Breaks on rotation changes.

* Fix MangaController's swipe refresh position

* All controllers are now doing lift app bar on scroll by default

They are already doing that before so this pretty much just a cleanups.

* TachiyomiCoordinatorLayout: Support ViewPager for app bar lift state check

I'm willing to revert this if this minute detail solution is deemed too hacky xD

* Fix app bar not lifted when scrolled without fling

* Save app bar lift state across configuration changes

* Fix MangaController's swipe refresh position after configuration change

* TachiyomiCoordinatorLayout: Update ViewPager reference when controller is changed
2021-08-19 09:12:52 -04:00
914b686c8e ReaderTransitionView: Use context theme color for texts (#5738)
* Put themed reader context in adapter

This avoids creating themed context everytime the page holder is created, this
also allows the transition view to use the same themed context.

* Check against app night mode to create themed reader context

* ReaderTransitionView: Use context theme color for texts

The whole reader will need to be recreated when changing reader background while
webtoon mode is used, because recreating just the RecyclerView without messing
up the scroll position is impossible (I hope I just missed something).
2021-08-19 09:10:05 -04:00
27133520fc Label one-shots correctly in anilist track search (#5741)
* use format instead of type in anilist search

As per anilist graphql docs, `type` refers to whether anime/manga and is
redundant (since we already limit it to `MANGA`). What we actually want
is `format` which includes whether the media is a One-shot or Manga

This should help in making search a bit better as one no longer needs to
rely on the Date to figure out if its the One-shot entry or the Manga
entry

* Revert "use format instead of type in anilist search"

This reverts commit 6f0ba4888669f414a2093d7632eb1fab109d74de.

Accidentally changed the wrong query while further testing whoops

* use format instead of type in anilist search

As per anilist graphql docs, `type` refers to whether anime/manga and is
redundant (since we already limit it to `MANGA`). What we actually want
is `format` which includes whether the media is a One-shot or Manga

Changes search query and corresponding JSONALManga structure
2021-08-19 09:08:48 -04:00
24b967ad5c Fix start/resume fab showing up when entering and then exiting action mode (#5735) 2021-08-17 22:44:35 -04:00
ca4b4a3f1e [skip ci] Replace deprecated argument for gradle command action 2021-08-17 22:40:34 -04:00
faef35ec47 Add check for current controller before setting extension update tab badge (#5733) 2021-08-16 11:58:30 -04:00
326d4c2641 Fix today still being displayed even though relative time is off (#5732) 2021-08-16 10:28:40 -04:00
83436c9550 add Theme "Teal & Turquoise" (#5681)
* add Theme "Teal & Turquoise"

* re sorting & change tertiary dark

* use alpha on ripple color & capitalize
2021-08-16 10:15:28 -04:00
2084822731 Fix library icon unchecked state (#5728)
Path data from AVD version
2021-08-16 10:15:10 -04:00
071bad1232 Use separate string for toRelativeString "Today"
Apparently 0 quantity is ignored for some locales...
2021-08-15 17:42:31 -04:00
ae1a76da2b Use toRelativeString in Updates and History headings 2021-08-15 17:42:06 -04:00
fbc6965c4e Update google-services.json with latest version from Firebase Console 2021-08-15 17:08:43 -04:00
57a5862840 Use relative time in ChapterHolder (#5719)
* Use relative time in ChapterHolder

Similar to how J2K does it

* Use custom implementation for relative time

* Changes based on review comments
2021-08-15 17:07:48 -04:00
91fbccdbaa Allow FilterList to be passed with default values (#5716) 2021-08-15 17:06:32 -04:00
0ab0dd95ae DNS-over-HTTPS (Adguard) (#5715)
* Update DohProviders.kt

* Update NetworkHelper.kt

* Update SettingsAdvancedController.kt

* fix typo

* Fix typo

* Fix typo
2021-08-15 11:20:52 -04:00
bc41040fd3 [skip ci] Split push and PR build workflows so they don't cancel each other 2021-08-15 11:07:27 -04:00
4c8dfd0c0c Add toggle to invert page color in reader color filter settings (#5713) 2021-08-15 10:58:01 -04:00
2b9dbfb390 Fix global search menu item title 2021-08-15 10:53:52 -04:00
84d546b724 Set expanded cover dimension ratio from the source image (#5721)
Avoids cropping.
2021-08-15 10:53:25 -04:00
63053b9940 Update menu icons in Browse Sources view (closes #5397) 2021-08-15 10:46:04 -04:00
2256030a2a Don't allow focus on ReaderNavigationOverlayView (maybe fixes #5555) 2021-08-15 10:42:45 -04:00
79da33b597 Open tracker page when clicking logo (closes #5624) 2021-08-15 10:38:10 -04:00
7d67450e58 Always re-setup background jobs on migration runs 2021-08-13 18:28:07 -04:00
8aa11951bf Do background app/extension checks less frequently
Since the in-app checks occur at least once a day anyway.
2021-08-13 18:24:21 -04:00
f23f22ab01 Add in-app app update check 2021-08-13 18:18:53 -04:00
96a64c7bd2 Update dependencies 2021-08-13 18:18:22 -04:00
d1bb0fdf1d Apply app theme styling to reader page errors and progress dialog (#5705) 2021-08-13 15:44:42 -04:00
feca30d7ed Fix selector in search card item (#5711) 2021-08-13 15:44:25 -04:00
b650151693 [skip ci] Update documentation 2021-08-10 18:10:46 -04:00
bb3afd0dc9 Update to Contributor Covenant 2.1 (#5697) 2021-08-10 13:30:54 -04:00
5e77ae208d Use correct color for reader loading indicator (#5685)
* Revert "Revert "Use correct color for reader loading indicator (#5645)" (fixes #5669)"

This reverts commit a4eba50c

* Fix crash on older APIs
2021-08-09 17:48:28 -04:00
24e5a4d7ec Enable elevation overlay for MaterialSwitch (#5686) 2021-08-09 17:47:57 -04:00
1d10d29fa9 Replace AboutLibraries activity with custom controller 2021-08-07 11:50:20 -04:00
9b00e91773 Reorganize dependencies a bit 2021-08-07 10:50:50 -04:00
cd73c30d6f Remove explicit CardView dependency 2021-08-07 10:50:40 -04:00
7bbba0c7d9 Update Duktape 2021-08-07 10:50:26 -04:00
7907a4fc24 Add ability to tweak auto hide sensitivity in Webtoon Reader (#5650)
* Tweak threshold

* Put setting under Webtoon instead

Because it only affects Webtoon related viewers
2021-08-07 10:34:47 -04:00
2f94f62a56 Merge branch 'fix-12' into master
# Conflicts:
#	.github/ISSUE_TEMPLATE.md
#	.github/ISSUE_TEMPLATE/report_issue.yml
#	.github/ISSUE_TEMPLATE/request_feature.yml
2021-08-06 17:33:24 -04:00
03980b2f27 Remove ability to set in-app language differently from system's 2021-08-06 16:45:40 -04:00
664e5cfb59 [skip ci] Update issue templates 2021-08-06 15:43:24 -04:00
b9736df7e0 Re-enable preview build things 2021-08-06 15:38:16 -04:00
575 changed files with 19225 additions and 10481 deletions

View File

@ -3,7 +3,7 @@
I acknowledge that:
- I have updated:
- To the latest version of the app (stable is v0.12.1)
- To the latest version of the app (stable is v0.12.3)
- 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

View File

@ -3,57 +3,6 @@ description: Report an issue in Tachiyomi
labels: [Bug]
body:
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.12.1](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: input
id: tachiyomi-version
attributes:
label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**.
placeholder: |
Example: "0.11.1"
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: You can find this somewhere in your Android settings.
placeholder: |
Example: "Android 11"
validations:
required: true
- type: input
id: device
attributes:
label: Device
description: List your device and model.
placeholder: |
Example: "Google Pixel 5"
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
@ -98,9 +47,60 @@ body:
placeholder: |
You can paste the crash logs in pure text or upload it as an attachment.
- type: input
id: tachiyomi-version
attributes:
label: Tachiyomi version
description: You can find your Tachiyomi version in **More → About**.
placeholder: |
Example: "0.12.3"
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: You can find this somewhere in your Android settings.
placeholder: |
Example: "Android 11"
validations:
required: true
- type: input
id: device
attributes:
label: Device
description: List your device and model.
placeholder: |
Example: "Google Pixel 5"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have tried the [troubleshooting guide](https://tachiyomi.org/help/guides/troubleshooting/).
required: true
- label: I have updated the app to version **[0.12.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I have updated all installed extensions.
required: true
- label: I will fill out all of the requested information in this form.
required: true

View File

@ -3,23 +3,6 @@ description: Suggest a feature to improve Tachiyomi
labels: [Feature request]
body:
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have updated the app to version **[0.12.1](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
- type: textarea
id: feature-description
attributes:
@ -37,3 +20,20 @@ body:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose).
required: true
- label: I have updated the app to version **[0.12.3](https://github.com/tachiyomiorg/tachiyomi/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

12
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,12 @@
<!--
Please include a summary of the change and which issue is fixed.
Also make sure you've tested your code and also done a self-review of it.
Don't forget to check all base themes and tablet mode for relevant changes.
If your changes are visual, please provide images below:
### Images
| Image 1 | Image 2 |
| ------- | ------- |
| ![](https://github.githubassets.com/images/modules/logos_page/Octocat.png) | ![](https://github.githubassets.com/images/modules/logos_page/Octocat.png) |
-->

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

View File

@ -0,0 +1,33 @@
name: PR build check
on:
pull_request:
paths-ignore:
- '**.md'
- 'app/src/main/res/**/strings.xml'
jobs:
build:
name: Build app
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Copy CI gradle.properties
run: |
mkdir -p ~/.gradle
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app
uses: gradle/gradle-command-action@v2
with:
arguments: assembleStandardRelease

View File

@ -5,34 +5,25 @@ on:
- master
tags:
- v*
pull_request:
jobs:
check_wrapper:
name: Validate Gradle Wrapper
build:
name: Build app
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
all_but_latest: true
- name: Clone repo
uses: actions/checkout@v2
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
build:
name: Build app
needs: check_wrapper
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.5.0
with:
access_token: ${{ github.token }}
- name: Clone repo
uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
@ -44,24 +35,18 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build app
uses: eskatos/gradle-command-action@v1
uses: gradle/gradle-command-action@v2
with:
arguments: assembleStandardRelease
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
# Sign APK and create release for tags
- name: Get tag name
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
id: get_tag_name
run: |
set -x
echo "VERSION_TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
# TODO: need to support multiple APKs
- name: Sign APK
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
uses: r0adkll/sign-android-release@v1
@ -75,9 +60,23 @@ jobs:
- name: Clean up build artifacts
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
run: |
cp ${{ env.SIGNED_RELEASE_FILE }} tachiyomi-${{ env.VERSION_TAG }}.apk
md5=`md5sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
echo "APK_MD5=$md5" >> $GITHUB_ENV
set -e
mv app/build/outputs/apk/standard/release/app-standard-universal-release-unsigned-signed.apk tachiyomi-${{ env.VERSION_TAG }}.apk
sha=`sha256sum tachiyomi-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
echo "APK_UNIVERSAL_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned-signed.apk tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
sha=`sha256sum tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
echo "APK_ARM64_V8A_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-armeabi-v7a-release-unsigned-signed.apk tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
sha=`sha256sum tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
echo "APK_ARMEABI_V7A_SHA=$sha" >> $GITHUB_ENV
cp app/build/outputs/apk/standard/release/app-standard-x86-release-unsigned-signed.apk tachiyomi-x86-${{ env.VERSION_TAG }}.apk
sha=`sha256sum tachiyomi-x86-${{ env.VERSION_TAG }}.apk | awk '{ print $1 }'`
echo "APK_X86_SHA=$sha" >> $GITHUB_ENV
- name: Create Release
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'tachiyomiorg/tachiyomi'
@ -86,9 +85,21 @@ jobs:
tag_name: ${{ env.VERSION_TAG }}
name: Tachiyomi ${{ env.VERSION_TAG }}
body: |
MD5: ${{ env.APK_MD5 }}
---
### Checksums
| Variant | SHA-256 |
| ------- | ------- |
| Universal | ${{ env.APK_UNIVERSAL_SHA }}
| arm64-v8a | ${{ env.APK_ARM64_V8A_SHA }}
| armeabi-v7a | ${{ env.APK_ARMEABI_V7A_SHA }}
| x86 | ${{ env.APK_X86_SHA }} |
files: |
tachiyomi-${{ env.VERSION_TAG }}.apk
tachiyomi-arm64-v8a-${{ env.VERSION_TAG }}.apk
tachiyomi-armeabi-v7a-${{ env.VERSION_TAG }}.apk
tachiyomi-x86-${{ env.VERSION_TAG }}.apk
draft: true
prerelease: false
env:

View File

@ -0,0 +1,16 @@
name: Cancel old pull request workflows
on:
workflow_run:
workflows: ["PR build check"]
types:
- requested
jobs:
cancel:
runs-on: ubuntu-latest
steps:
- uses: styfle/cancel-workflow-action@0.9.1
with:
all_but_latest: true
workflow_id: ${{ github.event.workflow.id }}

View File

@ -25,7 +25,7 @@ jobs:
},
{
"type": "both",
"regex": ".*(aniyomi|anime).*",
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
"ignoreCase": true,
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
}

View File

@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1.1
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -3,7 +3,7 @@ name: Lock threads
on:
# Daily
schedule:
- cron: '0 * * * *'
- cron: '0 0 * * *'
# Manual trigger
workflow_dispatch:
inputs:
@ -12,8 +12,8 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: '2'
pr-lock-inactive-days: '2'
issue-inactive-days: '2'
pr-inactive-days: '2'

View File

@ -1,76 +1,126 @@
# Code of Conduct
# Contributor Covenant 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.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
Examples of behavior that contributes to a positive environment for our
community 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
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior by participants include:
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or 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
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Our Responsibilities
## Enforcement 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.
Community moderators are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
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.
Community moderators 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, and will communicate reasons for moderation
decisions when appropriate.
## 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.
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## 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.
reported to the community moderators responsible for enforcement at
the [Tachiyomi Discord server](https://discord.gg/tachiyomi).
All complaints will be reviewed and investigated promptly and fairly.
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.
All community moderators are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community moderators will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community moderators, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## 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
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
version 2.1, available at
[v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
[homepage]: https://www.contributor-covenant.org
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
For answers to common questions about this code of conduct, see the FAQ at
[FAQ](https://www.contributor-covenant.org/faq). Translations are available
at [translations](https://www.contributor-covenant.org/translations).

View File

@ -10,6 +10,7 @@ Thanks for your interest in contributing to Tachiyomi!
Pull requests are welcome!
If you're interested in taking on [an open issue](https://github.com/tachiyomiorg/tachiyomi/issues), please comment on it so others are aware.
You do not need to ask for permission nor an assignment.
# Translations

View File

@ -6,8 +6,6 @@
# ![app icon](./.github/readme-images/app-icon.png)Tachiyomi
Tachiyomi is a free and open source manga reader for Android 6.0 and above.
![screenshots of app](./.github/readme-images/screens.png)
## Features
Features include:
@ -38,7 +36,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
<details><summary>Bugs</summary>
* Include version (More > About > Version)
* Include version (More About Version)
* If not latest, try updating, it may have already been solved
* Preview version is equal to the number of commits as seen in the main page
* Include steps to reproduce (if not obvious from description)

View File

@ -28,14 +28,14 @@ android {
applicationId = "eu.kanade.tachiyomi"
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
versionCode = 67
versionName = "0.12.1"
versionCode = 73
versionName = "0.13.0"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
buildConfigField("String", "BUILD_TIME", "\"${getBuildTime()}\"")
buildConfigField("boolean", "INCLUDE_UPDATER", "false")
buildConfigField("boolean", "PREVIEW", "false")
// Please disable ACRA or use your own instance in forked versions of the project
buildConfigField("String", "ACRA_URI", "\"https://tachiyomi.kanade.eu/crash_report\"")
@ -43,11 +43,13 @@ android {
ndk {
abiFilters += SUPPORTED_ABIS
}
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
splits {
abi {
isEnable = false
isEnable = true
reset()
include(*SUPPORTED_ABIS.toTypedArray())
isUniversalApk = true
@ -58,28 +60,28 @@ android {
named("debug") {
versionNameSuffix = "-${getCommitCount()}"
applicationIdSuffix = ".debug"
isShrinkResources = true
isMinifyEnabled = true
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
}
create("debugFull") { // Debug without R8
initWith(getByName("debug"))
isShrinkResources = false
isMinifyEnabled = false
}
named("release") {
isShrinkResources = true
isMinifyEnabled = true
proguardFiles("proguard-android-optimize.txt", "proguard-rules.pro")
}
create("preview") {
initWith(getByName("release"))
buildConfigField("boolean", "PREVIEW", "true")
val debugType = getByName("debug")
signingConfig = debugType.signingConfig
versionNameSuffix = debugType.versionNameSuffix
applicationIdSuffix = debugType.applicationIdSuffix
}
}
sourceSets {
getByName("debugFull").res.srcDirs("src/debug/res")
getByName("preview").res.srcDirs("src/debug/res")
}
flavorDimensions("default")
flavorDimensions.add("default")
productFlavors {
create("standard") {
@ -87,18 +89,22 @@ android {
dimension = "default"
}
create("dev") {
resConfigs("en", "xxhdpi")
resourceConfigurations.addAll(listOf("en", "xxhdpi"))
dimension = "default"
}
}
packagingOptions {
exclude("META-INF/DEPENDENCIES")
exclude("LICENSE.txt")
exclude("META-INF/LICENSE")
exclude("META-INF/LICENSE.txt")
exclude("META-INF/NOTICE")
exclude("META-INF/*.kotlin_module")
resources.excludes.addAll(listOf(
"META-INF/DEPENDENCIES",
"LICENSE.txt",
"META-INF/LICENSE",
"META-INF/LICENSE.txt",
"META-INF/README.md",
"META-INF/NOTICE",
"META-INF/*.kotlin_module",
"META-INF/*.version",
))
}
dependenciesInfo {
@ -107,12 +113,17 @@ android {
buildFeatures {
viewBinding = true
// Disable some unused things
aidl = false
renderScript = false
shaders = false
}
lint {
disable("MissingTranslation", "ExtraTranslation")
isAbortOnError = false
isCheckReleaseBuilds = false
disable.addAll(listOf("MissingTranslation", "ExtraTranslation"))
abortOnError = false
checkReleaseBuilds = false
}
compileOptions {
@ -126,10 +137,9 @@ android {
}
dependencies {
implementation(kotlin("reflect", version = BuildPluginsVersion.KOTLIN))
val coroutinesVersion = "1.5.1"
val coroutinesVersion = "1.6.0"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
@ -137,73 +147,68 @@ dependencies {
implementation("org.tachiyomi:source-api:1.1")
// AndroidX libraries
implementation("androidx.annotation:annotation:1.3.0-alpha01")
implementation("androidx.appcompat:appcompat:1.4.0-alpha03")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha03")
implementation("androidx.browser:browser:1.3.0")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.0")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.1.0")
implementation("androidx.core:core-ktx:1.7.0-alpha01")
implementation("androidx.core:core-splashscreen:1.0.0-alpha01")
implementation("androidx.preference:preference-ktx:1.1.1")
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation("androidx.annotation:annotation:1.4.0-alpha01")
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("androidx.biometric:biometric-ktx:1.2.0-alpha04")
implementation("androidx.browser:browser:1.4.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
implementation("androidx.core:core-ktx:1.8.0-alpha02")
implementation("androidx.core:core-splashscreen:1.0.0-alpha02")
implementation("androidx.recyclerview:recyclerview:1.3.0-alpha01")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01")
implementation("androidx.viewpager:viewpager:1.1.0-alpha01")
val lifecycleVersion = "2.4.0-alpha01"
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
val lifecycleVersion = "2.4.0"
implementation("androidx.lifecycle:lifecycle-common:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-process:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
// Job scheduling
implementation("androidx.work:work-runtime-ktx:2.6.0-beta01")
implementation("androidx.work:work-runtime-ktx:2.6.0")
// UI library
implementation("com.google.android.material:material:1.5.0-alpha01")
"standardImplementation"("com.google.firebase:firebase-core:19.0.0")
// ReactiveX
// RX
implementation("io.reactivex:rxandroid:1.2.1")
implementation("io.reactivex:rxjava:1.3.8")
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
implementation("com.github.pwittchen:reactivenetwork:0.13.0")
implementation("ru.beryukhov:flowreactivenetwork:1.0.4")
// Network client
val okhttpVersion = "4.9.1"
implementation("com.squareup.okhttp3:okhttp:$okhttpVersion")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttpVersion")
implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:$okhttpVersion")
implementation("com.squareup.okio:okio:2.10.0")
implementation("com.squareup.okio:okio:3.0.0")
// TLS 1.3 support for Android < 10
implementation("org.conscrypt:conscrypt-android:2.5.2")
// JSON
val kotlinSerializationVersion = "1.2.2"
// Data serialization (JSON, protobuf)
val kotlinSerializationVersion = "1.3.2"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
implementation("com.google.code.gson:gson:2.8.7")
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
// JavaScript engine
implementation("com.squareup.duktape:duktape-android:1.3.0")
implementation("app.cash.quickjs:quickjs-android:0.9.2")
// TODO: remove Duktape once all extensions are using QuickJS
implementation("com.squareup.duktape:duktape-android:1.4.0")
// HTML parser
implementation("org.jsoup:jsoup:1.14.3")
// Disk
implementation("com.jakewharton:disklrucache:2.0.2")
implementation("com.github.tachiyomiorg:unifile:17bec43")
implementation("com.github.junrar:junrar:7.4.0")
// HTML parser
implementation("org.jsoup:jsoup:1.14.1")
// Database
implementation("androidx.sqlite:sqlite-ktx:2.1.0")
implementation("androidx.sqlite:sqlite-ktx:2.2.0")
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
implementation("com.github.requery:sqlite-android:3.36.0")
// Preferences
implementation("androidx.preference:preference-ktx:1.2.0-rc01")
implementation("com.github.tfcporciuncula.flow-preferences:flow-preferences:1.4.0")
// Model View Presenter
@ -214,8 +219,8 @@ dependencies {
// Dependency injection
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
// Image library
val coilVersion = "1.3.2"
// Image loading
val coilVersion = "1.4.0"
implementation("io.coil-kt:coil:$coilVersion")
implementation("io.coil-kt:coil-gif:$coilVersion")
@ -224,26 +229,23 @@ dependencies {
}
implementation("com.github.tachiyomiorg:image-decoder:7481a4a")
// Logging
implementation("com.jakewharton.timber:timber:4.7.1")
// Crash reports
implementation("ch.acra:acra-http:5.8.1")
// Sort
implementation("com.github.gpanther:java-nat-sort:natural-comparator-1.1")
// UI
// UI libraries
implementation("com.google.android.material:material:1.6.0-alpha02")
implementation("com.github.dmytrodanylyk.android-process-button:library:1.0.4")
implementation("eu.davidea:flexible-adapter:5.1.0")
implementation("eu.davidea:flexible-adapter-ui:1.0.0")
implementation("com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533")
implementation("com.github.arkon.FlexibleAdapter:flexible-adapter-ui:c8013533")
implementation("com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.1.0")
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0")
implementation("dev.chrisbanes.insetter:insetter:0.6.0")
implementation("com.github.tachiyomiorg:DirectionalViewPager:1.0.0") {
exclude(group = "androidx.viewpager", module = "viewpager")
}
implementation("dev.chrisbanes.insetter:insetter:0.6.1")
// Conductor
val conductorVersion = "3.0.0"
val conductorVersion = "3.1.2"
implementation("com.bluelinelabs:conductor:$conductorVersion")
implementation("com.bluelinelabs:conductor-viewpager:$conductorVersion")
implementation("com.github.tachiyomiorg:conductor-support-preference:$conductorVersion")
@ -256,8 +258,20 @@ dependencies {
implementation("io.github.reactivecircus.flowbinding:flowbinding-swiperefreshlayout:$flowbindingVersion")
implementation("io.github.reactivecircus.flowbinding:flowbinding-viewpager:$flowbindingVersion")
// Logging
implementation("com.squareup.logcat:logcat:0.1")
// Crash reports/analytics
implementation("ch.acra:acra-http:5.8.4")
"standardImplementation"("com.google.firebase:firebase-analytics-ktx:20.0.2")
// Licenses
implementation("com.mikepenz:aboutlibraries:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
implementation("com.mikepenz:aboutlibraries-core:${BuildPluginsVersion.ABOUTLIB_PLUGIN}")
// Shizuku
val shizukuVersion = "12.1.0"
implementation("dev.rikka.shizuku:api:$shizukuVersion")
implementation("dev.rikka.shizuku:provider:$shizukuVersion")
// Tests
testImplementation("junit:junit:4.13.2")
@ -278,12 +292,12 @@ tasks {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.Experimental",
"-Xopt-in=kotlin.RequiresOptIn",
"-Xuse-experimental=kotlin.ExperimentalStdlibApi",
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi",
"-Xuse-experimental=coil.annotation.ExperimentalCoilApi",
"-Xopt-in=kotlin.ExperimentalStdlibApi",
"-Xopt-in=kotlinx.coroutines.FlowPreview",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-Xopt-in=coil.annotation.ExperimentalCoilApi",
)
}

View File

@ -12,6 +12,7 @@
-keep,allowoptimization class com.google.gson.** { public protected *; }
-keep,allowoptimization class com.github.salomonbrys.kotson.** { public protected *; }
-keep,allowoptimization class com.squareup.duktape.** { public protected *; }
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
##---------------Begin: proguard configuration for RxJava 1.x ----------

View File

@ -18,6 +18,7 @@
<!-- For managing extensions -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
<!-- To view extension packages in API 30+ -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
@ -177,7 +178,7 @@
android:exported="false" />
<service
android:name=".data.updater.UpdaterService"
android:name=".data.updater.AppUpdateService"
android:exported="false" />
<service
@ -188,6 +189,9 @@
android:name=".data.backup.BackupRestoreService"
android:exported="false" />
<service android:name=".extension.util.ExtensionInstallService"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
@ -198,6 +202,19 @@
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:multiprocess="false"
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="false" />
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
android:value="true" />
</application>
</manifest>

View File

@ -12,46 +12,50 @@ import android.webkit.WebView
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.util.DebugLogger
import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.notification
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.AndroidLogcatLogger
import logcat.LogPriority
import logcat.LogcatLogger
import org.acra.config.httpSender
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.conscrypt.Conscrypt
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.security.Security
open class App : Application(), LifecycleObserver, ImageLoaderFactory {
open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val preferences: PreferencesHelper by injectLazy()
private val disableIncognitoReceiver = DisableIncognitoReceiver()
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
super<Application>.onCreate()
// TLS 1.3 support for Android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
@ -109,30 +113,33 @@ open class App : Application(), LifecycleObserver, ImageLoaderFactory {
}
)
}.launchIn(ProcessLifecycleOwner.get().lifecycleScope)
if (!LogcatLogger.isInstalled && preferences.verboseLogging()) {
LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE))
}
}
override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply {
componentRegistry {
add(TachiyomiImageDecoder(this@App.resources))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder(this@App))
} else {
add(GifDecoder())
}
add(TachiyomiImageDecoder(this@App.resources))
add(ByteBufferFetcher())
add(MangaCoverFetcher())
}
okHttpClient(Injekt.get<NetworkHelper>().coilClient)
crossfade(300)
crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
if (preferences.verboseLogging()) logger(DebugLogger())
}.build()
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
@Suppress("unused")
fun onAppBackgrounded() {
if (preferences.lockAppAfter().get() >= 0) {
override fun onStop(owner: LifecycleOwner) {
if (!AuthenticatorUtil.isAuthenticating && preferences.lockAppAfter().get() >= 0) {
SecureActivityDelegate.locked = true
}
}
@ -152,7 +159,11 @@ open class App : Application(), LifecycleObserver, ImageLoaderFactory {
}
protected open fun setupNotificationChannels() {
Notifications.createChannels(this)
try {
Notifications.createChannels(this)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { "Failed to modify notification channels" }
}
}
private inner class DisableIncognitoReceiver : BroadcastReceiver() {
@ -176,8 +187,6 @@ open class App : Application(), LifecycleObserver, ImageLoaderFactory {
}
}
}
companion object {
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
}
}
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi
/**
* Used by extensions.
*
* @since extension-lib 1.3
*/
object AppInfo {
fun getVersionCode() = BuildConfig.VERSION_CODE
fun getVersionName() = BuildConfig.VERSION_NAME
}

View File

@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
@ -23,6 +24,8 @@ class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)
addSingletonFactory { Json { ignoreUnknownKeys = true } }
addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(app) }
@ -41,7 +44,7 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { TrackManager(app) }
addSingletonFactory { Json { ignoreUnknownKeys = true } }
addSingletonFactory { DelayedTrackingStore(app) }
// Asynchronously init expensive components for a faster cold start
ContextCompat.getMainExecutor(app).execute {

View File

@ -5,16 +5,19 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.data.updater.AppUpdateJob
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.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.util.preference.minusAssign
import eu.kanade.tachiyomi.util.preference.plusAssign
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import uy.kohesive.injekt.Injekt
@ -32,30 +35,29 @@ object Migrations {
fun upgrade(preferences: PreferencesHelper): Boolean {
val context = preferences.context
// Cancel app updater job for debug builds that don't include it
if (BuildConfig.DEBUG && !BuildConfig.INCLUDE_UPDATER) {
UpdaterJob.cancelTask(context)
}
val oldVersion = preferences.lastVersionCode().get()
if (oldVersion < BuildConfig.VERSION_CODE) {
preferences.lastVersionCode().set(BuildConfig.VERSION_CODE)
// Always set up background tasks to ensure they're running
if (BuildConfig.INCLUDE_UPDATER) {
AppUpdateJob.setupTask(context)
}
ExtensionUpdateJob.setupTask(context)
LibraryUpdateJob.setupTask(context)
BackupCreatorJob.setupTask(context)
// Fresh install
if (oldVersion == 0) {
// Set up default background tasks
if (BuildConfig.INCLUDE_UPDATER) {
UpdaterJob.setupTask(context)
}
ExtensionUpdateJob.setupTask(context)
LibraryUpdateJob.setupTask(context)
return false
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (oldVersion < 14) {
// Restore jobs after upgrading to Evernote's job scheduler.
if (BuildConfig.INCLUDE_UPDATER) {
UpdaterJob.setupTask(context)
AppUpdateJob.setupTask(context)
}
LibraryUpdateJob.setupTask(context)
}
@ -88,7 +90,7 @@ object Migrations {
if (oldVersion < 43) {
// Restore jobs after migrating from Evernote's job scheduler to WorkManager.
if (BuildConfig.INCLUDE_UPDATER) {
UpdaterJob.setupTask(context)
AppUpdateJob.setupTask(context)
}
LibraryUpdateJob.setupTask(context)
BackupCreatorJob.setupTask(context)
@ -98,8 +100,6 @@ object Migrations {
}
if (oldVersion < 44) {
// Reset sorting preference if using removed sort by source
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
@Suppress("DEPRECATION")
@ -111,7 +111,6 @@ object Migrations {
}
if (oldVersion < 52) {
// Migrate library filters to tri-state versions
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
fun convertBooleanPrefToTriState(key: String): Int {
val oldPrefValue = prefs.getBoolean(key, false)
return if (oldPrefValue) ExtendedNavigationView.Item.TriStateGroup.State.INCLUDE.value
@ -140,7 +139,6 @@ object Migrations {
}
if (oldVersion < 57) {
// Migrate DNS over HTTPS setting
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
if (wasDohEnabled) {
prefs.edit {
@ -151,7 +149,6 @@ object Migrations {
}
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)
@ -160,17 +157,16 @@ object Migrations {
// Disable update check for Android 5.x users
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
UpdaterJob.cancelTask(context)
AppUpdateJob.cancelTask(context)
}
}
if (oldVersion < 60) {
// Re-enable update check that was prevously accidentally disabled for M
if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT == Build.VERSION_CODES.M) {
UpdaterJob.setupTask(context)
AppUpdateJob.setupTask(context)
}
// 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
@ -199,8 +195,6 @@ object Migrations {
}
}
if (oldVersion < 64) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val oldSortingMode = prefs.getInt(PreferenceKeys.librarySortingMode, 0)
val oldSortingDirection = prefs.getBoolean(PreferenceKeys.librarySortingDirection, true)
@ -232,11 +226,26 @@ object Migrations {
putString(PreferenceKeys.librarySortingDirection, newSortingDirection.name)
}
}
if (oldVersion < 65) {
if (preferences.lang().get() in listOf("en-US", "en-GB")) {
preferences.lang().set("en")
if (oldVersion < 70) {
if (preferences.enabledLanguages().isSet()) {
preferences.enabledLanguages() += "all"
}
}
if (oldVersion < 71) {
// Handle removed every 3, 4, 6, and 8 hour library updates
val updateInterval = preferences.libraryUpdateInterval().get()
if (updateInterval in listOf(3, 4, 6, 8)) {
preferences.libraryUpdateInterval().set(12)
LibraryUpdateJob.setupTask(context, 12)
}
}
if (oldVersion < 72) {
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
if (!oldUpdateOngoingOnly) {
preferences.libraryUpdateMangaRestriction() -= MANGA_ONGOING
}
}
return true
}

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Nsfw

View File

@ -21,7 +21,7 @@ abstract class AbstractBackupManager(protected val context: Context) {
internal val trackManager: TrackManager by injectLazy()
protected val preferences: PreferencesHelper by injectLazy()
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String?
abstract fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String
/**
* Returns manga

View File

@ -14,3 +14,5 @@ abstract class AbstractBackupRestoreValidator {
data class Results(val missingSources: List<String>, val missingTrackers: List<String>)
}
class ValidatorParseException(e: Exception) : RuntimeException(e)

View File

@ -9,6 +9,8 @@ import androidx.work.Worker
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.backup.full.FullBackupManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
@ -24,6 +26,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
FullBackupManager(context).createBackup(uri, flags, true)
Result.success()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.failure()
}
}

View File

@ -139,10 +139,12 @@ class BackupNotifier(private val context: Context) {
val destFile = File(path, file)
val uri = destFile.getUriCompat(context)
val errorLogIntent = NotificationReceiver.openErrorLogPendingActivity(context, uri)
setContentIntent(errorLogIntent)
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_show_errors),
NotificationReceiver.openErrorLogPendingActivity(context, uri)
errorLogIntent,
)
}

View File

@ -13,13 +13,14 @@ import eu.kanade.tachiyomi.data.backup.legacy.LegacyBackupRestore
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import timber.log.Timber
import logcat.LogPriority
/**
* Restores backup.
@ -128,7 +129,7 @@ class BackupRestoreService : Service() {
}
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
logcat(LogPriority.ERROR, exception)
backupRestore?.writeErrorLog()
notifier.showRestoreError(exception.message)

View File

@ -26,11 +26,12 @@ import eu.kanade.tachiyomi.data.database.models.History
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.serialization.protobuf.ProtoBuf
import logcat.LogPriority
import okio.buffer
import okio.gzip
import okio.sink
import timber.log.Timber
import kotlin.math.max
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
@ -43,7 +44,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
* @param uri path of Uri
* @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): String {
// Create root object
var backup: Backup? = null
@ -53,12 +54,14 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
backup = Backup(
backupManga(databaseManga, flags),
backupCategories(),
emptyList(),
backupExtensionInfo(databaseManga)
)
}
var file: UniFile? = null
try {
val file: UniFile = (
file = (
if (isJob) {
// Get dir of file and create
var dir = UniFile.fromUri(context, uri)
@ -83,9 +86,15 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val byteArray = parser.encodeToByteArray(BackupSerializer, backup!!)
file.openOutputStream().sink().gzip().buffer().use { it.write(byteArray) }
return file.uri.toString()
val fileUri = file.uri
// Make sure it's a valid backup file
FullBackupRestoreValidator().validate(context, fileUri)
return fileUri.toString()
} catch (e: Exception) {
Timber.e(e)
logcat(LogPriority.ERROR, e)
file?.delete()
throw e
}
}

View File

@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.backup.full.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.full.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.full.models.BackupManga
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import eu.kanade.tachiyomi.data.backup.full.models.BackupSource
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
@ -33,7 +34,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
}
// Store source mapping for error messages
sourceMapping = backup.backupSources.map { it.sourceId to it.name }.toMap()
var backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.map { it.sourceId to it.name }.toMap()
// Restore individual manga
backup.backupManga.forEach {
@ -62,7 +64,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa
val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories
val history = backupManga.history
val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
val tracks = backupManga.getTrackingImpl()
try {

View File

@ -4,12 +4,14 @@ import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import okio.buffer
import okio.gzip
import okio.source
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
/**
* Checks for critical backup file data.
*
@ -19,14 +21,20 @@ class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
override fun validate(context: Context, uri: Uri): Results {
val backupManager = FullBackupManager(context)
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
val backup = backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
val backup = try {
val backupString =
context.contentResolver.openInputStream(uri)!!.source().gzip().buffer()
.use { it.readByteArray() }
backupManager.parser.decodeFromByteArray(BackupSerializer, backupString)
} catch (e: Exception) {
throw ValidatorParseException(e)
}
if (backup.backupManga.isEmpty()) {
throw Exception(context.getString(R.string.invalid_backup_file_missing_manga))
}
val sources = backup.backupSources.map { it.sourceId to it.name }.toMap()
val sources = backup.backupSources.associate { it.sourceId to it.name }
val missingSources = sources
.filter { sourceManager.get(it.key) == null }
.values

View File

@ -8,5 +8,6 @@ data class Backup(
@ProtoNumber(1) val backupManga: List<BackupManga>,
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
// Bump by 100 to specify this is a 0.x value
@ProtoNumber(100) var backupSources: List<BackupSource> = emptyList(),
@ProtoNumber(100) var backupBrokenSources: List<BrokenBackupSource> = emptyList(),
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList()
)

View File

@ -4,7 +4,13 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupHistory(
data class BrokenBackupHistory(
@ProtoNumber(0) var url: String,
@ProtoNumber(1) var lastRead: Long
)
@Serializable
data class BackupHistory(
@ProtoNumber(1) var url: String,
@ProtoNumber(2) var lastRead: Long
)

View File

@ -33,8 +33,9 @@ data class BackupManga(
// Bump by 100 for values that are not saved/implemented in 1.x but are used in 0.x
@ProtoNumber(100) var favorite: Boolean = true,
@ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList()
) {
fun getMangaImpl(): MangaImpl {
return MangaImpl().apply {

View File

@ -5,9 +5,15 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
@Serializable
data class BackupSource(
data class BrokenBackupSource(
@ProtoNumber(0) var name: String = "",
@ProtoNumber(1) var sourceId: Long
)
@Serializable
data class BackupSource(
@ProtoNumber(1) var name: String = "",
@ProtoNumber(2) var sourceId: Long
) {
companion object {
fun copyFrom(source: Source): BackupSource {

View File

@ -32,8 +32,7 @@ data class BackupTracking(
media_id = this@BackupTracking.mediaId
library_id = this@BackupTracking.libraryId
title = this@BackupTracking.title
// convert from float to int because of 1.x types
last_chapter_read = this@BackupTracking.lastChapterRead.toInt()
last_chapter_read = this@BackupTracking.lastChapterRead
total_chapters = this@BackupTracking.totalChapters
score = this@BackupTracking.score
status = this@BackupTracking.status
@ -51,8 +50,7 @@ data class BackupTracking(
// forced not null so its compatible with 1.x backup system
libraryId = track.library_id!!,
title = track.title,
// convert to float for 1.x
lastChapterRead = track.last_chapter_read.toFloat(),
lastChapterRead = track.last_chapter_read,
totalChapters = track.total_chapters,
score = track.score,
status = track.status,

View File

@ -13,13 +13,12 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.Source
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.jsonPrimitive
import okio.buffer
import okio.source
import java.util.Date
@ -28,8 +27,8 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
override suspend fun performRestore(uri: Uri): Boolean {
// Read the json and create a Json Object,
// cannot use the backupManager json deserializer one because its not initialized yet
val backupObject = Json.decodeFromString<JsonObject>(
context.contentResolver.openInputStream(uri)!!.source().buffer().use { it.readUtf8() }
val backupObject = Json.decodeFromStream<JsonObject>(
context.contentResolver.openInputStream(uri)!!
)
// Get parser version

View File

@ -4,12 +4,12 @@ import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
import eu.kanade.tachiyomi.data.backup.ValidatorParseException
import eu.kanade.tachiyomi.data.backup.legacy.models.Backup
import kotlinx.serialization.decodeFromString
import okio.buffer
import okio.source
import kotlinx.serialization.json.decodeFromStream
class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
/**
* Checks for critical backup file data.
*
@ -19,9 +19,13 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
override fun validate(context: Context, uri: Uri): Results {
val backupManager = LegacyBackupManager(context)
val backup = backupManager.parser.decodeFromString<Backup>(
context.contentResolver.openInputStream(uri)!!.source().buffer().use { it.readUtf8() }
)
val backup = try {
backupManager.parser.decodeFromStream<Backup>(
context.contentResolver.openInputStream(uri)!!
)
} catch (e: Exception) {
throw ValidatorParseException(e)
}
if (backup.version == null) {
throw Exception(context.getString(R.string.invalid_backup_file_missing_data))
@ -53,12 +57,10 @@ class LegacyBackupRestoreValidator : AbstractBackupRestoreValidator() {
companion object {
fun getSourceMapping(extensionsMapping: List<String>): Map<Long, String> {
return extensionsMapping
.map {
val items = it.split(":")
items[0].toLong() to items[1]
}
.toMap()
return extensionsMapping.associate {
val items = it.split(":")
items[0].toLong() to items[1]
}
}
}
}

View File

@ -10,6 +10,7 @@ import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.float
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
@ -46,7 +47,7 @@ open class TrackBaseSerializer<T : Track> : KSerializer<T> {
sync_id = jsonObject[SYNC]!!.jsonPrimitive.int
media_id = jsonObject[MEDIA]!!.jsonPrimitive.int
library_id = jsonObject[LIBRARY]!!.jsonPrimitive.long
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.int
last_chapter_read = jsonObject[LAST_READ]!!.jsonPrimitive.float
tracking_url = jsonObject[TRACKING_URL]!!.jsonPrimitive.content
} as T
}

View File

@ -10,6 +10,7 @@ import coil.network.HttpException
import coil.request.get
import coil.size.Size
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await

View File

@ -20,7 +20,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
/**
* Version of the database.
*/
const val DATABASE_VERSION = 12
const val DATABASE_VERSION = 14
}
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
@ -85,6 +85,15 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
if (oldVersion < 12) {
db.execSQL(MangaTable.addNextUpdateCol)
}
if (oldVersion < 13) {
db.execSQL(TrackTable.renameTableToTemp)
db.execSQL(TrackTable.createTableQuery)
db.execSQL(TrackTable.insertFromTempTable)
db.execSQL(TrackTable.dropTempTable)
}
if (oldVersion < 14) {
db.execSQL(ChapterTable.fixDateUploadIfNeeded)
}
}
override fun onConfigure(db: SupportSQLiteDatabase) {

View File

@ -47,10 +47,10 @@ class CategoryPutResolver : DefaultPutResolver<Category>() {
class CategoryGetResolver : DefaultGetResolver<Category>() {
override fun mapFromCursor(cursor: Cursor): Category = CategoryImpl().apply {
id = cursor.getInt(cursor.getColumnIndex(COL_ID))
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
order = cursor.getInt(cursor.getColumnIndex(COL_ORDER))
flags = cursor.getInt(cursor.getColumnIndex(COL_FLAGS))
id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ID))
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_ORDER))
flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FLAGS))
}
}

View File

@ -63,18 +63,18 @@ class ChapterPutResolver : DefaultPutResolver<Chapter>() {
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
url = cursor.getString(cursor.getColumnIndex(COL_URL))
name = cursor.getString(cursor.getColumnIndex(COL_NAME))
scanlator = cursor.getString(cursor.getColumnIndex(COL_SCANLATOR))
read = cursor.getInt(cursor.getColumnIndex(COL_READ)) == 1
bookmark = cursor.getInt(cursor.getColumnIndex(COL_BOOKMARK)) == 1
date_fetch = cursor.getLong(cursor.getColumnIndex(COL_DATE_FETCH))
date_upload = cursor.getLong(cursor.getColumnIndex(COL_DATE_UPLOAD))
last_page_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_PAGE_READ))
chapter_number = cursor.getFloat(cursor.getColumnIndex(COL_CHAPTER_NUMBER))
source_order = cursor.getInt(cursor.getColumnIndex(COL_SOURCE_ORDER))
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
scanlator = cursor.getString(cursor.getColumnIndexOrThrow(COL_SCANLATOR))
read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_READ)) == 1
bookmark = cursor.getInt(cursor.getColumnIndexOrThrow(COL_BOOKMARK)) == 1
date_fetch = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_FETCH))
date_upload = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_UPLOAD))
last_page_read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_LAST_PAGE_READ))
chapter_number = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_CHAPTER_NUMBER))
source_order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SOURCE_ORDER))
}
}

View File

@ -47,10 +47,10 @@ open class HistoryPutResolver : DefaultPutResolver<History>() {
class HistoryGetResolver : DefaultGetResolver<History>() {
override fun mapFromCursor(cursor: Cursor): History = HistoryImpl().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
chapter_id = cursor.getLong(cursor.getColumnIndex(COL_CHAPTER_ID))
last_read = cursor.getLong(cursor.getColumnIndex(COL_LAST_READ))
time_read = cursor.getLong(cursor.getColumnIndex(COL_TIME_READ))
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
chapter_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_CHAPTER_ID))
last_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_READ))
time_read = cursor.getLong(cursor.getColumnIndexOrThrow(COL_TIME_READ))
}
}

View File

@ -44,9 +44,9 @@ class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
category_id = cursor.getInt(cursor.getColumnIndex(COL_CATEGORY_ID))
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
category_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY_ID))
}
}

View File

@ -22,7 +22,6 @@ 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_INITIALIZED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_NEXT_UPDATE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
@ -63,7 +62,6 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
COL_THUMBNAIL_URL to obj.thumbnail_url,
COL_FAVORITE to obj.favorite,
COL_LAST_UPDATE to obj.last_update,
COL_NEXT_UPDATE to obj.next_update,
COL_INITIALIZED to obj.initialized,
COL_VIEWER to obj.viewer_flags,
COL_CHAPTER_FLAGS to obj.chapter_flags,
@ -74,24 +72,23 @@ class MangaPutResolver : DefaultPutResolver<Manga>() {
interface BaseMangaGetResolver {
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
source = cursor.getLong(cursor.getColumnIndex(COL_SOURCE))
url = cursor.getString(cursor.getColumnIndex(COL_URL))
artist = cursor.getString(cursor.getColumnIndex(COL_ARTIST))
author = cursor.getString(cursor.getColumnIndex(COL_AUTHOR))
description = cursor.getString(cursor.getColumnIndex(COL_DESCRIPTION))
genre = cursor.getString(cursor.getColumnIndex(COL_GENRE))
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
thumbnail_url = cursor.getString(cursor.getColumnIndex(COL_THUMBNAIL_URL))
favorite = cursor.getInt(cursor.getColumnIndex(COL_FAVORITE)) == 1
last_update = cursor.getLong(cursor.getColumnIndex(COL_LAST_UPDATE))
next_update = cursor.getLong(cursor.getColumnIndex(COL_NEXT_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndex(COL_INITIALIZED)) == 1
viewer_flags = cursor.getInt(cursor.getColumnIndex(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndex(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndex(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED))
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
artist = cursor.getString(cursor.getColumnIndexOrThrow(COL_ARTIST))
author = cursor.getString(cursor.getColumnIndexOrThrow(COL_AUTHOR))
description = cursor.getString(cursor.getColumnIndexOrThrow(COL_DESCRIPTION))
genre = cursor.getString(cursor.getColumnIndexOrThrow(COL_GENRE))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
thumbnail_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_THUMBNAIL_URL))
favorite = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FAVORITE)) == 1
last_update = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndexOrThrow(COL_INITIALIZED)) == 1
viewer_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndexOrThrow(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_ADDED))
}
}

View File

@ -65,19 +65,19 @@ class TrackPutResolver : DefaultPutResolver<Track>() {
class TrackGetResolver : DefaultGetResolver<Track>() {
override fun mapFromCursor(cursor: Cursor): Track = TrackImpl().apply {
id = cursor.getLong(cursor.getColumnIndex(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID))
sync_id = cursor.getInt(cursor.getColumnIndex(COL_SYNC_ID))
media_id = cursor.getInt(cursor.getColumnIndex(COL_MEDIA_ID))
library_id = cursor.getLong(cursor.getColumnIndex(COL_LIBRARY_ID))
title = cursor.getString(cursor.getColumnIndex(COL_TITLE))
last_chapter_read = cursor.getInt(cursor.getColumnIndex(COL_LAST_CHAPTER_READ))
total_chapters = cursor.getInt(cursor.getColumnIndex(COL_TOTAL_CHAPTERS))
status = cursor.getInt(cursor.getColumnIndex(COL_STATUS))
score = cursor.getFloat(cursor.getColumnIndex(COL_SCORE))
tracking_url = cursor.getString(cursor.getColumnIndex(COL_TRACKING_URL))
started_reading_date = cursor.getLong(cursor.getColumnIndex(COL_START_DATE))
finished_reading_date = cursor.getLong(cursor.getColumnIndex(COL_FINISH_DATE))
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
sync_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SYNC_ID))
media_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_MEDIA_ID))
library_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LIBRARY_ID))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
last_chapter_read = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_LAST_CHAPTER_READ))
total_chapters = cursor.getInt(cursor.getColumnIndexOrThrow(COL_TOTAL_CHAPTERS))
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
score = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_SCORE))
tracking_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_TRACKING_URL))
started_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_START_DATE))
finished_reading_date = cursor.getLong(cursor.getColumnIndexOrThrow(COL_FINISH_DATE))
}
}

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.data.database.models
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
@ -37,6 +39,6 @@ interface Category : Serializable {
this.name = name
}
fun createDefault(): Category = create("Default").apply { id = 0 }
fun createDefault(context: Context): Category = create(context.getString(R.string.label_default)).apply { id = 0 }
}
}

View File

@ -16,9 +16,6 @@ interface Manga : SManga {
// last time the chapter list changed in any way
var last_update: Long
// predicted next update time based on latest (by date) 4 chapters' deltas
var next_update: Long
var date_added: Long
var viewer_flags: Int
@ -36,7 +33,8 @@ interface Manga : SManga {
}
fun getGenres(): List<String>? {
return genre?.split(", ")?.map { it.trim() }
if (genre.isNullOrBlank()) return null
return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct()
}
private fun setChapterFlags(flag: Int, mask: Int) {

View File

@ -26,8 +26,6 @@ open class MangaImpl : Manga {
override var last_update: Long = 0
override var next_update: Long = 0
override var date_added: Long = 0
override var initialized: Boolean = false

View File

@ -0,0 +1,3 @@
package eu.kanade.tachiyomi.data.database.models
data class SourceIdMangaCount(val source: Long, val count: Int)

View File

@ -16,7 +16,7 @@ interface Track : Serializable {
var title: String
var last_chapter_read: Int
var last_chapter_read: Float
var total_chapters: Int

View File

@ -14,7 +14,7 @@ class TrackImpl : Track {
override lateinit var title: String
override var last_chapter_read: Int = 0
override var last_chapter_read: Float = 0F
override var total_chapters: Int = 0

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.Queries
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.Query
@ -7,7 +8,14 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.*
import eu.kanade.tachiyomi.data.database.models.SourceIdMangaCount
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaTitlePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
@ -64,6 +72,17 @@ interface MangaQueries : DbProvider {
)
.prepare()
fun getSourceIdsWithNonLibraryManga() = db.get()
.listOfObjects(SourceIdMangaCount::class.java)
.withQuery(
RawQuery.builder()
.query(getSourceIdsWithNonLibraryMangaQuery())
.observesTables(MangaTable.TABLE)
.build()
)
.withGetResolver(SourceIdMangaCountGetResolver.INSTANCE)
.prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun insertMangas(mangas: List<Manga>) = db.put().objects(mangas).prepare()
@ -75,7 +94,7 @@ interface MangaQueries : DbProvider {
fun updateChapterFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags, true))
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateViewerFlags(manga: Manga) = db.put()
@ -85,12 +104,7 @@ interface MangaQueries : DbProvider {
fun updateViewerFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags, true))
.prepare()
fun updateNextUpdated(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaNextUpdatedPutResolver())
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare()
fun updateLastUpdated(manga: Manga) = db.put()
@ -117,12 +131,12 @@ interface MangaQueries : DbProvider {
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
fun deleteMangasNotInLibrary() = db.delete()
fun deleteMangasNotInLibraryBySourceIds(sourceIds: List<Long>) = db.delete()
.byQuery(
DeleteQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(0)
.where("${MangaTable.COL_FAVORITE} = ? AND ${MangaTable.COL_SOURCE} IN (${Queries.placeholders(sourceIds.size)})")
.whereArgs(0, *sourceIds.toTypedArray())
.build()
)
.prepare()

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.database.queries
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History
@ -142,3 +143,14 @@ fun getCategoriesForMangaQuery() =
${MangaCategory.TABLE}.${MangaCategory.COL_CATEGORY_ID}
WHERE ${MangaCategory.COL_MANGA_ID} = ?
"""
/** Query to get the list of sources in the database that have
* non-library manga, and how many
*/
fun getSourceIdsWithNonLibraryMangaQuery() =
"""
SELECT ${Manga.COL_SOURCE}, COUNT(*) as ${SourceIdMangaCountGetResolver.COL_COUNT}
FROM ${Manga.TABLE}
WHERE ${Manga.COL_FAVORITE} = 0
GROUP BY ${Manga.COL_SOURCE}
"""

View File

@ -27,9 +27,7 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
.build()
)
val putResult: PutResult
putResult = cursor.use { putCursor ->
cursor.use { putCursor ->
if (putCursor.count == 0) {
val insertQuery = mapToInsertQuery(history)
val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history))
@ -39,25 +37,15 @@ class HistoryLastReadPutResolver : HistoryPutResolver() {
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
}
putResult
}
/**
* Creates update query
* @param obj history object
*/
override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder()
.table(HistoryTable.TABLE)
.where("${HistoryTable.COL_CHAPTER_ID} = ?")
.whereArgs(obj.chapter_id)
.build()
/**
* Create content query
* @param history object
*/
fun mapToUpdateContentValues(history: History) =
private fun mapToUpdateContentValues(history: History) =
contentValuesOf(
HistoryTable.COL_LAST_READ to history.last_read
)

View File

@ -16,8 +16,8 @@ class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGet
val manga = LibraryManga()
mapBaseFromCursor(manga, cursor)
manga.unread = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_UNREAD))
manga.category = cursor.getInt(cursor.getColumnIndex(MangaTable.COL_CATEGORY))
manga.unread = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COL_UNREAD))
manga.category = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COL_CATEGORY))
return manga
}

View File

@ -20,7 +20,7 @@ class MangaChapterGetResolver : DefaultGetResolver<MangaChapter>() {
val manga = mangaGetResolver.mapFromCursor(cursor)
val chapter = chapterGetResolver.mapFromCursor(cursor)
manga.id = chapter.manga_id
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
manga.url = cursor.getString(cursor.getColumnIndexOrThrow("mangaUrl"))
return MangaChapter(manga, chapter)
}

View File

@ -42,7 +42,7 @@ class MangaChapterHistoryGetResolver : DefaultGetResolver<MangaChapterHistory>()
// Make certain column conflicts are dealt with
manga.id = chapter.manga_id
manga.url = cursor.getString(cursor.getColumnIndex("mangaUrl"))
manga.url = cursor.getString(cursor.getColumnIndexOrThrow("mangaUrl"))
chapter.id = history.chapter_id
// Return result

View File

@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import kotlin.reflect.KProperty1
class MangaFlagsPutResolver(private val colName: String, private val fieldGetter: KProperty1<Manga, Int>, private val updateAll: Boolean = false) : PutResolver<Manga>() {
class MangaFlagsPutResolver(private val colName: String, private val fieldGetter: KProperty1<Manga, Int>) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
@ -20,21 +20,11 @@ class MangaFlagsPutResolver(private val colName: String, private val fieldGetter
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga): UpdateQuery {
val builder = UpdateQuery.builder()
return if (updateAll) {
builder
.table(MangaTable.TABLE)
.build()
} else {
builder
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
}
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) =
contentValuesOf(

View File

@ -1,31 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.content.ContentValues
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class MangaNextUpdatedPutResolver : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
put(MangaTable.COL_NEXT_UPDATE, manga.next_update)
}
}

View File

@ -0,0 +1,23 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.annotation.SuppressLint
import android.database.Cursor
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import eu.kanade.tachiyomi.data.database.models.SourceIdMangaCount
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class SourceIdMangaCountGetResolver : DefaultGetResolver<SourceIdMangaCount>() {
companion object {
val INSTANCE = SourceIdMangaCountGetResolver()
const val COL_COUNT = "manga_count"
}
@SuppressLint("Range")
override fun mapFromCursor(cursor: Cursor): SourceIdMangaCount {
val sourceID = cursor.getLong(cursor.getColumnIndexOrThrow(MangaTable.COL_SOURCE))
val count = cursor.getInt(cursor.getColumnIndexOrThrow(COL_COUNT))
return SourceIdMangaCount(sourceID, count)
}
}

View File

@ -62,4 +62,7 @@ object ChapterTable {
val addScanlator: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_SCANLATOR TEXT DEFAULT NULL"
val fixDateUploadIfNeeded: String
get() = "UPDATE $TABLE SET $COL_DATE_UPLOAD = $COL_DATE_FETCH WHERE $COL_DATE_UPLOAD = 0"
}

View File

@ -28,6 +28,7 @@ object MangaTable {
const val COL_LAST_UPDATE = "last_update"
// Not actually used anymore
const val COL_NEXT_UPDATE = "next_update"
const val COL_DATE_ADDED = "date_added"

View File

@ -39,7 +39,7 @@ object TrackTable {
$COL_MEDIA_ID INTEGER NOT NULL,
$COL_LIBRARY_ID INTEGER,
$COL_TITLE TEXT NOT NULL,
$COL_LAST_CHAPTER_READ INTEGER NOT NULL,
$COL_LAST_CHAPTER_READ REAL NOT NULL,
$COL_TOTAL_CHAPTERS INTEGER NOT NULL,
$COL_STATUS INTEGER NOT NULL,
$COL_SCORE FLOAT NOT NULL,
@ -62,4 +62,19 @@ object TrackTable {
val addFinishDate: String
get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FINISH_DATE LONG NOT NULL DEFAULT 0"
val renameTableToTemp: String
get() =
"ALTER TABLE $TABLE RENAME TO ${TABLE}_tmp"
val insertFromTempTable: String
get() =
"""
|INSERT INTO $TABLE($COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE)
|SELECT $COL_ID,$COL_MANGA_ID,$COL_SYNC_ID,$COL_MEDIA_ID,$COL_LIBRARY_ID,$COL_TITLE,$COL_LAST_CHAPTER_READ,$COL_TOTAL_CHAPTERS,$COL_STATUS,$COL_SCORE,$COL_TRACKING_URL,$COL_START_DATE,$COL_FINISH_DATE
|FROM ${TABLE}_tmp
""".trimMargin()
val dropTempTable: String
get() = "DROP TABLE ${TABLE}_tmp"
}

View File

@ -143,7 +143,7 @@ class DownloadCache(
mangaDirs.values.forEach { mangaDir ->
val chapterDirs = mangaDir.dir.listFiles()
.orEmpty()
.mapNotNull { it.name }
.mapNotNull { it.name?.replace(".cbz", "") }
.toHashSet()
mangaDir.files = chapterDirs

View File

@ -4,6 +4,7 @@ import android.content.Context
import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
@ -13,8 +14,11 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
import rx.Observable
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
/**
@ -24,7 +28,10 @@ import uy.kohesive.injekt.injectLazy
*
* @param context the application context.
*/
class DownloadManager(private val context: Context) {
class DownloadManager(
private val context: Context,
private val db: DatabaseHelper = Injekt.get()
) {
private val sourceManager: SourceManager by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
@ -32,7 +39,7 @@ class DownloadManager(private val context: Context) {
/**
* Downloads provider, used to retrieve the folders where the chapters are or should be stored.
*/
private val provider = DownloadProvider(context)
val provider = DownloadProvider(context)
/**
* Cache of downloaded chapters.
@ -217,7 +224,7 @@ class DownloadManager(private val context: Context) {
* @param download the download to cancel.
*/
fun deletePendingDownload(download: Download) {
deleteChapters(listOf(download.chapter), download.manga, download.source)
deleteChapters(listOf(download.chapter), download.manga, download.source, true)
}
fun deletePendingDownloads(vararg downloads: Download) {
@ -225,7 +232,7 @@ class DownloadManager(private val context: Context) {
downloadsByManga.map { entry ->
val manga = entry.value.first().manga
val source = entry.value.first().source
deleteChapters(entry.value.map { it.chapter }, manga, source)
deleteChapters(entry.value.map { it.chapter }, manga, source, true)
}
}
@ -235,9 +242,15 @@ class DownloadManager(private val context: Context) {
* @param chapters the list of chapters to delete.
* @param manga the manga of the chapters.
* @param source the source of the chapters.
* @param isCancelling true if it's simply cancelling a download
*/
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source): List<Chapter> {
val filteredChapters = getChaptersToDelete(chapters)
fun deleteChapters(chapters: List<Chapter>, manga: Manga, source: Source, isCancelling: Boolean = false): List<Chapter> {
val filteredChapters = if (isCancelling) {
chapters
} else {
getChaptersToDelete(chapters, manga)
}
launchIO {
removeFromDownloadQueue(filteredChapters)
@ -290,7 +303,7 @@ class DownloadManager(private val context: Context) {
* @param manga the manga of the chapters.
*/
fun enqueueDeleteChapters(chapters: List<Chapter>, manga: Manga) {
pendingDeleter.addChapters(getChaptersToDelete(chapters), manga)
pendingDeleter.addChapters(getChaptersToDelete(chapters, manga), manga)
}
/**
@ -314,24 +327,37 @@ class DownloadManager(private val context: Context) {
*/
fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) {
val oldNames = provider.getValidChapterDirNames(oldChapter)
val newName = provider.getChapterDirName(newChapter)
val mangaDir = provider.getMangaDir(manga, source)
// Assume there's only 1 version of the chapter name formats present
val oldFolder = oldNames.asSequence()
val oldDownload = oldNames.asSequence()
.mapNotNull { mangaDir.findFile(it) }
.firstOrNull()
.firstOrNull() ?: return
if (oldFolder?.renameTo(newName) == true) {
var newName = provider.getChapterDirName(newChapter)
if (oldDownload.isFile && oldDownload.name?.endsWith(".cbz") == true) {
newName += ".cbz"
}
if (oldDownload.renameTo(newName)) {
cache.removeChapter(oldChapter, manga)
cache.addChapter(newName, mangaDir, manga)
} else {
Timber.e("Could not rename downloaded chapter: %s.", oldNames.joinToString())
logcat(LogPriority.ERROR) { "Could not rename downloaded chapter: ${oldNames.joinToString()}." }
}
}
private fun getChaptersToDelete(chapters: List<Chapter>): List<Chapter> {
return if (!preferences.removeBookmarkedChapters()) {
private fun getChaptersToDelete(chapters: List<Chapter>, manga: Manga): List<Chapter> {
// Retrieve the categories that are set to exclude from being deleted on read
val categoriesToExclude = preferences.removeExcludeCategories().get().map(String::toInt)
val categoriesForManga = db.getCategoriesForManga(manga).executeAsBlocking()
.mapNotNull { it.id }
.takeUnless { it.isEmpty() }
?: listOf(0)
return if (categoriesForManga.intersect(categoriesToExclude).isNotEmpty()) {
chapters.filterNot { it.read }
} else if (!preferences.removeBookmarkedChapters()) {
chapters.filterNot { it.bookmark }
} else {
chapters

View File

@ -52,7 +52,7 @@ internal class DownloadNotifier(private val context: Context) {
/**
* Updated when error is thrown
*/
var errorThrown = false
private var errorThrown = false
/**
* Updated when paused
@ -105,6 +105,7 @@ internal class DownloadNotifier(private val context: Context) {
if (preferences.hideNotificationContent()) {
setContentTitle(downloadingProgressText)
setContentText(null)
} else {
val title = download.manga.title.chop(15)
val quotedTitle = Pattern.quote(title)
@ -187,8 +188,8 @@ internal class DownloadNotifier(private val context: Context) {
fun onWarning(reason: String) {
with(errorNotificationBuilder) {
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
setContentText(reason)
setSmallIcon(android.R.drawable.stat_sys_warning)
setStyle(NotificationCompat.BigTextStyle().bigText(reason))
setSmallIcon(R.drawable.ic_warning_white_24dp)
setAutoCancel(true)
clearActions()
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
@ -208,15 +209,14 @@ internal class DownloadNotifier(private val context: Context) {
* @param error string containing error information.
* @param chapter string containing chapter title.
*/
fun onError(error: String? = null, chapter: String? = null) {
fun onError(error: String? = null, chapter: String? = null, mangaTitle: String? = null) {
// Create notification
with(errorNotificationBuilder) {
setContentTitle(
chapter
?: context.getString(R.string.download_notifier_downloader_title)
mangaTitle?.plus(": $chapter") ?: context.getString(R.string.download_notifier_downloader_title)
)
setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
setSmallIcon(android.R.drawable.stat_sys_warning)
setSmallIcon(R.drawable.ic_warning_white_24dp)
clearActions()
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))
setProgress(0, 0, false)

View File

@ -9,10 +9,11 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import logcat.LogPriority
import uy.kohesive.injekt.injectLazy
/**
@ -54,7 +55,7 @@ class DownloadProvider(private val context: Context) {
.createDirectory(getSourceDirName(source))
.createDirectory(getMangaDirName(manga))
} catch (e: Throwable) {
Timber.e(e, "Invalid download directory")
logcat(LogPriority.ERROR, e) { "Invalid download directory" }
throw Exception(context.getString(R.string.invalid_download_dir))
}
}
@ -147,10 +148,14 @@ class DownloadProvider(private val context: Context) {
* @param chapter the chapter to query.
*/
fun getValidChapterDirNames(chapter: Chapter): List<String> {
val chapterName = getChapterDirName(chapter)
return listOf(
getChapterDirName(chapter),
// Folder of images
chapterName,
// Archived chapters
"$chapterName.cbz",
// TODO: remove this
// Legacy chapter directory name used in v0.9.2 and before
DiskUtil.buildValidFilename(chapter.name)
)

View File

@ -4,26 +4,32 @@ import android.app.Notification
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkInfo.State.CONNECTED
import android.net.NetworkInfo.State.DISCONNECTED
import android.os.IBinder
import android.os.PowerManager
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.github.pwittchen.reactivenetwork.library.Connectivity
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.connectivityManager
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.toast
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.LogPriority
import ru.beryukhov.reactivenetwork.ReactiveNetwork
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy
@ -80,16 +86,15 @@ class DownloadService : Service() {
*/
private lateinit var wakeLock: PowerManager.WakeLock
/**
* Subscriptions to store while the service is running.
*/
private lateinit var subscriptions: CompositeSubscription
private lateinit var ioScope: CoroutineScope
/**
* Called when the service is created.
*/
override fun onCreate() {
super.onCreate()
ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
wakeLock = acquireWakeLock(javaClass.name)
runningRelay.call(true)
@ -102,6 +107,7 @@ class DownloadService : Service() {
* Called when the service is destroyed.
*/
override fun onDestroy() {
ioScope?.cancel()
runningRelay.call(false)
subscriptions.unsubscribe()
downloadManager.stopDownloads()
@ -129,55 +135,56 @@ class DownloadService : Service() {
* @see onNetworkStateChanged
*/
private fun listenNetworkChanges() {
subscriptions += ReactiveNetwork.observeNetworkConnectivity(applicationContext)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ state ->
onNetworkStateChanged(state)
},
{
ReactiveNetwork()
.observeNetworkConnectivity(applicationContext)
.onEach {
withUIContext {
onNetworkStateChanged()
}
}
.catch { error ->
withUIContext {
logcat(LogPriority.ERROR, error)
toast(R.string.download_queue_error)
stopSelf()
}
)
}
.launchIn(ioScope)
}
/**
* Called when the network state changes.
*
* @param connectivity the new network state.
*/
private fun onNetworkStateChanged(connectivity: Connectivity) {
when (connectivity.state) {
CONNECTED -> {
if (preferences.downloadOnlyOverWifi() && connectivityManager.activeNetworkInfo?.type != ConnectivityManager.TYPE_WIFI) {
downloadManager.stopDownloads(getString(R.string.download_notifier_text_only_wifi))
} else {
val started = downloadManager.startDownloads()
if (!started) stopSelf()
}
}
DISCONNECTED -> {
downloadManager.stopDownloads(getString(R.string.download_notifier_no_network))
}
else -> {
/* Do nothing */
private fun onNetworkStateChanged() {
if (isOnline()) {
if (preferences.downloadOnlyOverWifi() && !isConnectedToWifi()) {
stopDownloads(R.string.download_notifier_text_only_wifi)
} else {
val started = downloadManager.startDownloads()
if (!started) stopSelf()
}
} else {
stopDownloads(R.string.download_notifier_no_network)
}
}
private fun stopDownloads(@StringRes string: Int) {
downloadManager.stopDownloads(getString(string))
}
/**
* Listens to downloader status. Enables or disables the wake lock depending on the status.
*/
private fun listenDownloaderState() {
subscriptions += downloadManager.runningRelay.subscribe { running ->
if (running) {
wakeLock.acquireIfNeeded()
} else {
wakeLock.releaseIfNeeded()
subscriptions += downloadManager.runningRelay
.doOnError { /* Swallow wakelock error */ }
.subscribe { running ->
if (running) {
wakeLock.acquireIfNeeded()
} else {
wakeLock.releaseIfNeeded()
}
}
}
}
/**

View File

@ -11,7 +11,9 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
@ -22,15 +24,20 @@ import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.async
import logcat.LogPriority
import okhttp3.Response
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import rx.subscriptions.CompositeSubscription
import timber.log.Timber
import uy.kohesive.injekt.injectLazy
import java.io.BufferedOutputStream
import java.io.File
import java.util.zip.CRC32
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
/**
* This class is the one in charge of downloading chapters.
@ -55,6 +62,8 @@ class Downloader(
private val chapterCache: ChapterCache by injectLazy()
private val preferences: PreferencesHelper by injectLazy()
/**
* Store for persisting downloads across restarts.
*/
@ -209,7 +218,7 @@ class Downloader(
},
{ error ->
DownloadService.stop(context)
Timber.e(error)
logcat(LogPriority.ERROR, error)
notifier.onError(error.message)
}
)
@ -262,7 +271,17 @@ class Downloader(
// Start downloader if needed
if (autoStart && wasEmpty) {
DownloadService.start(this@Downloader.context)
val maxDownloadsFromSource = queue
.groupBy { it.source }
.filterKeys { it !is UnmeteredSource }
.maxOf { it.value.size }
// TODO: re-enable warning
if (maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
// withUIContext {
// context.toast(R.string.download_queue_size_warning, Toast.LENGTH_LONG)
// }
}
DownloadService.start(context)
}
}
}
@ -278,7 +297,7 @@ class Downloader(
val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir)
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
download.status = Download.State.ERROR
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name)
notifier.onError(context.getString(R.string.download_insufficient_space), download.chapter.name, download.manga.title)
return@defer Observable.just(download)
}
@ -324,7 +343,7 @@ class Downloader(
// If the page list threw, it will resume here
.onErrorReturn { error ->
download.status = Download.State.ERROR
notifier.onError(error.message, download.chapter.name)
notifier.onError(error.message, download.chapter.name, download.manga.title)
download
}
}
@ -470,13 +489,51 @@ class Downloader(
// Only rename the directory if it's downloaded.
if (download.status == Download.State.DOWNLOADED) {
tmpDir.renameTo(dirname)
if (preferences.saveChaptersAsCBZ().get()) {
archiveChapter(mangaDir, dirname, tmpDir)
} else {
tmpDir.renameTo(dirname)
}
cache.addChapter(dirname, mangaDir, download.manga)
DiskUtil.createNoMediaFile(tmpDir, context)
}
}
/**
* Archive the chapter pages as a CBZ.
*/
private fun archiveChapter(
mangaDir: UniFile,
dirname: String,
tmpDir: UniFile,
) {
val zip = mangaDir.createFile("$dirname.cbz.tmp")
ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut ->
zipOut.setMethod(ZipEntry.STORED)
tmpDir.listFiles()?.forEach { img ->
img.openInputStream().use { input ->
val data = input.readBytes()
val size = img.length()
val entry = ZipEntry(img.name).apply {
val crc = CRC32().apply {
update(data)
}
setCrc(crc.value)
compressedSize = size
setSize(size)
}
zipOut.putNextEntry(entry)
zipOut.write(data)
}
}
}
zip.renameTo("$dirname.cbz")
tmpDir.delete()
}
/**
* Completes a download. This method is called in the main thread.
*/
@ -500,8 +557,9 @@ class Downloader(
companion object {
const val TMP_DIR_SUFFIX = "_tmp"
// Arbitrary minimum required space to start a download: 50 MB
const val MIN_DISK_SPACE = 50 * 1024 * 1024
const val CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 15
}
}
// Arbitrary minimum required space to start a download: 50 MB
private const val MIN_DISK_SPACE = 50 * 1024 * 1024

View File

@ -8,9 +8,10 @@ import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.preference.CHARGING
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
@ -19,6 +20,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
Worker(context, workerParams) {
override fun doWork(): Result {
val preferences = Injekt.get<PreferencesHelper>()
if (requiresWifiConnection(preferences) && !context.isConnectedToWifi()) {
Result.failure()
}
return if (LibraryUpdateService.start(context)) {
Result.success()
} else {
@ -33,17 +39,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val preferences = Injekt.get<PreferencesHelper>()
val interval = prefInterval ?: preferences.libraryUpdateInterval().get()
if (interval > 0) {
val restrictions = preferences.libraryUpdateRestriction().get()
val acRestriction = CHARGING in restrictions
val wifiRestriction = if (UNMETERED_NETWORK in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction)
.setRequiresCharging(acRestriction)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(DEVICE_CHARGING in restrictions)
.build()
val request = PeriodicWorkRequestBuilder<LibraryUpdateJob>(
@ -61,5 +60,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
}
}
fun requiresWifiConnection(preferences: PreferencesHelper): Boolean {
val restrictions = preferences.libraryUpdateDeviceRestriction().get()
return DEVICE_ONLY_ON_WIFI in restrictions
}
}
}

View File

@ -16,6 +16,7 @@ import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.Downloader
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -51,7 +52,7 @@ class LibraryUpdateNotifier(private val context: Context) {
* Cached progress notification to avoid creating a lot.
*/
val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) {
setContentTitle(context.getString(R.string.app_name))
setSmallIcon(R.drawable.ic_refresh_24dp)
setLargeIcon(notificationBitmap)
@ -101,25 +102,12 @@ class LibraryUpdateNotifier(private val context: Context) {
context.notificationManager.notify(
Notifications.ID_LIBRARY_ERROR,
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_ERROR) {
setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_error, errors.size, errors.size))
setStyle(
NotificationCompat.BigTextStyle().bigText(
errors.joinToString("\n") {
it.chop(NOTIF_TITLE_MAX_LEN)
}
)
)
setContentText(context.getString(R.string.action_show_errors))
setSmallIcon(R.drawable.ic_tachi)
val errorLogIntent = NotificationReceiver.openErrorLogPendingActivity(context, uri)
setContentIntent(errorLogIntent)
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_show_errors),
errorLogIntent
)
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
}
.build()
)
@ -225,6 +213,20 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.ID_NEW_CHAPTERS
)
)
// Download chapters action
// Only add the action when chapters is within threshold
if (chapters.size <= Downloader.CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
NotificationReceiver.downloadChaptersPendingBroadcast(
context,
manga,
chapters,
Notifications.ID_NEW_CHAPTERS
)
)
}
}
}

View File

@ -1,59 +0,0 @@
package eu.kanade.tachiyomi.data.library
import eu.kanade.tachiyomi.data.database.models.Manga
import java.util.Collections
import kotlin.Comparator
import kotlin.math.abs
/**
* This class will provide various functions to rank manga to efficiently schedule manga to update.
*/
object LibraryUpdateRanker {
val rankingScheme = listOf(
(this::lexicographicRanking)(),
(this::latestFirstRanking)(),
(this::nextFirstRanking)()
)
/**
* Provides a total ordering over all the Mangas.
*
* Orders the manga based on the distance between the next expected update and now.
* The comparator is reversed, placing the smallest (and thus closest to updating now) first.
*/
fun nextFirstRanking(): Comparator<Manga> {
val time = System.currentTimeMillis()
return Collections.reverseOrder(
Comparator { mangaFirst: Manga,
mangaSecond: Manga ->
compareValues(abs(mangaSecond.next_update - time), abs(mangaFirst.next_update - time))
}
)
}
/**
* Provides a total ordering over all the [Manga]s.
*
* Assumption: An active [Manga] mActive is expected to have been last updated after an
* inactive [Manga] mInactive.
*
* Using this insight, function returns a Comparator for which mActive appears before mInactive.
* @return a Comparator that ranks manga based on relevance.
*/
private fun latestFirstRanking(): Comparator<Manga> =
Comparator { first: Manga, second: Manga ->
compareValues(second.last_update, first.last_update)
}
/**
* Provides a total ordering over all the [Manga]s.
*
* Order the manga lexicographically.
* @return a Comparator that ranks manga lexicographically based on the title.
*/
private fun lexicographicRanking(): Comparator<Manga> =
Comparator { first: Manga, second: Manga ->
compareValues(first.title, second.title)
}
}

View File

@ -16,14 +16,16 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.MANGA_FULLY_READ
import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
@ -37,10 +39,10 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.isServiceRunning
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
@ -50,7 +52,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import timber.log.Timber
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -212,7 +214,7 @@ class LibraryUpdateService(
// Destroy service when completed or in case of an error.
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
logcat(LogPriority.ERROR, exception)
stopSelf(startId)
}
updateJob = ioScope.launch(handler) {
@ -255,14 +257,30 @@ class LibraryUpdateService(
listToInclude.minus(listToExclude)
}
if (target == Target.CHAPTERS && preferences.updateOnlyNonCompleted()) {
listToUpdate = listToUpdate.filterNot { it.status == SManga.COMPLETED }
if (target == Target.CHAPTERS) {
val restrictions = preferences.libraryUpdateMangaRestriction().get()
if (MANGA_ONGOING in restrictions) {
listToUpdate = listToUpdate.filterNot { it.status == SManga.COMPLETED }
}
if (MANGA_FULLY_READ in restrictions) {
listToUpdate = listToUpdate.filter { it.unread == 0 }
}
}
val selectedScheme = preferences.libraryUpdatePrioritization().get()
mangaToUpdate = listToUpdate
.distinctBy { it.id }
.sortedWith(rankingScheme[selectedScheme])
.sortedBy { it.title }
// Warn when excessively checking a single source
val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
// TODO: re-enable warning
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
// toast(R.string.notification_size_warning, Toast.LENGTH_LONG)
}
}
/**
@ -282,6 +300,7 @@ class LibraryUpdateService(
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
val hasDownloads = AtomicBoolean(false)
val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
val currentUnreadUpdatesCount = preferences.unreadUpdatesCount().get()
withIOContext {
mangaToUpdate.groupBy { it.source }
@ -294,48 +313,46 @@ class LibraryUpdateService(
return@async
}
currentlyUpdatingManga.add(manga)
notifier.showProgressNotification(
withUpdateNotification(
currentlyUpdatingManga,
progressCount.get(),
mangaToUpdate.size
)
progressCount,
manga,
) { manga ->
try {
val (newChapters, _) = updateManga(manga)
try {
val (newChapters, _) = updateManga(manga)
if (newChapters.isNotEmpty()) {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(manga, newChapters)
hasDownloads.set(true)
}
if (newChapters.isNotEmpty()) {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(manga, newChapters)
hasDownloads.set(true)
// Convert to the manga that contains new chapters
newUpdates.add(
manga to newChapters.sortedByDescending { ch -> ch.source_order }
.toTypedArray()
)
}
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.sortedByDescending { ch -> ch.source_order }.toTypedArray())
} catch (e: Throwable) {
val errorMessage = when (e) {
is NoChaptersException -> {
getString(R.string.no_chapters_error)
}
is SourceManager.SourceNotInstalledException -> {
// failedUpdates will already have the source, don't need to copy it into the message
getString(R.string.loader_not_implemented_error)
}
else -> {
e.message
}
}
failedUpdates.add(manga to errorMessage)
}
} catch (e: Throwable) {
val errorMessage = if (e is NoChaptersException) {
getString(R.string.no_chapters_error)
} else if (e is SourceManager.SourceNotInstalledException) {
// failedUpdates will already have the source, don't need to copy it into the message
getString(R.string.loader_not_implemented_error)
} else {
e.message
if (preferences.autoUpdateTrackers()) {
updateTrackings(manga, loggedServices)
}
failedUpdates.add(manga to errorMessage)
}
if (preferences.autoUpdateTrackers()) {
updateTrackings(manga, loggedServices)
}
currentlyUpdatingManga.remove(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),
mangaToUpdate.size
)
}
}
}
@ -347,12 +364,14 @@ class LibraryUpdateService(
if (newUpdates.isNotEmpty()) {
notifier.showUpdateNotifications(newUpdates)
val newChapterCount = newUpdates.sumOf { it.second.size }
preferences.unreadUpdatesCount().set(currentUnreadUpdatesCount + newChapterCount)
if (hasDownloads.get()) {
DownloadService.start(this)
}
}
if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) {
if (failedUpdates.isNotEmpty()) {
val errorFile = writeErrorFile(failedUpdates)
notifier.showUpdateErrorNotification(
failedUpdates.map { it.first.title },
@ -376,24 +395,19 @@ class LibraryUpdateService(
suspend fun updateManga(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
val source = sourceManager.getOrStub(manga.source)
// Update manga details metadata in the background
// Update manga details metadata
if (preferences.autoUpdateMetadata()) {
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
val sManga = updatedManga.toSManga()
// Avoid "losing" existing cover
if (!sManga.thumbnail_url.isNullOrEmpty()) {
manga.prepUpdateCover(coverCache, sManga, false)
} else {
sManga.thumbnail_url = manga.thumbnail_url
}
GlobalScope.launch(Dispatchers.IO + handler) {
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
val sManga = updatedManga.toSManga()
// Avoid "losing" existing cover
if (!sManga.thumbnail_url.isNullOrEmpty()) {
manga.prepUpdateCover(coverCache, sManga, false)
} else {
sManga.thumbnail_url = manga.thumbnail_url
}
manga.copyFrom(sManga)
db.insertManga(manga).executeAsBlocking()
}
manga.copyFrom(sManga)
db.insertManga(manga).executeAsBlocking()
}
val chapters = source.getChapterList(manga.toMangaInfo())
@ -418,36 +432,27 @@ class LibraryUpdateService(
return@async
}
currentlyUpdatingManga.add(manga)
notifier.showProgressNotification(
withUpdateNotification(
currentlyUpdatingManga,
progressCount.get(),
mangaToUpdate.size
)
sourceManager.get(manga.source)?.let { source ->
try {
val networkManga =
source.getMangaDetails(manga.toMangaInfo())
val sManga = networkManga.toSManga()
manga.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let {
manga.thumbnail_url = it
db.insertManga(manga).executeAsBlocking()
progressCount,
manga,
) { manga ->
sourceManager.get(manga.source)?.let { source ->
try {
val networkManga =
source.getMangaDetails(manga.toMangaInfo())
val sManga = networkManga.toSManga()
manga.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let {
manga.thumbnail_url = it
db.insertManga(manga).executeAsBlocking()
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
} catch (e: Throwable) {
// Ignore errors and continue
Timber.e(e)
}
}
currentlyUpdatingManga.remove(manga)
progressCount.andIncrement
notifier.showProgressNotification(
currentlyUpdatingManga,
progressCount.get(),
mangaToUpdate.size
)
}
}
}
@ -497,7 +502,7 @@ class LibraryUpdateService(
}
} catch (e: Throwable) {
// Ignore errors and continue
Timber.e(e)
logcat(LogPriority.ERROR, e)
}
}
}
@ -506,6 +511,38 @@ class LibraryUpdateService(
.awaitAll()
}
private suspend fun withUpdateNotification(
updatingManga: CopyOnWriteArrayList<LibraryManga>,
completed: AtomicInteger,
manga: LibraryManga,
block: suspend (LibraryManga) -> Unit,
) {
if (updateJob?.isActive != true) {
return
}
updatingManga.add(manga)
notifier.showProgressNotification(
updatingManga,
completed.get(),
mangaToUpdate.size
)
block(manga)
if (updateJob?.isActive != true) {
return
}
updatingManga.remove(manga)
completed.andIncrement
notifier.showProgressNotification(
updatingManga,
completed.get(),
mangaToUpdate.size
)
}
/**
* Writes basic file of update errors to cache dir.
*/
@ -514,12 +551,13 @@ class LibraryUpdateService(
if (errors.isNotEmpty()) {
val file = createFileInCacheDir("tachiyomi_update_errors.txt")
file.bufferedWriter().use { out ->
out.write(getString(R.string.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n")
// Error file format:
// ! Error
// # Source
// - Manga
errors.groupBy({ it.second }, { it.first }).forEach { (error, mangas) ->
out.write("! ${error}\n")
out.write("\n! ${error}\n")
mangas.groupBy { it.source }.forEach { (srcId, mangas) ->
val source = sourceManager.getOrStub(srcId)
out.write(" # $source\n")
@ -537,3 +575,6 @@ class LibraryUpdateService(
return File("")
}
}
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/help/guides/troubleshooting"

View File

@ -20,10 +20,10 @@ object NotificationHandler {
*/
internal fun openDownloadManagerPendingActivity(context: Context): PendingIntent {
val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
action = MainActivity.SHORTCUT_DOWNLOADS
}
return PendingIntent.getActivity(context, 0, intent, 0)
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**

View File

@ -102,6 +102,18 @@ class NotificationReceiver : BroadcastReceiver() {
markAsRead(urls, mangaId)
}
}
// Download manga chapters
ACTION_DOWNLOAD_CHAPTER -> {
val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
if (notificationId > -1) {
dismissNotification(context, notificationId, intent.getIntExtra(EXTRA_GROUP_ID, 0))
}
val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return
val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
if (mangaId > -1) {
downloadChapters(urls, mangaId)
}
}
// Share crash dump file
ACTION_SHARE_CRASH_LOG ->
shareFile(
@ -235,6 +247,24 @@ class NotificationReceiver : BroadcastReceiver() {
}
}
/**
* Method called when user wants to download chapters
*
* @param chapterUrls URLs of chapter to download
* @param mangaId id of manga
*/
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
val db: DatabaseHelper = Injekt.get()
launchIO {
val chapters = chapterUrls.mapNotNull { db.getChapter(it, mangaId).executeAsBlocking() }
val manga = db.getManga(mangaId).executeAsBlocking()
if (chapters.isNotEmpty() && manga != null) {
downloadManager.downloadChapters(manga, chapters)
}
}
}
companion object {
private const val NAME = "NotificationReceiver"
@ -251,6 +281,7 @@ class NotificationReceiver : BroadcastReceiver() {
private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
private const val ACTION_DOWNLOAD_CHAPTER = "$ID.$NAME.ACTION_DOWNLOAD_CHAPTER"
private const val ACTION_RESUME_DOWNLOADS = "$ID.$NAME.ACTION_RESUME_DOWNLOADS"
private const val ACTION_PAUSE_DOWNLOADS = "$ID.$NAME.ACTION_PAUSE_DOWNLOADS"
@ -442,6 +473,28 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Returns [PendingIntent] that downloads chapters
*
* @param context context of application
* @param manga manga of chapter
*/
internal fun downloadChaptersPendingBroadcast(
context: Context,
manga: Manga,
chapters: Array<Chapter>,
groupId: Int
): PendingIntent {
val newIntent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_DOWNLOAD_CHAPTER
putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
putExtra(EXTRA_MANGA_ID, manga.id)
putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
putExtra(EXTRA_GROUP_ID, groupId)
}
return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Returns [PendingIntent] that starts a service which stops the library update
*

View File

@ -1,12 +1,13 @@
package eu.kanade.tachiyomi.data.notification
import android.app.NotificationChannel
import android.app.NotificationChannelGroup
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.buildNotificationChannel
import eu.kanade.tachiyomi.util.system.buildNotificationChannelGroup
/**
* Class to manage the basic information of all the notifications used in the app.
@ -17,14 +18,15 @@ object Notifications {
* Common notification channel and ids used anywhere.
*/
const val CHANNEL_COMMON = "common_channel"
const val ID_UPDATER = 1
const val ID_DOWNLOAD_IMAGE = 2
/**
* Notification channel and ids used by the library updater.
*/
const val CHANNEL_LIBRARY = "library_channel"
private const val GROUP_LIBRARY = "group_library"
const val CHANNEL_LIBRARY_PROGRESS = "library_progress_channel"
const val ID_LIBRARY_PROGRESS = -101
const val CHANNEL_LIBRARY_ERROR = "library_errors_channel"
const val ID_LIBRARY_ERROR = -102
/**
@ -45,12 +47,6 @@ object Notifications {
const val ID_NEW_CHAPTERS = -301
const val GROUP_NEW_CHAPTERS = "eu.kanade.tachiyomi.NEW_CHAPTERS"
/**
* Notification channel and ids used by the library updater.
*/
const val CHANNEL_UPDATES_TO_EXTS = "updates_ext_channel"
const val ID_UPDATES_TO_EXTS = -401
/**
* Notification channel and ids used by the backup/restore system.
*/
@ -74,101 +70,112 @@ object Notifications {
const val CHANNEL_INCOGNITO_MODE = "incognito_mode_channel"
const val ID_INCOGNITO_MODE = -701
/**
* Notification channel and ids used for app and extension updates.
*/
private const val GROUP_APK_UPDATES = "group_apk_updates"
const val CHANNEL_APP_UPDATE = "app_apk_update_channel"
const val ID_APP_UPDATER = 1
const val CHANNEL_EXTENSIONS_UPDATE = "ext_apk_update_channel"
const val ID_UPDATES_TO_EXTS = -401
const val ID_EXTENSION_INSTALLER = -402
private val deprecatedChannels = listOf(
"downloader_channel",
"backup_restore_complete_channel"
"backup_restore_complete_channel",
"library_channel",
"library_progress_channel",
"updates_ext_channel",
)
/**
* Creates the notification channels introduced in Android Oreo.
* This won't do anything on Android versions that don't support notification channels.
*
* @param context The application context.
*/
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
listOf(
NotificationChannelGroup(GROUP_BACKUP_RESTORE, context.getString(R.string.group_backup_restore)),
NotificationChannelGroup(GROUP_DOWNLOADER, context.getString(R.string.group_downloader))
).forEach(context.notificationManager::createNotificationChannelGroup)
listOf(
NotificationChannel(
CHANNEL_COMMON,
context.getString(R.string.channel_common),
NotificationManager.IMPORTANCE_LOW
),
NotificationChannel(
CHANNEL_LIBRARY,
context.getString(R.string.channel_library),
NotificationManager.IMPORTANCE_LOW
).apply {
setShowBadge(false)
},
NotificationChannel(
CHANNEL_DOWNLOADER_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW
).apply {
group = GROUP_DOWNLOADER
setShowBadge(false)
},
NotificationChannel(
CHANNEL_DOWNLOADER_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_LOW
).apply {
group = GROUP_DOWNLOADER
setShowBadge(false)
},
NotificationChannel(
CHANNEL_DOWNLOADER_ERROR,
context.getString(R.string.channel_errors),
NotificationManager.IMPORTANCE_LOW
).apply {
group = GROUP_DOWNLOADER
setShowBadge(false)
},
NotificationChannel(
CHANNEL_NEW_CHAPTERS,
context.getString(R.string.channel_new_chapters),
NotificationManager.IMPORTANCE_DEFAULT
),
NotificationChannel(
CHANNEL_UPDATES_TO_EXTS,
context.getString(R.string.channel_ext_updates),
NotificationManager.IMPORTANCE_DEFAULT
),
NotificationChannel(
CHANNEL_BACKUP_RESTORE_PROGRESS,
context.getString(R.string.channel_progress),
NotificationManager.IMPORTANCE_LOW
).apply {
group = GROUP_BACKUP_RESTORE
setShowBadge(false)
},
NotificationChannel(
CHANNEL_BACKUP_RESTORE_COMPLETE,
context.getString(R.string.channel_complete),
NotificationManager.IMPORTANCE_HIGH
).apply {
group = GROUP_BACKUP_RESTORE
setShowBadge(false)
setSound(null, null)
},
NotificationChannel(
CHANNEL_CRASH_LOGS,
context.getString(R.string.channel_crash_logs),
NotificationManager.IMPORTANCE_HIGH
),
NotificationChannel(
CHANNEL_INCOGNITO_MODE,
context.getString(R.string.pref_incognito_mode),
NotificationManager.IMPORTANCE_LOW
)
).forEach(context.notificationManager::createNotificationChannel)
val notificationService = NotificationManagerCompat.from(context)
// Delete old notification channels
deprecatedChannels.forEach(context.notificationManager::deleteNotificationChannel)
deprecatedChannels.forEach(notificationService::deleteNotificationChannel)
notificationService.createNotificationChannelGroupsCompat(
listOf(
buildNotificationChannelGroup(GROUP_BACKUP_RESTORE) {
setName(context.getString(R.string.label_backup))
},
buildNotificationChannelGroup(GROUP_DOWNLOADER) {
setName(context.getString(R.string.download_notifier_downloader_title))
},
buildNotificationChannelGroup(GROUP_LIBRARY) {
setName(context.getString(R.string.label_library))
},
buildNotificationChannelGroup(GROUP_APK_UPDATES) {
setName(context.getString(R.string.label_recent_updates))
},
)
)
notificationService.createNotificationChannelsCompat(
listOf(
buildNotificationChannel(CHANNEL_COMMON, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_common))
},
buildNotificationChannel(CHANNEL_LIBRARY_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_LIBRARY_ERROR, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_errors))
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_NEW_CHAPTERS, IMPORTANCE_DEFAULT) {
setName(context.getString(R.string.channel_new_chapters))
},
buildNotificationChannel(CHANNEL_DOWNLOADER_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setGroup(GROUP_DOWNLOADER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_DOWNLOADER_COMPLETE, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_complete))
setGroup(GROUP_DOWNLOADER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_DOWNLOADER_ERROR, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_errors))
setGroup(GROUP_DOWNLOADER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setGroup(GROUP_BACKUP_RESTORE)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_COMPLETE, IMPORTANCE_HIGH) {
setName(context.getString(R.string.channel_complete))
setGroup(GROUP_BACKUP_RESTORE)
setShowBadge(false)
setSound(null, null)
},
buildNotificationChannel(CHANNEL_CRASH_LOGS, IMPORTANCE_HIGH) {
setName(context.getString(R.string.channel_crash_logs))
},
buildNotificationChannel(CHANNEL_INCOGNITO_MODE, IMPORTANCE_LOW) {
setName(context.getString(R.string.pref_incognito_mode))
},
buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_DEFAULT) {
setGroup(GROUP_APK_UPDATES)
setName(context.getString(R.string.channel_app_updates))
},
buildNotificationChannel(CHANNEL_EXTENSIONS_UPDATE, IMPORTANCE_DEFAULT) {
setGroup(GROUP_APK_UPDATES)
setName(context.getString(R.string.channel_ext_updates))
},
)
)
}
}

View File

@ -5,137 +5,28 @@ package eu.kanade.tachiyomi.data.preference
*/
object PreferenceKeys {
const val themeMode = "pref_theme_mode_key"
const val appTheme = "pref_app_theme"
const val themeDarkAmoled = "pref_theme_dark_amoled_key"
const val confirmExit = "pref_confirm_exit"
const val hideBottomBarOnScroll = "pref_hide_bottom_bar_on_scroll"
const val sideNavIconAlignment = "pref_side_nav_icon_alignment"
const val enableTransitions = "pref_enable_transitions_key"
const val doubleTapAnimationSpeed = "pref_double_tap_anim_speed"
const val showPageNumber = "pref_show_page_number_key"
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 trueColor = "pref_true_color_key"
const val fullscreen = "fullscreen"
const val cutoutShort = "cutout_short"
const val keepScreenOn = "pref_keep_screen_on_key"
const val customBrightness = "pref_custom_brightness_key"
const val customBrightnessValue = "custom_brightness_value"
const val colorFilter = "pref_color_filter_key"
const val colorFilterValue = "color_filter_value"
const val colorFilterMode = "color_filter_mode"
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 zoomStart = "pref_zoom_start_key"
const val readerTheme = "pref_reader_theme_key"
const val cropBorders = "crop_borders"
const val cropBordersWebtoon = "crop_borders_webtoon"
const val readWithTapping = "reader_tap"
const val pagerNavInverted = "reader_tapping_inverted"
const val webtoonNavInverted = "reader_tapping_inverted_webtoon"
const val readWithLongTap = "reader_long_tap"
const val readWithVolumeKeys = "reader_volume_keys"
const val readWithVolumeKeysInverted = "reader_volume_keys_inverted"
const val navigationModePager = "reader_navigation_mode_pager"
const val navigationModeWebtoon = "reader_navigation_mode_webtoon"
const val showNavigationOverlayNewUser = "reader_navigation_overlay_new_user"
const val showNavigationOverlayOnStart = "reader_navigation_overlay_on_start"
const val webtoonSidePadding = "webtoon_side_padding"
const val portraitColumns = "pref_library_columns_portrait_key"
const val landscapeColumns = "pref_library_columns_landscape_key"
const val jumpToChapters = "jump_to_chapters"
const val updateOnlyNonCompleted = "pref_update_only_non_completed_key"
const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
const val lastUsedSource = "last_catalogue_source"
const val lastUsedCategory = "last_used_category"
const val sourceDisplayMode = "pref_display_mode_catalogue"
const val enabledLanguages = "source_languages"
const val backupDirectory = "backup_directory"
const val downloadsDirectory = "download_directory"
const val downloadOnlyOverWifi = "pref_download_only_over_wifi_key"
const val folderPerManga = "create_folder_per_manga"
const val numberOfBackups = "backup_slots"
const val backupInterval = "backup_interval"
const val removeAfterReadSlots = "remove_after_read_slots"
const val removeAfterMarkedAsRead = "pref_remove_after_marked_as_read_key"
const val removeBookmarkedChapters = "pref_remove_bookmarked"
const val libraryUpdateInterval = "pref_library_update_interval_key"
const val libraryUpdateRestriction = "library_update_restriction"
const val libraryUpdateCategories = "library_update_categories"
const val libraryUpdateCategoriesExclude = "library_update_categories_exclude"
const val libraryUpdatePrioritization = "library_update_prioritization"
const val downloadedOnly = "pref_downloaded_only"
const val filterDownloaded = "pref_filter_library_downloaded"
const val filterUnread = "pref_filter_library_unread"
@ -147,61 +38,25 @@ object PreferenceKeys {
const val librarySortingMode = "library_sorting_mode"
const val librarySortingDirection = "library_sorting_ascending"
const val automaticExtUpdates = "automatic_ext_updates"
const val showNsfwSource = "show_nsfw_source"
const val showNsfwExtension = "show_nsfw_extension"
const val labelNsfwExtension = "label_nsfw_extension"
const val migrationSortingMode = "pref_migration_sorting"
const val migrationSortingDirection = "pref_migration_direction"
const val startScreen = "start_screen"
const val useAuthenticator = "use_biometric_lock"
const val lockAppAfter = "lock_app_after"
const val lastAppUnlock = "last_app_unlock"
const val secureScreen = "secure_screen"
const val hideNotificationContent = "hide_notification_content"
const val autoUpdateMetadata = "auto_update_metadata"
const val autoUpdateTrackers = "auto_update_trackers"
const val showLibraryUpdateErrors = "show_library_update_errors"
const val downloadNew = "download_new"
const val downloadNewCategories = "download_new_categories"
const val downloadNewCategoriesExclude = "download_new_categories_exclude"
const val libraryDisplayMode = "pref_display_mode_library"
const val lang = "app_language"
const val dateFormat = "app_date_format"
const val defaultCategory = "default_category"
const val categorizedDisplay = "categorized_display"
const val skipRead = "skip_read"
const val skipFiltered = "skip_filtered"
const val downloadBadge = "display_download_badge"
const val unreadBadge = "display_unread_badge"
const val localBadge = "display_local_badge"
const val categoryTabs = "display_category_tabs"
const val categoryNumberOfItems = "display_number_of_items"
const val alwaysShowChapterTransition = "always_show_chapter_transition"
const val searchPinnedSourcesOnly = "search_pinned_sources_only"
const val dohProvider = "doh_provider"
@ -218,7 +73,9 @@ object PreferenceKeys {
const val defaultChapterDisplayByNameOrNumber = "default_chapter_display_by_name_or_number"
const val incognitoMode = "incognito_mode"
const val verboseLogging = "verbose_logging"
const val autoClearChapterCache = "auto_clear_chapter_cache"
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"

View File

@ -2,8 +2,11 @@ package eu.kanade.tachiyomi.data.preference
import eu.kanade.tachiyomi.R
const val UNMETERED_NETWORK = "wifi"
const val CHARGING = "ac"
const val DEVICE_ONLY_ON_WIFI = "wifi"
const val DEVICE_CHARGING = "ac"
const val MANGA_ONGOING = "manga_ongoing"
const val MANGA_FULLY_READ = "manga_fully_read"
/**
* This class stores the values for the preferences in the application.
@ -22,19 +25,20 @@ object PreferenceValues {
/* ktlint-enable experimental:enum-entry-name-case */
enum class AppTheme(val titleResId: Int?) {
DEFAULT(R.string.theme_default),
DEFAULT(R.string.label_default),
MONET(R.string.theme_monet),
BLUE(R.string.theme_blue),
GREEN_APPLE(R.string.theme_greenapple),
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
TAKO(R.string.theme_tako),
YINYANG(R.string.theme_yinyang),
YOTSUBA(R.string.theme_yotsuba),
TAKO(R.string.theme_tako),
GREEN_APPLE(R.string.theme_greenapple),
TEALTURQUOISE(R.string.theme_tealturquoise),
YINYANG(R.string.theme_yinyang),
// Deprecated
DARK_BLUE(null),
HOT_PINK(null),
BLUE(null),
}
enum class TappingInvertMode(val shouldInvertHorizontal: Boolean = false, val shouldInvertVertical: Boolean = false) {
@ -43,4 +47,24 @@ object PreferenceValues {
VERTICAL(shouldInvertVertical = true),
BOTH(shouldInvertHorizontal = true, shouldInvertVertical = true),
}
enum class ReaderHideThreshold(val threshold: Int) {
HIGHEST(5),
HIGH(13),
LOW(31),
LOWEST(47),
}
enum class TabletUiMode {
AUTOMATIC,
ALWAYS,
LANDSCAPE,
NEVER,
}
enum class ExtensionInstaller {
LEGACY,
PACKAGEINSTALLER,
SHIZUKU,
}
}

View File

@ -1,25 +1,24 @@
package eu.kanade.tachiyomi.data.preference
import android.content.Context
import android.os.Build
import android.os.Environment
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.PreferenceManager
import com.tfcporciuncula.flow.FlowSharedPreferences
import com.tfcporciuncula.flow.Preference
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferenceValues.ThemeMode.*
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.anilist.Anilist
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting
import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
@ -27,25 +26,6 @@ import java.util.Locale
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
fun <T> Preference<T>.asImmediateFlow(block: (T) -> Unit): Flow<T> {
block(get())
return asFlow()
.onEach { block(it) }
}
operator fun <T> Preference<Set<T>>.plusAssign(item: T) {
set(get() + item)
}
operator fun <T> Preference<Set<T>>.minusAssign(item: T) {
set(get() - item)
}
fun Preference<Boolean>.toggle(): Boolean {
set(!get())
return get()
}
class PreferencesHelper(val context: Context) {
private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -67,17 +47,17 @@ class PreferencesHelper(val context: Context) {
fun confirmExit() = prefs.getBoolean(Keys.confirmExit, false)
fun hideBottomBarOnScroll() = flowPrefs.getBoolean(Keys.hideBottomBarOnScroll, true)
fun hideBottomBarOnScroll() = flowPrefs.getBoolean("pref_hide_bottom_bar_on_scroll", true)
fun sideNavIconAlignment() = flowPrefs.getInt(Keys.sideNavIconAlignment, 0)
fun sideNavIconAlignment() = flowPrefs.getInt("pref_side_nav_icon_alignment", 0)
fun useAuthenticator() = flowPrefs.getBoolean(Keys.useAuthenticator, false)
fun useAuthenticator() = flowPrefs.getBoolean("use_biometric_lock", false)
fun lockAppAfter() = flowPrefs.getInt(Keys.lockAppAfter, 0)
fun lockAppAfter() = flowPrefs.getInt("lock_app_after", 0)
fun lastAppUnlock() = flowPrefs.getLong(Keys.lastAppUnlock, 0)
fun lastAppUnlock() = flowPrefs.getLong("last_app_unlock", 0)
fun secureScreen() = flowPrefs.getBoolean(Keys.secureScreen, false)
fun secureScreen() = flowPrefs.getBoolean("secure_screen", false)
fun hideNotificationContent() = prefs.getBoolean(Keys.hideNotificationContent, false)
@ -85,107 +65,113 @@ class PreferencesHelper(val context: Context) {
fun autoUpdateTrackers() = prefs.getBoolean(Keys.autoUpdateTrackers, false)
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, true)
fun themeMode() = flowPrefs.getEnum(
"pref_theme_mode_key",
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { Values.ThemeMode.system } else { Values.ThemeMode.light }
)
fun themeMode() = flowPrefs.getEnum(Keys.themeMode, system)
fun appTheme() = flowPrefs.getEnum(
"pref_app_theme",
if (DeviceUtil.isDynamicColorAvailable) { Values.AppTheme.MONET } else { Values.AppTheme.DEFAULT }
)
fun appTheme() = flowPrefs.getEnum(Keys.appTheme, Values.AppTheme.DEFAULT)
fun themeDarkAmoled() = flowPrefs.getBoolean("pref_theme_dark_amoled_key", false)
fun themeDarkAmoled() = flowPrefs.getBoolean(Keys.themeDarkAmoled, false)
fun pageTransitions() = flowPrefs.getBoolean("pref_enable_transitions_key", true)
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true)
fun doubleTapAnimSpeed() = flowPrefs.getInt("pref_double_tap_anim_speed", 500)
fun doubleTapAnimSpeed() = flowPrefs.getInt(Keys.doubleTapAnimationSpeed, 500)
fun showPageNumber() = flowPrefs.getBoolean("pref_show_page_number_key", true)
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
fun dualPageSplitPaged() = flowPrefs.getBoolean("pref_dual_page_split", false)
fun dualPageSplitPaged() = flowPrefs.getBoolean(Keys.dualPageSplitPaged, false)
fun dualPageSplitWebtoon() = flowPrefs.getBoolean("pref_dual_page_split_webtoon", false)
fun dualPageSplitWebtoon() = flowPrefs.getBoolean(Keys.dualPageSplitWebtoon, false)
fun dualPageInvertPaged() = flowPrefs.getBoolean("pref_dual_page_invert", false)
fun dualPageInvertPaged() = flowPrefs.getBoolean(Keys.dualPageInvertPaged, false)
fun dualPageInvertWebtoon() = flowPrefs.getBoolean(Keys.dualPageInvertWebtoon, false)
fun dualPageInvertWebtoon() = flowPrefs.getBoolean("pref_dual_page_invert_webtoon", false)
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false)
fun trueColor() = flowPrefs.getBoolean("pref_true_color_key", false)
fun fullscreen() = flowPrefs.getBoolean(Keys.fullscreen, true)
fun fullscreen() = flowPrefs.getBoolean("fullscreen", true)
fun cutoutShort() = flowPrefs.getBoolean(Keys.cutoutShort, true)
fun cutoutShort() = flowPrefs.getBoolean("cutout_short", true)
fun keepScreenOn() = flowPrefs.getBoolean(Keys.keepScreenOn, true)
fun keepScreenOn() = flowPrefs.getBoolean("pref_keep_screen_on_key", true)
fun customBrightness() = flowPrefs.getBoolean(Keys.customBrightness, false)
fun customBrightness() = flowPrefs.getBoolean("pref_custom_brightness_key", false)
fun customBrightnessValue() = flowPrefs.getInt(Keys.customBrightnessValue, 0)
fun customBrightnessValue() = flowPrefs.getInt("custom_brightness_value", 0)
fun colorFilter() = flowPrefs.getBoolean(Keys.colorFilter, false)
fun colorFilter() = flowPrefs.getBoolean("pref_color_filter_key", false)
fun colorFilterValue() = flowPrefs.getInt(Keys.colorFilterValue, 0)
fun colorFilterValue() = flowPrefs.getInt("color_filter_value", 0)
fun colorFilterMode() = flowPrefs.getInt(Keys.colorFilterMode, 0)
fun colorFilterMode() = flowPrefs.getInt("color_filter_mode", 0)
fun grayscale() = flowPrefs.getBoolean(Keys.grayscale, false)
fun grayscale() = flowPrefs.getBoolean("pref_grayscale", false)
fun invertedColors() = flowPrefs.getBoolean("pref_inverted_colors", 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("pref_image_scale_type_key", 1)
fun zoomStart() = flowPrefs.getInt(Keys.zoomStart, 1)
fun zoomStart() = flowPrefs.getInt("pref_zoom_start_key", 1)
fun readerTheme() = flowPrefs.getInt(Keys.readerTheme, 1)
fun readerTheme() = flowPrefs.getInt("pref_reader_theme_key", 1)
fun alwaysShowChapterTransition() = flowPrefs.getBoolean(Keys.alwaysShowChapterTransition, true)
fun alwaysShowChapterTransition() = flowPrefs.getBoolean("always_show_chapter_transition", true)
fun cropBorders() = flowPrefs.getBoolean(Keys.cropBorders, false)
fun cropBorders() = flowPrefs.getBoolean("crop_borders", false)
fun cropBordersWebtoon() = flowPrefs.getBoolean(Keys.cropBordersWebtoon, false)
fun cropBordersWebtoon() = flowPrefs.getBoolean("crop_borders_webtoon", false)
fun webtoonSidePadding() = flowPrefs.getInt(Keys.webtoonSidePadding, 0)
fun webtoonSidePadding() = flowPrefs.getInt("webtoon_side_padding", 0)
fun readWithTapping() = flowPrefs.getBoolean(Keys.readWithTapping, true)
fun readWithTapping() = flowPrefs.getBoolean("reader_tap", true)
fun pagerNavInverted() = flowPrefs.getEnum(Keys.pagerNavInverted, Values.TappingInvertMode.NONE)
fun pagerNavInverted() = flowPrefs.getEnum("reader_tapping_inverted", Values.TappingInvertMode.NONE)
fun webtoonNavInverted() = flowPrefs.getEnum(Keys.webtoonNavInverted, Values.TappingInvertMode.NONE)
fun webtoonNavInverted() = flowPrefs.getEnum("reader_tapping_inverted_webtoon", Values.TappingInvertMode.NONE)
fun readWithLongTap() = flowPrefs.getBoolean(Keys.readWithLongTap, true)
fun readWithLongTap() = flowPrefs.getBoolean("reader_long_tap", true)
fun readWithVolumeKeys() = flowPrefs.getBoolean(Keys.readWithVolumeKeys, false)
fun readWithVolumeKeys() = flowPrefs.getBoolean("reader_volume_keys", false)
fun readWithVolumeKeysInverted() = flowPrefs.getBoolean(Keys.readWithVolumeKeysInverted, false)
fun readWithVolumeKeysInverted() = flowPrefs.getBoolean("reader_volume_keys_inverted", false)
fun navigationModePager() = flowPrefs.getInt(Keys.navigationModePager, 0)
fun navigationModePager() = flowPrefs.getInt("reader_navigation_mode_pager", 0)
fun navigationModeWebtoon() = flowPrefs.getInt(Keys.navigationModeWebtoon, 0)
fun navigationModeWebtoon() = flowPrefs.getInt("reader_navigation_mode_webtoon", 0)
fun showNavigationOverlayNewUser() = flowPrefs.getBoolean(Keys.showNavigationOverlayNewUser, true)
fun showNavigationOverlayNewUser() = flowPrefs.getBoolean("reader_navigation_overlay_new_user", true)
fun showNavigationOverlayOnStart() = flowPrefs.getBoolean(Keys.showNavigationOverlayOnStart, false)
fun showNavigationOverlayOnStart() = flowPrefs.getBoolean("reader_navigation_overlay_on_start", false)
fun portraitColumns() = flowPrefs.getInt(Keys.portraitColumns, 0)
fun readerHideThreshold() = flowPrefs.getEnum("reader_hide_threshold", Values.ReaderHideThreshold.LOW)
fun landscapeColumns() = flowPrefs.getInt(Keys.landscapeColumns, 0)
fun portraitColumns() = flowPrefs.getInt("pref_library_columns_portrait_key", 0)
fun landscapeColumns() = flowPrefs.getInt("pref_library_columns_landscape_key", 0)
fun jumpToChapters() = prefs.getBoolean(Keys.jumpToChapters, false)
fun updateOnlyNonCompleted() = prefs.getBoolean(Keys.updateOnlyNonCompleted, false)
fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
fun lastUsedSource() = flowPrefs.getLong(Keys.lastUsedSource, -1)
fun lastUsedSource() = flowPrefs.getLong("last_catalogue_source", -1)
fun lastUsedCategory() = flowPrefs.getInt(Keys.lastUsedCategory, 0)
fun lastUsedCategory() = flowPrefs.getInt("last_used_category", 0)
fun lastVersionCode() = flowPrefs.getInt("last_version_code", 0)
fun sourceDisplayMode() = flowPrefs.getEnum(Keys.sourceDisplayMode, DisplayModeSetting.COMPACT_GRID)
fun sourceDisplayMode() = flowPrefs.getEnum("pref_display_mode_catalogue", DisplayModeSetting.COMPACT_GRID)
fun enabledLanguages() = flowPrefs.getStringSet(Keys.enabledLanguages, setOf("en", Locale.getDefault().language))
fun enabledLanguages() = flowPrefs.getStringSet("source_languages", setOf("all", "en", Locale.getDefault().language))
fun trackUsername(sync: TrackService) = prefs.getString(Keys.trackUsername(sync.id), "")
@ -202,22 +188,26 @@ class PreferencesHelper(val context: Context) {
fun anilistScoreType() = flowPrefs.getString("anilist_score_type", Anilist.POINT_10)
fun backupsDirectory() = flowPrefs.getString(Keys.backupDirectory, defaultBackupDir.toString())
fun backupsDirectory() = flowPrefs.getString("backup_directory", defaultBackupDir.toString())
fun relativeTime() = flowPrefs.getInt("relative_time", 7)
fun dateFormat(format: String = flowPrefs.getString(Keys.dateFormat, "").get()): DateFormat = when (format) {
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
else -> SimpleDateFormat(format, Locale.getDefault())
}
fun downloadsDirectory() = flowPrefs.getString(Keys.downloadsDirectory, defaultDownloadsDir.toString())
fun downloadsDirectory() = flowPrefs.getString("download_directory", defaultDownloadsDir.toString())
fun downloadOnlyOverWifi() = prefs.getBoolean(Keys.downloadOnlyOverWifi, true)
fun saveChaptersAsCBZ() = flowPrefs.getBoolean("save_chapter_as_cbz", false)
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
fun numberOfBackups() = flowPrefs.getInt(Keys.numberOfBackups, 1)
fun numberOfBackups() = flowPrefs.getInt("backup_slots", 1)
fun backupInterval() = flowPrefs.getInt(Keys.backupInterval, 0)
fun backupInterval() = flowPrefs.getInt("backup_interval", 0)
fun removeAfterReadSlots() = prefs.getInt(Keys.removeAfterReadSlots, -1)
@ -225,28 +215,34 @@ class PreferencesHelper(val context: Context) {
fun removeBookmarkedChapters() = prefs.getBoolean(Keys.removeBookmarkedChapters, false)
fun libraryUpdateInterval() = flowPrefs.getInt(Keys.libraryUpdateInterval, 24)
fun removeExcludeCategories() = flowPrefs.getStringSet("remove_exclude_categories", emptySet())
fun libraryUpdateRestriction() = flowPrefs.getStringSet(Keys.libraryUpdateRestriction, setOf(UNMETERED_NETWORK))
fun libraryUpdateInterval() = flowPrefs.getInt("pref_library_update_interval_key", 24)
fun libraryUpdateCategories() = flowPrefs.getStringSet(Keys.libraryUpdateCategories, emptySet())
fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet(Keys.libraryUpdateCategoriesExclude, emptySet())
fun libraryUpdateDeviceRestriction() = flowPrefs.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_FULLY_READ, MANGA_ONGOING))
fun libraryUpdatePrioritization() = flowPrefs.getInt(Keys.libraryUpdatePrioritization, 0)
fun showUpdatesNavBadge() = flowPrefs.getBoolean("library_update_show_tab_badge", false)
fun unreadUpdatesCount() = flowPrefs.getInt("library_unread_updates_count", 0)
fun libraryDisplayMode() = flowPrefs.getEnum(Keys.libraryDisplayMode, DisplayModeSetting.COMPACT_GRID)
fun libraryUpdateCategories() = flowPrefs.getStringSet("library_update_categories", emptySet())
fun libraryUpdateCategoriesExclude() = flowPrefs.getStringSet("library_update_categories_exclude", emptySet())
fun downloadBadge() = flowPrefs.getBoolean(Keys.downloadBadge, false)
fun libraryDisplayMode() = flowPrefs.getEnum("pref_display_mode_library", DisplayModeSetting.COMPACT_GRID)
fun localBadge() = flowPrefs.getBoolean(Keys.localBadge, true)
fun downloadBadge() = flowPrefs.getBoolean("display_download_badge", false)
fun downloadedOnly() = flowPrefs.getBoolean(Keys.downloadedOnly, false)
fun localBadge() = flowPrefs.getBoolean("display_local_badge", true)
fun unreadBadge() = flowPrefs.getBoolean(Keys.unreadBadge, true)
fun downloadedOnly() = flowPrefs.getBoolean("pref_downloaded_only", false)
fun categoryTabs() = flowPrefs.getBoolean(Keys.categoryTabs, true)
fun unreadBadge() = flowPrefs.getBoolean("display_unread_badge", true)
fun categoryNumberOfItems() = flowPrefs.getBoolean(Keys.categoryNumberOfItems, false)
fun languageBadge() = flowPrefs.getBoolean("display_language_badge", false)
fun categoryTabs() = flowPrefs.getBoolean("display_category_tabs", true)
fun categoryNumberOfItems() = flowPrefs.getBoolean("display_number_of_items", false)
fun filterDownloaded() = flowPrefs.getInt(Keys.filterDownloaded, ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)
@ -259,14 +255,16 @@ class PreferencesHelper(val context: Context) {
fun librarySortingMode() = flowPrefs.getEnum(Keys.librarySortingMode, SortModeSetting.ALPHABETICAL)
fun librarySortingAscending() = flowPrefs.getEnum(Keys.librarySortingDirection, SortDirectionSetting.ASCENDING)
fun automaticExtUpdates() = flowPrefs.getBoolean(Keys.automaticExtUpdates, true)
fun migrationSortingMode() = flowPrefs.getEnum(Keys.migrationSortingMode, MigrationSourcesController.SortSetting.ALPHABETICAL)
fun migrationSortingDirection() = flowPrefs.getEnum(Keys.migrationSortingDirection, MigrationSourcesController.DirectionSetting.ASCENDING)
fun showNsfwSource() = flowPrefs.getBoolean(Keys.showNsfwSource, true)
fun showNsfwExtension() = flowPrefs.getBoolean(Keys.showNsfwExtension, true)
fun labelNsfwExtension() = prefs.getBoolean(Keys.labelNsfwExtension, true)
fun automaticExtUpdates() = flowPrefs.getBoolean("automatic_ext_updates", true)
fun showNsfwSource() = flowPrefs.getBoolean("show_nsfw_source", true)
fun extensionUpdatesCount() = flowPrefs.getInt("ext_updates_count", 0)
fun lastAppCheck() = flowPrefs.getLong("last_app_check", 0)
fun lastExtCheck() = flowPrefs.getLong("last_ext_check", 0)
fun searchPinnedSourcesOnly() = prefs.getBoolean(Keys.searchPinnedSourcesOnly, false)
@ -275,16 +273,14 @@ class PreferencesHelper(val context: Context) {
fun pinnedSources() = flowPrefs.getStringSet("pinned_catalogues", emptySet())
fun downloadNew() = flowPrefs.getBoolean(Keys.downloadNew, false)
fun downloadNew() = flowPrefs.getBoolean("download_new", false)
fun downloadNewCategories() = flowPrefs.getStringSet(Keys.downloadNewCategories, emptySet())
fun downloadNewCategoriesExclude() = flowPrefs.getStringSet(Keys.downloadNewCategoriesExclude, emptySet())
fun lang() = flowPrefs.getString(Keys.lang, "")
fun downloadNewCategories() = flowPrefs.getStringSet("download_new_categories", emptySet())
fun downloadNewCategoriesExclude() = flowPrefs.getStringSet("download_new_categories_exclude", emptySet())
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
fun categorisedDisplaySettings() = flowPrefs.getBoolean(Keys.categorizedDisplay, false)
fun categorizedDisplaySettings() = flowPrefs.getBoolean("categorized_display", false)
fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
@ -310,7 +306,18 @@ class PreferencesHelper(val context: Context) {
fun sortChapterByAscendingOrDescending() = prefs.getInt(Keys.defaultChapterSortByAscendingOrDescending, Manga.CHAPTER_SORT_DESC)
fun incognitoMode() = flowPrefs.getBoolean(Keys.incognitoMode, false)
fun incognitoMode() = flowPrefs.getBoolean("incognito_mode", false)
fun tabletUiMode() = flowPrefs.getEnum("tablet_ui_mode", Values.TabletUiMode.AUTOMATIC)
fun extensionInstaller() = flowPrefs.getEnum(
"extension_installer",
if (DeviceUtil.isMiui) Values.ExtensionInstaller.LEGACY else Values.ExtensionInstaller.PACKAGEINSTALLER
)
fun verboseLogging() = prefs.getBoolean(Keys.verboseLogging, false)
fun autoClearChapterCache() = prefs.getBoolean(Keys.autoClearChapterCache, false)
fun setChapterSettingsDefault(manga: Manga) {
prefs.edit {

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.track
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
@ -25,4 +26,14 @@ interface EnhancedTrackService {
* match is similar to TrackService.search, but only return zero or one match.
*/
suspend fun match(manga: Manga): TrackSearch?
/**
* Checks whether the provided source/track/manga triplet is from this TrackService
*/
fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean
/**
* Migrates the given track for the manga to the newSource, if possible
*/
fun migrateTrack(track: Track, manga: Manga, newSource: Source): Track?
}

View File

@ -17,10 +17,10 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
companion object {
const val READING = 1
const val COMPLETED = 2
const val PAUSED = 3
const val ON_HOLD = 3
const val DROPPED = 4
const val PLANNING = 5
const val REPEATING = 6
const val PLAN_TO_READ = 5
const val REREADING = 6
const val POINT_100 = "POINT_100"
const val POINT_10 = "POINT_10"
@ -57,24 +57,24 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
override fun getLogoColor() = Color.rgb(18, 25, 35)
override fun getStatusList(): List<Int> {
return listOf(READING, PLANNING, COMPLETED, REPEATING, PAUSED, DROPPED)
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
}
override fun getStatus(status: Int): String = with(context) {
when (status) {
READING -> getString(R.string.reading)
PLANNING -> getString(R.string.plan_to_read)
PLAN_TO_READ -> getString(R.string.plan_to_read)
COMPLETED -> getString(R.string.completed)
REPEATING -> getString(R.string.repeating)
PAUSED -> getString(R.string.paused)
ON_HOLD -> getString(R.string.on_hold)
DROPPED -> getString(R.string.dropped)
REREADING -> getString(R.string.repeating)
else -> ""
}
}
override fun getReadingStatus(): Int = READING
override fun getRereadingStatus(): Int = REPEATING
override fun getRereadingStatus(): Int = REREADING
override fun getCompletionStatus(): Int = COMPLETED
@ -147,8 +147,16 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
}
if (track.status != COMPLETED) {
if (track.status != REPEATING && didReadChapter) {
track.status = READING
if (didReadChapter) {
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
track.status = COMPLETED
track.finished_reading_date = System.currentTimeMillis()
} else if (track.status != REREADING) {
track.status = READING
if (track.last_chapter_read == 1F) {
track.started_reading_date = System.currentTimeMillis()
}
}
}
}
@ -162,14 +170,14 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
track.library_id = remoteTrack.library_id
if (track.status != COMPLETED) {
val isRereading = track.status == REPEATING
val isRereading = track.status == REREADING
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
}
update(track)
} else {
// Set default fields if it's not found in the list
track.status = if (hasReadChapters) READING else PLANNING
track.status = if (hasReadChapters) READING else PLAN_TO_READ
track.score = 0F
add(track)
}
@ -182,6 +190,7 @@ class Anilist(private val context: Context, id: Int) : TrackService(id) {
override suspend fun refresh(track: Track): Track {
val remoteTrack = api.getLibManga(track, getUsername().toInt())
track.copyPersonalFrom(remoteTrack)
track.title = remoteTrack.title
track.total_chapters = remoteTrack.total_chapters
return track
}

View File

@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.interceptor.RateLimitInterceptor
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.network.jsonMime
import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.lang.withIOContext
@ -25,13 +25,13 @@ import kotlinx.serialization.json.putJsonObject
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Calendar
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private val authClient = client.newBuilder()
.addInterceptor(interceptor)
.addInterceptor(RateLimitInterceptor(85, 1, MINUTES))
.rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES)
.build()
suspend fun addLibManga(track: Track): Track {
@ -48,7 +48,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", query)
putJsonObject("variables") {
put("mangaId", track.media_id)
put("progress", track.last_chapter_read)
put("progress", track.last_chapter_read.toInt())
put("status", track.toAnilistStatus())
}
}
@ -89,7 +89,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", query)
putJsonObject("variables") {
put("listId", track.library_id)
put("progress", track.last_chapter_read)
put("progress", track.last_chapter_read.toInt())
put("status", track.toAnilistStatus())
put("score", track.score.toInt())
put("startedAt", createDate(track.started_reading_date))
@ -110,12 +110,12 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|media(search: ${'$'}query, type: MANGA, format_not_in: [NOVEL]) {
|id
|title {
|romaji
|userPreferred
|}
|coverImage {
|large
|}
|type
|format
|status
|chapters
|description
@ -175,12 +175,12 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|media {
|id
|title {
|romaji
|userPreferred
|}
|coverImage {
|large
|}
|type
|format
|status
|chapters
|description
@ -264,10 +264,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private fun jsonToALManga(struct: JsonObject): ALManga {
return ALManga(
struct["id"]!!.jsonPrimitive.int,
struct["title"]!!.jsonObject["romaji"]!!.jsonPrimitive.content,
struct["title"]!!.jsonObject["userPreferred"]!!.jsonPrimitive.content,
struct["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
struct["description"]!!.jsonPrimitive.contentOrNull,
struct["type"]!!.jsonPrimitive.content,
struct["format"]!!.jsonPrimitive.content.replace("_", "-"),
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
parseDate(struct, "startDate"),
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.track.anilist
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Interceptor {
@ -28,12 +29,12 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
// Refresh access token if null or expired.
if (oauth!!.isExpired()) {
anilist.logout()
throw Exception("Token expired")
throw IOException("Token expired")
}
// Throw on null auth.
if (oauth == null) {
throw Exception("No authentication token")
throw IOException("No authentication token")
}
// Add the authorization header to the original request.

View File

@ -10,10 +10,10 @@ import java.util.Locale
data class ALManga(
val media_id: Int,
val title_romaji: String,
val title_user_pref: String,
val image_url_lge: String,
val description: String?,
val type: String,
val format: String,
val publishing_status: String,
val start_date_fuzzy: Long,
val total_chapters: Int
@ -21,13 +21,13 @@ data class ALManga(
fun toTrack() = TrackSearch.create(TrackManager.ANILIST).apply {
media_id = this@ALManga.media_id
title = title_romaji
title = title_user_pref
total_chapters = this@ALManga.total_chapters
cover_url = image_url_lge
summary = description ?: ""
tracking_url = AnilistApi.mangaUrl(media_id)
publishing_status = this@ALManga.publishing_status
publishing_type = type
publishing_type = format
if (start_date_fuzzy != 0L) {
start_date = try {
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
@ -51,11 +51,12 @@ data class ALUserManga(
fun toTrack() = Track.create(TrackManager.ANILIST).apply {
media_id = manga.media_id
title = manga.title_user_pref
status = toTrackStatus()
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.toFloat()
library_id = this@ALUserManga.library_id
total_chapters = manga.total_chapters
}
@ -63,10 +64,10 @@ data class ALUserManga(
fun toTrackStatus() = when (list_status) {
"CURRENT" -> Anilist.READING
"COMPLETED" -> Anilist.COMPLETED
"PAUSED" -> Anilist.PAUSED
"PAUSED" -> Anilist.ON_HOLD
"DROPPED" -> Anilist.DROPPED
"PLANNING" -> Anilist.PLANNING
"REPEATING" -> Anilist.REPEATING
"PLANNING" -> Anilist.PLAN_TO_READ
"REPEATING" -> Anilist.REREADING
else -> throw NotImplementedError("Unknown status: $list_status")
}
}
@ -74,10 +75,10 @@ data class ALUserManga(
fun Track.toAnilistStatus() = when (status) {
Anilist.READING -> "CURRENT"
Anilist.COMPLETED -> "COMPLETED"
Anilist.PAUSED -> "PAUSED"
Anilist.ON_HOLD -> "PAUSED"
Anilist.DROPPED -> "DROPPED"
Anilist.PLANNING -> "PLANNING"
Anilist.REPEATING -> "REPEATING"
Anilist.PLAN_TO_READ -> "PLANNING"
Anilist.REREADING -> "REPEATING"
else -> throw NotImplementedError("Unknown status: $status")
}

View File

@ -38,7 +38,11 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
track.status = COMPLETED
} else {
track.status = READING
}
}
}
@ -62,7 +66,7 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
refresh(track)
} else {
// Set default fields if it's not found in the list
track.status = if (hasReadChapters) READING else PLANNING
track.status = if (hasReadChapters) READING else PLAN_TO_READ
track.score = 0F
add(track)
update(track)
@ -87,16 +91,16 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
override fun getLogoColor() = Color.rgb(240, 145, 153)
override fun getStatusList(): List<Int> {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLANNING)
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
}
override fun getStatus(status: Int): String = with(context) {
when (status) {
READING -> getString(R.string.reading)
PLAN_TO_READ -> getString(R.string.plan_to_read)
COMPLETED -> getString(R.string.completed)
ON_HOLD -> getString(R.string.on_hold)
DROPPED -> getString(R.string.dropped)
PLANNING -> getString(R.string.plan_to_read)
else -> ""
}
}
@ -142,6 +146,6 @@ class Bangumi(private val context: Context, id: Int) : TrackService(id) {
const val COMPLETED = 2
const val ON_HOLD = 4
const val DROPPED = 5
const val PLANNING = 1
const val PLAN_TO_READ = 1
}
}

View File

@ -24,6 +24,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
class BangumiApi(private val client: OkHttpClient, interceptor: BangumiInterceptor) {
@ -55,7 +56,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
// chapter update
val body = FormBody.Builder()
.add("watched_eps", track.last_chapter_read.toString())
.add("watched_eps", track.last_chapter_read.toInt().toString())
.build()
authClient.newCall(
POST(
@ -70,7 +71,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
suspend fun search(search: String): List<TrackSearch> {
return withIOContext {
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, Charsets.UTF_8.name())}"
val url = "$apiUrl/search/subject/${URLEncoder.encode(search, StandardCharsets.UTF_8.name())}"
.toUri()
.buildUpon()
.appendQueryParameter("max_results", "20")
@ -143,7 +144,7 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
} else {
json.decodeFromString<Collection>(responseBody).let {
track.status = it.status?.id!!
track.last_chapter_read = it.ep_status!!
track.last_chapter_read = it.ep_status!!.toFloat()
track.score = it.rating!!
track
}

View File

@ -16,15 +16,6 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
*/
private var oauth: OAuth? = bangumi.restoreToken()
fun addToken(token: String, oidFormBody: FormBody): FormBody {
val newFormBody = FormBody.Builder()
for (i in 0 until oidFormBody.size) {
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
}
newFormBody.add("access_token", token)
return newFormBody.build()
}
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
@ -65,4 +56,13 @@ class BangumiInterceptor(val bangumi: Bangumi) : Interceptor {
bangumi.saveToken(oauth)
}
private fun addToken(token: String, oidFormBody: FormBody): FormBody {
val newFormBody = FormBody.Builder()
for (i in 0 until oidFormBody.size) {
newFormBody.add(oidFormBody.name(i), oidFormBody.value(i))
}
newFormBody.add("access_token", token)
return newFormBody.build()
}
}

View File

@ -7,7 +7,7 @@ fun Track.toBangumiStatus() = when (status) {
Bangumi.COMPLETED -> "collect"
Bangumi.ON_HOLD -> "on_hold"
Bangumi.DROPPED -> "dropped"
Bangumi.PLANNING -> "wish"
Bangumi.PLAN_TO_READ -> "wish"
else -> throw NotImplementedError("Unknown status: $status")
}
@ -16,6 +16,6 @@ fun toTrackStatus(status: String) = when (status) {
"collect" -> Bangumi.COMPLETED
"on_hold" -> Bangumi.ON_HOLD
"dropped" -> Bangumi.DROPPED
"wish" -> Bangumi.PLANNING
"wish" -> Bangumi.PLAN_TO_READ
else -> throw NotImplementedError("Unknown status: $status")
}

View File

@ -0,0 +1,52 @@
package eu.kanade.tachiyomi.data.track.job
import android.content.Context
import androidx.core.content.edit
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
class DelayedTrackingStore(context: Context) {
/**
* Preference file where queued tracking updates are stored.
*/
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
fun addItem(track: Track) {
val trackId = track.id.toString()
val (_, lastChapterRead) = preferences.getString(trackId, "0:0.0")!!.split(":")
if (track.last_chapter_read > lastChapterRead.toFloat()) {
val value = "${track.manga_id}:${track.last_chapter_read}"
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, $value" }
preferences.edit {
putString(trackId, value)
}
}
}
fun clear() {
preferences.edit {
clear()
}
}
@Suppress("UNCHECKED_CAST")
fun getItems(): List<DelayedTrackingItem> {
return (preferences.all as Map<String, String>).entries
.map {
val (mangaId, lastChapterRead) = it.value.split(":")
DelayedTrackingItem(
trackId = it.key.toLong(),
mangaId = mangaId.toLong(),
lastChapterRead = lastChapterRead.toFloat(),
)
}
}
data class DelayedTrackingItem(
val trackId: Long,
val mangaId: Long,
val lastChapterRead: Float,
)
}

View File

@ -0,0 +1,76 @@
package eu.kanade.tachiyomi.data.track.job
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import logcat.LogPriority
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val db = Injekt.get<DatabaseHelper>()
val trackManager = Injekt.get<TrackManager>()
val delayedTrackingStore = Injekt.get<DelayedTrackingStore>()
withContext(Dispatchers.IO) {
val tracks = delayedTrackingStore.getItems().mapNotNull {
val manga = db.getManga(it.mangaId).executeAsBlocking() ?: return@withContext
db.getTracks(manga).executeAsBlocking()
.find { track -> track.id == it.trackId }
?.also { track ->
track.last_chapter_read = it.lastChapterRead
}
}
tracks.forEach { track ->
try {
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged) {
service.update(track, true)
db.insertTrack(track).executeAsBlocking()
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
delayedTrackingStore.clear()
}
return Result.success()
}
companion object {
private const val TAG = "DelayedTrackingUpdate"
fun setupTask(context: Context) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
.addTag(TAG)
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
}
}
}

View File

@ -39,13 +39,13 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
override fun getLogoColor() = Color.rgb(51, 37, 50)
override fun getStatusList(): List<Int> {
return listOf(READING, PLAN_TO_READ, COMPLETED, ON_HOLD, DROPPED)
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
}
override fun getStatus(status: Int): String = with(context) {
when (status) {
READING -> getString(R.string.currently_reading)
PLAN_TO_READ -> getString(R.string.want_to_read)
READING -> getString(R.string.reading)
PLAN_TO_READ -> getString(R.string.plan_to_read)
COMPLETED -> getString(R.string.completed)
ON_HOLD -> getString(R.string.on_hold)
DROPPED -> getString(R.string.dropped)
@ -80,7 +80,15 @@ class Kitsu(private val context: Context, id: Int) : TrackService(id) {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
track.status = COMPLETED
track.finished_reading_date = System.currentTimeMillis()
} else {
track.status = READING
if (track.last_chapter_read == 1F) {
track.started_reading_date = System.currentTimeMillis()
}
}
}
}

View File

@ -24,6 +24,8 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) {
@ -36,7 +38,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
put("type", "libraryEntries")
putJsonObject("attributes") {
put("status", track.toKitsuStatus())
put("progress", track.last_chapter_read)
put("progress", track.last_chapter_read.toInt())
}
putJsonObject("relationships") {
putJsonObject("user") {
@ -82,7 +84,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
put("id", track.media_id)
putJsonObject("attributes") {
put("status", track.toKitsuStatus())
put("progress", track.last_chapter_read)
put("progress", track.last_chapter_read.toInt())
put("ratingTwenty", track.toKitsuScore())
put("startedAt", KitsuDateHelper.convert(track.started_reading_date))
put("finishedAt", KitsuDateHelper.convert(track.finished_reading_date))
@ -125,7 +127,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
private suspend fun algoliaSearch(key: String, query: String): List<TrackSearch> {
return withIOContext {
val jsonObject = buildJsonObject {
put("params", "query=$query$algoliaFilter")
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter")
}
client.newCall(

View File

@ -25,7 +25,7 @@ class KitsuSearchManga(obj: JsonObject) {
// posterImage is sometimes a jsonNull object instead
null
}
private val synopsis = obj["synopsis"]!!.jsonPrimitive.content
private val synopsis = obj["synopsis"]?.jsonPrimitive?.contentOrNull
private var startDate = obj["startDate"]?.jsonPrimitive?.contentOrNull?.let {
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
outputDf.format(Date(it.toLong() * 1000))
@ -38,7 +38,7 @@ class KitsuSearchManga(obj: JsonObject) {
title = canonicalTitle
total_chapters = chapterCount ?: 0
cover_url = original ?: ""
summary = synopsis
summary = synopsis ?: ""
tracking_url = KitsuApi.mangaUrl(media_id)
publishing_status = if (endDate == null) {
"Publishing"
@ -79,7 +79,7 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
finished_reading_date = KitsuDateHelper.parse(finishedAt)
status = toTrackStatus()
score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f
last_chapter_read = progress
last_chapter_read = progress.toFloat()
}
private fun toTrackStatus() = when (status) {

View File

@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.NoLoginTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import okhttp3.Dns
import okhttp3.OkHttpClient
@ -40,7 +41,7 @@ class Komga(private val context: Context, id: Int) : TrackService(id), EnhancedT
override fun getStatus(status: Int): String = with(context) {
when (status) {
UNREAD -> getString(R.string.unread)
READING -> getString(R.string.currently_reading)
READING -> getString(R.string.reading)
COMPLETED -> getString(R.string.completed)
else -> ""
}
@ -59,7 +60,11 @@ class Komga(private val context: Context, id: Int) : TrackService(id), EnhancedT
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (didReadChapter) {
track.status = READING
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
track.status = COMPLETED
} else {
track.status = READING
}
}
}
@ -99,4 +104,14 @@ class Komga(private val context: Context, id: Int) : TrackService(id), EnhancedT
} catch (e: Exception) {
null
}
override fun isTrackFrom(track: Track, manga: Manga, source: Source?): Boolean =
track.tracking_url == manga.url && source?.let { accept(it) } == true
override fun migrateTrack(track: Track, manga: Manga, newSource: Source): Track? =
if (accept(newSource)) {
track.also { track.tracking_url = manga.url }
} else {
null
}
}

View File

@ -7,13 +7,14 @@ 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 eu.kanade.tachiyomi.util.system.logcat
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import logcat.LogPriority
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"
@ -38,33 +39,38 @@ class KomgaApi(private val client: OkHttpClient) {
}
val progress = client
.newCall(GET("$url/read-progress/tachiyomi"))
.await()
.parseAs<ReadProgressDto>()
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
.await().let {
if (url.contains("/api/v1/series/")) it.parseAs<ReadProgressV2Dto>()
else it.parseAs<ReadProgressDto>().toV2()
}
track.apply {
cover_url = "$url/thumbnail"
tracking_url = url
total_chapters = progress.booksCount
total_chapters = progress.maxNumberSort.toInt()
status = when (progress.booksCount) {
progress.booksUnreadCount -> Komga.UNREAD
progress.booksReadCount -> Komga.COMPLETED
else -> Komga.READING
}
last_chapter_read = progress.lastReadContinuousIndex
last_chapter_read = progress.lastReadContinuousNumberSort
}
} catch (e: Exception) {
Timber.w(e, "Could not get item: $url")
logcat(LogPriority.WARN, 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)
val payload = if (track.tracking_url.contains("/api/v1/series/")) {
json.encodeToString(ReadProgressUpdateV2Dto(track.last_chapter_read))
} else {
json.encodeToString(ReadProgressUpdateDto(track.last_chapter_read.toInt()))
}
client.newCall(
Request.Builder()
.url("${track.tracking_url}/read-progress/tachiyomi")
.url("${track.tracking_url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi")
.put(payload.toRequestBody("application/json".toMediaType()))
.build()
)

View File

@ -63,6 +63,11 @@ data class ReadProgressUpdateDto(
val lastBookRead: Int,
)
@Serializable
data class ReadProgressUpdateV2Dto(
val lastBookNumberSortRead: Float,
)
@Serializable
data class ReadListDto(
val id: String,
@ -80,4 +85,23 @@ data class ReadProgressDto(
val booksUnreadCount: Int,
val booksInProgressCount: Int,
val lastReadContinuousIndex: Int,
) {
fun toV2() = ReadProgressV2Dto(
booksCount,
booksReadCount,
booksUnreadCount,
booksInProgressCount,
lastReadContinuousIndex.toFloat(),
booksCount.toFloat(),
)
}
@Serializable
data class ReadProgressV2Dto(
val booksCount: Int,
val booksReadCount: Int,
val booksUnreadCount: Int,
val booksInProgressCount: Int,
val lastReadContinuousNumberSort: Float,
val maxNumberSort: Float,
)

View File

@ -16,7 +16,7 @@ class TrackSearch : Track {
override lateinit var title: String
override var last_chapter_read: Int = 0
override var last_chapter_read: Float = 0F
override var total_chapters: Int = 0

View File

@ -47,10 +47,10 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
override fun getStatus(status: Int): String = with(context) {
when (status) {
READING -> getString(R.string.reading)
PLAN_TO_READ -> getString(R.string.plan_to_read)
COMPLETED -> getString(R.string.completed)
ON_HOLD -> getString(R.string.on_hold)
DROPPED -> getString(R.string.dropped)
PLAN_TO_READ -> getString(R.string.plan_to_read)
REREADING -> getString(R.string.repeating)
else -> ""
}
@ -76,8 +76,16 @@ class MyAnimeList(private val context: Context, id: Int) : TrackService(id) {
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
if (track.status != COMPLETED) {
if (track.status != REREADING && didReadChapter) {
track.status = READING
if (didReadChapter) {
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
track.status = COMPLETED
track.finished_reading_date = System.currentTimeMillis()
} else if (track.status != REREADING) {
track.status = READING
if (track.last_chapter_read == 1F) {
track.started_reading_date = System.currentTimeMillis()
}
}
}
}

View File

@ -16,6 +16,7 @@ import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.float
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@ -61,7 +62,8 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
suspend fun search(query: String): List<TrackSearch> {
return withIOContext {
val url = "$baseApiUrl/manga".toUri().buildUpon()
.appendQueryParameter("q", query)
// MAL API throws a 400 when the query is over 64 characters...
.appendQueryParameter("q", query.take(64))
.appendQueryParameter("nsfw", "true")
.build()
authClient.newCall(GET(url.toString()))
@ -75,7 +77,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
async { getMangaDetails(id) }
}
.awaitAll()
.filter { trackSearch -> trackSearch.publishing_type != "novel" }
.filter { trackSearch -> !trackSearch.publishing_type.contains("novel") }
}
}
}
@ -117,7 +119,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.add("status", track.toMyAnimeListStatus() ?: "reading")
.add("is_rereading", (track.status == MyAnimeList.REREADING).toString())
.add("score", track.score.toString())
.add("num_chapters_read", track.last_chapter_read.toString())
.add("num_chapters_read", track.last_chapter_read.toInt().toString())
convertToIsoDate(track.started_reading_date)?.let {
formBodyBuilder.add("start_date", it)
}
@ -205,7 +207,7 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
return track.apply {
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]!!.jsonPrimitive.content)
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.int
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.float
score = obj["score"]!!.jsonPrimitive.int.toFloat()
obj["start_date"]?.let {
started_reading_date = parseDate(it.jsonPrimitive.content)

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