Compare commits

...

458 Commits

Author SHA1 Message Date
Koitharu
8cc04b0f7a Add Remove from history action on details activity 2024-02-18 10:07:42 +02:00
Koitharu
258dbf3dc3 Fix reader info bar text size 2024-02-18 09:26:57 +02:00
Koitharu
e7af4e8450 Fix backup restore dialog layout #770 2024-02-18 09:20:34 +02:00
Koitharu
0c25c61858 Fix filtering pages by branches 2024-02-18 09:14:49 +02:00
Koitharu
abc3e45907 Fix reader state on changed 2024-02-18 09:08:22 +02:00
Koitharu
bd98d8eded Fix onboarding sources selection 2024-02-18 08:44:13 +02:00
Koitharu
2e81f41073 Update dependencies 2024-02-18 08:44:13 +02:00
Oğuz Ersen
5cccebc416 Translated using Weblate (Turkish)
Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-18 08:43:59 +02:00
Макар Разин
c668ffd555 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-18 08:43:59 +02:00
gekka
a0f77b715f Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (589 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (590 of 591 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-18 08:43:59 +02:00
Anonymous
2831843a25 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.8% (590 of 591 strings)

Co-authored-by: Anonymous <noreply@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-18 08:43:59 +02:00
Koitharu
86c1aa11b0 Fix crash 2024-02-17 12:45:02 +02:00
Koitharu
d71514ec7a Configurable dir for silent pages saving 2024-02-17 12:31:12 +02:00
Koitharu
92ed320f57 Add compact navbar option 2024-02-17 10:41:55 +02:00
Koitharu
2de1fe8b77 Fix icon tint 2024-02-16 12:29:52 +02:00
vianh
cebc3cd9e8 Adjust search screen ui 2024-02-16 10:40:23 +02:00
vianh
6c0e2e2b90 Fix mirror domain switching 2024-02-16 10:40:23 +02:00
vianh
b4bd923ce8 Improve webtoon zoom fling 2024-02-16 10:39:17 +02:00
Koitharu
813561fd3b Merge remote-tracking branch 'weblate/devel' into devel 2024-02-16 10:37:43 +02:00
Jordan \"Tes\" Michel
4107336132 Translated using Weblate (French)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (French)

Currently translated at 93.4% (552 of 591 strings)

Co-authored-by: Jordan \"Tes\" Michel <jordan.michel13@yahoo.fr>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-16 10:35:52 +02:00
Anon
30d9d87c17 Translated using Weblate (Serbian)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Serbian)

Currently translated at 99.6% (589 of 591 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-02-16 10:35:52 +02:00
gekka
c4b5be657d Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-16 10:35:51 +02:00
Koitharu
8a763b2b9f Bring back adaptive reader control preference 2024-02-16 10:35:27 +02:00
Koitharu
c783378022 Update app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt 2024-02-15 10:26:22 +02:00
Isira Seneviratne
c4355f16e8 Improve serializable extra extension 2024-02-15 10:26:22 +02:00
Isira Seneviratne
522dfc2418 Use SoftwareKeyboardControllerCompat 2024-02-15 10:22:28 +02:00
Koitharu
06d03e3ddd Fix SyncSettings lifecycle 2024-02-14 12:49:46 +02:00
Koitharu
9dc8c7959d Update parsers 2024-02-14 12:19:08 +02:00
Davi Silveira
db219020ca Translated using Weblate (Portuguese)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Davi Silveira <davilego10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-14 12:00:37 +02:00
Aiman Sara
c04edcb76c Translated using Weblate (Malay)
Currently translated at 53.9% (319 of 591 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Aiman Sara <aimansara21@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-14 12:00:37 +02:00
Anon
936fc2e4ae Translated using Weblate (Serbian)
Currently translated at 99.6% (589 of 591 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-02-14 12:00:37 +02:00
gekka
cbed866665 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-14 12:00:37 +02:00
Madaraki
ac568b6361 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-14 12:00:37 +02:00
Oğuz Ersen
84157f988d Translated using Weblate (Turkish)
Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-14 12:00:37 +02:00
gallegonovato
6f6339f0f8 Translated using Weblate (Spanish)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (589 of 589 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-02-14 12:00:37 +02:00
Jordan \"Tes\" Michel
a7019b9096 Translated using Weblate (French)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (French)

Currently translated at 93.4% (552 of 591 strings)

Co-authored-by: Jordan \"Tes\" Michel <jordan.michel13@yahoo.fr>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-14 10:33:12 +01:00
Davi Silveira
867e3f10ca Translated using Weblate (Portuguese)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Davi Silveira <davilego10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-14 10:33:12 +01:00
Aiman Sara
fb2cf04d75 Translated using Weblate (Malay)
Currently translated at 53.9% (319 of 591 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Aiman Sara <aimansara21@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-14 10:33:12 +01:00
Anon
3ed44ba0d6 Translated using Weblate (Serbian)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Serbian)

Currently translated at 99.6% (589 of 591 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-02-14 10:33:12 +01:00
gekka
b78104a0f1 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-14 10:33:12 +01:00
Madaraki
e4ee93f77c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-14 10:33:11 +01:00
Oğuz Ersen
c6e8da5f23 Translated using Weblate (Turkish)
Currently translated at 100.0% (591 of 591 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-14 10:33:11 +01:00
gallegonovato
376de7cce3 Translated using Weblate (Spanish)
Currently translated at 100.0% (591 of 591 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (589 of 589 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-02-14 10:33:11 +01:00
Koitharu
bec2195971 Fix processing tap actions in reader when not resumed 2024-02-13 08:03:09 +02:00
Zakhar Timoshenko
722ac4ecc7 Tweak chapter item 2024-02-12 19:02:23 +03:00
Koitharu
516c1c02a6 Improve chapters list ui accessibility #752 2024-02-12 14:26:20 +02:00
Koitharu
0cb7e71781 Fix chapters selection decoration 2024-02-12 09:07:32 +02:00
Koitharu
36a74f32df Show hint if navigation section is unavailable #751 2024-02-10 16:52:49 +02:00
Koitharu
0e4ef32642 Show battery percentage in reader bar 2024-02-10 16:33:18 +02:00
Koitharu
3125cac4c8 Use constant textSize for reader info bar #748 2024-02-10 15:16:08 +02:00
Koitharu
5d9016d1bc Do not query suggestions by blacklisted tags #749 2024-02-10 14:59:25 +02:00
Koitharu
c5eeb89d10 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (589 of 589 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (589 of 589 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Davi Silveira
4f8f43cab1 Translated using Weblate (Portuguese)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Davi Silveira <davilego10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Diegofmar
4cbff308ce Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Diegofmar <diego2771@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Макар Разин
d786ab7deb Translated using Weblate (Polish)
Currently translated at 83.7% (491 of 586 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Mirkó Attila
c823d402ff Translated using Weblate (Hungarian)
Currently translated at 11.2% (66 of 586 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Hungarian)

Co-authored-by: Mirkó Attila <kisatti007hun@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hu/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hu/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-10 14:43:16 +02:00
gekka
12e68db41f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (586 of 586 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Madaraki
96717321d2 Translated using Weblate (Ukrainian)
Currently translated at 99.8% (585 of 586 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Oğuz Ersen
044b5590ef Translated using Weblate (Turkish)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Eduardo Malaspina
00112ebb44 Translated using Weblate (Spanish)
Currently translated at 100.0% (586 of 586 strings)

Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-02-10 14:43:16 +02:00
Koitharu
00dde80fdf Update parsers 2024-02-10 14:42:12 +02:00
Koitharu
f1dfc4ebd6 Fix strings 2024-02-10 14:33:57 +02:00
Koitharu
5426edd83a Option to disable reading time estimator 2024-02-10 14:10:38 +02:00
Koitharu
2a500eb2cb Improve background works notifications 2024-02-10 13:54:36 +02:00
Koitharu
2310ed06c1 Translated using Weblate (Russian)
Currently translated at 99.8% (585 of 586 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
gallegonovato
68ed7a09d6 Translated using Weblate (Spanish)
Currently translated at 100.0% (584 of 584 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Scrambled777
6cdb56e740 Translated using Weblate (Hindi)
Currently translated at 22.9% (134 of 583 strings)

Translated using Weblate (Hindi)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-07 14:01:25 +02:00
大王叫我来巡山
35fb78c924 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Davi Silveira
8c3b5d7f53 Translated using Weblate (Portuguese)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (582 of 583 strings)

Co-authored-by: Davi Silveira <davilego10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-07 14:01:25 +02:00
Макар Разин
af6592a8df Translated using Weblate (Belarusian)
Currently translated at 99.8% (582 of 583 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Deivinni Silva
9efb82d887 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (583 of 583 strings)

Translated using Weblate (English)

Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
gekka
f8722ddc73 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (583 of 583 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Nayuki
656ac97153 Translated using Weblate (Thai)
Currently translated at 69.8% (407 of 583 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
LL Magical
4fc23f8f54 Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.8% (582 of 583 strings)

Translated using Weblate (Portuguese)

Currently translated at 99.8% (582 of 583 strings)

Co-authored-by: LL Magical <lolayami2004@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Joshua “Josh”
e1f325993f Translated using Weblate (German)
Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: Joshua “Josh” <22joshua.mueller22@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2024-02-07 14:01:25 +02:00
Koitharu
4c3938a1fd Reader fullscreen option 2024-02-07 13:53:45 +02:00
Koitharu
530dfa8cde Fix workers scheduling 2024-02-07 13:19:51 +02:00
Koitharu
58d1c3de26 Default webtoon zoom out option 2024-02-07 12:55:51 +02:00
Koitharu
ba2ed6a2ef Webtoon reader improvements 2024-02-07 10:25:23 +02:00
Koitharu
2d909854fb Update parsers 2024-02-07 09:40:30 +02:00
Koitharu
cba694bedd Merge branch 'master' into devel 2024-02-07 09:08:09 +02:00
Koitharu
e5cf1be91a Fix build signature validation 2024-02-05 12:54:14 +02:00
Koitharu
72a1dd8227 Increase versionCode 2024-02-03 16:58:24 +02:00
Koitharu
8558b00dca Fix chapter number for saved chapters 2024-02-03 16:53:26 +02:00
Koitharu
8e9175d5f0 Update parsers 2024-02-03 16:52:15 +02:00
Koitharu
eae40d9b90 Double reader fixes 2024-02-03 16:52:15 +02:00
Koitharu
2d61209696 Double page reader for reversed mode 2024-02-03 16:52:15 +02:00
Koitharu
d24754f2a0 Handle MAL errors in html 2024-02-03 16:52:15 +02:00
Koitharu
54ef02ad88 Fix downloading 2024-02-03 16:52:14 +02:00
gekka
e2a82920b6 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-02-03 16:37:45 +02:00
ALi.w
d494030d50 Translated using Weblate (Persian)
Currently translated at 44.5% (260 of 583 strings)

Co-authored-by: ALi.w <aminnimaj@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
2024-02-03 16:37:45 +02:00
Anon
73369f9a6d Translated using Weblate (Serbian)
Currently translated at 99.6% (581 of 583 strings)

Translated using Weblate (English)

Currently translated at 99.8% (582 of 583 strings)

Translated using Weblate (Serbian)

Currently translated at 99.3% (579 of 583 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/sr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-02-03 16:37:45 +02:00
Madaraki
cc1da6e8da Translated using Weblate (Ukrainian)
Currently translated at 100.0% (583 of 583 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-02-03 16:37:45 +02:00
Oğuz Ersen
668a5bd040 Translated using Weblate (Turkish)
Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2024-02-03 16:37:45 +02:00
Koitharu
8efa8bc0d2 Handle MAL errors in html 2024-02-01 10:31:23 +02:00
Koitharu
6e6c70a770 Fix downloading 2024-02-01 10:12:20 +02:00
Sup Kelelawar
413605b520 Translated using Weblate (Indonesian)
Currently translated at 100.0% (583 of 583 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (583 of 583 strings)

Co-authored-by: Sup Kelelawar <apkfile007@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Anna
bdf23a0d62 Translated using Weblate (German)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Anna <scheffler.anna@gmx.net>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/de/
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Eji-san
4c5d26d4b4 Translated using Weblate (Indonesian)
Currently translated at 100.0% (581 of 581 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Eji-san <ejierubani@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Wrld-Lain
3b7ad7f28d Translated using Weblate (Spanish)
Currently translated at 99.8% (578 of 579 strings)

Co-authored-by: Wrld-Lain <anzonx@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
gekka
331af45a29 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (583 of 583 strings)

Added translation using Weblate (Chinese (Literary))

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (581 of 581 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (579 of 579 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (579 of 579 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Макар Разин
d349bd30c9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (579 of 579 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (579 of 579 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
ZerOriSama
3349e3abc5 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (579 of 579 strings)

Co-authored-by: ZerOriSama <godarms2010@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
gekka
4aa31ead67 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (579 of 579 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Clxff H3r4ld0
113da3b6c1 Translated using Weblate (Indonesian)
Currently translated at 100.0% (566 of 566 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Clxff H3r4ld0 <123844876+clxf12@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
何意挽秋風
8b027e2f45 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (565 of 565 strings)

Added translation using Weblate (Chinese (Traditional))

Co-authored-by: 何意挽秋風 <9120518@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
FateXBlood
9c462b1a3a Translated using Weblate (Nepali)
Currently translated at 41.5% (235 of 565 strings)

Translated using Weblate (Nepali)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: FateXBlood <zecrofelix@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ne/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ne/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Madaraki
a5bc8c1e9e Translated using Weblate (Ukrainian)
Currently translated at 98.9% (573 of 579 strings)

Translated using Weblate (Russian)

Currently translated at 99.6% (577 of 579 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (566 of 566 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (566 of 566 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (565 of 565 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (565 of 565 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (564 of 564 strings)

Co-authored-by: Madaraki <115705267+Madaraki-chan@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Deivinni Silva
ebb77c68cc Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (564 of 564 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Madaraki
74ddf86ebe Translated using Weblate (Russian)
Currently translated at 100.0% (563 of 563 strings)

Co-authored-by: Madaraki <Madaraki@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
nobSEIFO
12d2fdaf3e Translated using Weblate (Arabic)
Currently translated at 59.6% (335 of 562 strings)

Co-authored-by: nobSEIFO <BEZAI2002@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
D
8cfc97c795 Translated using Weblate (Italian)
Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (562 of 562 strings)

Co-authored-by: D <dogger56@hotmail.it>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Сергій
3855ca802e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (563 of 563 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (560 of 560 strings)

Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/uk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Макар Разин
9db427275f Translated using Weblate (Belarusian)
Currently translated at 100.0% (579 of 579 strings)

Translated using Weblate (Ukrainian)

Currently translated at 99.2% (556 of 560 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (560 of 560 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
yearn
3a38644089 Added translation using Weblate (Chinese (Literary))
Co-authored-by: yearn <13676164923@163.com>
2024-01-31 16:37:37 +02:00
Anibal Dams
60a34ec092 Translated using Weblate (Spanish)
Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Anibal Dams <elnini120@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/es/
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
shehuaizong
acd79f12e3 Added translation using Weblate (Franco-Provençal)
Co-authored-by: shehuaizong <1511985899@qq.com>
2024-01-31 16:37:37 +02:00
Infy's Tagalog Translations
461d7ed578 Translated using Weblate (Filipino)
Currently translated at 100.0% (558 of 558 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Alex Georgiou
5374ac390c Translated using Weblate (Greek)
Currently translated at 100.0% (557 of 557 strings)

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
plum7x
913a67a652 Translated using Weblate (Chinese (Traditional))
Currently translated at 94.0% (524 of 557 strings)

Co-authored-by: plum7x <plumgift@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
大王叫我来巡山
e7a920e43a Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (565 of 565 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (563 of 563 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (562 of 562 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (560 of 560 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (558 of 558 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (557 of 557 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
scaledzdn
9668b3ef5f Translated using Weblate (Indonesian)
Currently translated at 99.6% (555 of 557 strings)

Co-authored-by: scaledzdn <zaidanmovic0512@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Ridhoardhiansyah7
9581f937de Translated using Weblate (Indonesian)
Currently translated at 99.6% (555 of 557 strings)

Co-authored-by: Ridhoardhiansyah7 <Zxx97607@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Platiplus
44ef6f6dbf Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.4% (554 of 557 strings)

Co-authored-by: Platiplus <quazarweb@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-01-31 16:37:37 +02:00
Oğuz Ersen
af11697133 Translated using Weblate (Turkish)
Currently translated at 100.0% (581 of 581 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (579 of 579 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (566 of 566 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (565 of 565 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (563 of 563 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (562 of 562 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (557 of 557 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/tr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
gallegonovato
09ff356790 Translated using Weblate (Spanish)
Currently translated at 100.0% (566 of 566 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (565 of 565 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (563 of 563 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (560 of 560 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (558 of 558 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (557 of 557 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
ALi.w
92ea50d6b6 Translated using Weblate (Persian)
Currently translated at 41.9% (235 of 560 strings)

Translated using Weblate (Persian)

Currently translated at 41.9% (234 of 558 strings)

Translated using Weblate (Persian)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Persian)

Currently translated at 40.0% (223 of 557 strings)

Added translation using Weblate (Persian)

Co-authored-by: ALi.w <aminnimaj@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fa/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-31 16:37:37 +02:00
Koitharu
077107e9a7 Merge branch 'master' into devel 2024-01-31 16:30:30 +02:00
Koitharu
ae57561591 Update parsers 2024-01-31 16:16:04 +02:00
Zakhar Timoshenko
2379efc191 TLS 1.3 support for Android < 10
(cherry picked from commit 889b799d8d)
2024-01-31 15:59:04 +02:00
Koitharu
edca0e5334 Partially migrate to new collections 2024-01-31 15:55:33 +02:00
Koitharu
a4e2675d61 Update dependencies 2024-01-31 15:23:52 +02:00
Koitharu
892f95a7a6 Fix scrobbling chapter index 2024-01-31 13:56:48 +02:00
Koitharu
95aaa967a8 Scrobbling using Kitsu #360 2024-01-31 13:29:18 +02:00
Koitharu
5687ca6e96 Kitsu auth implementation 2024-01-30 15:41:03 +02:00
Koitharu
d0ee185d2e Merge branch 'feature/kitsu' of github.com:KotatsuApp/Kotatsu into devel 2024-01-30 13:59:34 +02:00
Koitharu
21a3ac0902 Use crop parameter for wsrv.nl #721 2024-01-30 13:33:59 +02:00
Koitharu
1382ab7933 Improve reader actions editor 2024-01-30 09:43:52 +02:00
Koitharu
aabdd281f3 Fix imports 2024-01-30 09:14:25 +02:00
Koitharu
131a0ffcaa Double reader integration 2024-01-29 18:57:18 +02:00
Koitharu
4194609929 Editor actions fixes 2024-01-29 11:54:21 +02:00
Zakhar Timoshenko
889b799d8d TLS 1.3 support for Android < 10 2024-01-28 17:58:38 +03:00
Koitharu
6f7f3dc5e2 Configurable reader tap actions 2024-01-27 18:06:51 +02:00
Koitharu
72187e7da0 Double reader integration 2024-01-27 12:25:39 +02:00
Koitharu
f881cc439a Draft double reader implementation 2024-01-27 12:25:34 +02:00
Zakhar Timoshenko
ccdebf6789 Initial support of long press in reader 2024-01-27 13:19:22 +03:00
Koitharu
4252ebd24d Bump versionCode 2024-01-24 12:40:18 +02:00
Koitharu
4db61d3c04 Merge branch 'master' into devel 2024-01-24 12:32:04 +02:00
Koitharu
cd0575a524 Update parsers 2024-01-24 12:28:50 +02:00
Koitharu
6eb2608f88 Disable autofill for protect password fields #702
(cherry picked from commit e7c9d1943d)
2024-01-24 12:16:18 +02:00
Koitharu
39e21ff93c Last read order in favorites #705
(cherry picked from commit b1240e7efa)
2024-01-24 12:16:12 +02:00
Koitharu
5ec2eab6b8 Fix webtoon scroll dispatching
(cherry picked from commit a0a72b1192)
2024-01-24 12:15:41 +02:00
Koitharu
850f6c2f3e Fix pages numbers
(cherry picked from commit 83cb35fe6e)
2024-01-24 12:09:11 +02:00
Koitharu
ec53eb9c70 Incognito mode indicator in reader
(cherry picked from commit db1ddf539c)
2024-01-24 12:08:52 +02:00
Koitharu
cdd76f723f Fix favorites counters
(cherry picked from commit d56fc674ab)
2024-01-24 12:08:17 +02:00
Koitharu
e7c9d1943d Disable autofill for protect password fields #702 2024-01-24 11:56:04 +02:00
Koitharu
b1240e7efa Last read order in favorites #705 2024-01-24 11:53:53 +02:00
Koitharu
a0a72b1192 Fix webtoon scroll dispatching 2024-01-24 11:36:48 +02:00
Koitharu
5d9a59d577 Update parsers and migrate to float chapter numbers 2024-01-22 17:11:00 +02:00
Koitharu
83cb35fe6e Fix pages numbers 2024-01-22 13:13:14 +02:00
Koitharu
0fff53ae47 Readers refactor 2024-01-22 13:00:56 +02:00
Koitharu
a95017a5f0 Update reader mode icons 2024-01-22 12:02:25 +02:00
Koitharu
9251823d9a Vertical reader mode 2024-01-21 11:37:22 +02:00
Koitharu
ce8f87272b Refactor ReadActivity menus 2024-01-21 11:02:53 +02:00
Koitharu
db1ddf539c Incognito mode indicator in reader 2024-01-21 10:10:20 +02:00
Koitharu
d56fc674ab Fix favorites counters 2024-01-20 15:54:15 +02:00
Koitharu
a37e8825b0 All favorites item change payload 2024-01-20 09:31:17 +02:00
Isira Seneviratne
c9fcc0f0f8 Improve signature check 2024-01-20 09:23:33 +02:00
Koitharu
2450544454 Update parsers
(cherry picked from commit da2ad40adf)
2024-01-20 09:22:28 +02:00
Koitharu
f6a510653e Fix import dialog injection
(cherry picked from commit af5716a8ce)
2024-01-20 09:21:43 +02:00
Koitharu
5990da587c Update supported links domains
(cherry picked from commit a98202e15e)
2024-01-20 09:21:40 +02:00
Koitharu
91e3d2f5db Skip unsupported sources in global search
(cherry picked from commit d6887e2d75)
2024-01-20 09:21:35 +02:00
Koitharu
971c683746 Show all favorites on categories screen
(cherry picked from commit 2a5300a634)
2024-01-20 09:21:27 +02:00
Koitharu
15e9aaab26 Fix pages list scrolling
(cherry picked from commit a1120ea709)
2024-01-20 09:21:18 +02:00
Koitharu
da2ad40adf Update parsers 2024-01-20 09:19:29 +02:00
Koitharu
af5716a8ce Fix import dialog injection 2024-01-20 09:16:06 +02:00
Koitharu
a98202e15e Update supported links domains 2024-01-20 09:08:26 +02:00
Koitharu
d6887e2d75 Skip unsupported sources in global search 2024-01-20 09:07:27 +02:00
Koitharu
ba6afd44dd Fix reading time format 2024-01-19 16:37:25 +02:00
Koitharu
0b55c4d037 Add volume headers to chapters list 2024-01-19 16:36:36 +02:00
Koitharu
2a5300a634 Show all favorites on categories screen 2024-01-18 16:16:12 +02:00
Koitharu
59bfa929fd Current branch indicator 2024-01-18 14:54:30 +02:00
Koitharu
c5d88f8700 Refactor reading time estimator 2024-01-18 13:38:25 +02:00
Koitharu
a1120ea709 Fix pages list scrolling 2024-01-18 12:19:34 +02:00
Koitharu
796af6b811 Refactor chapters description 2024-01-18 10:58:11 +02:00
Zakhar Timoshenko
eafd878413 Approximate reading time preview 2024-01-17 22:22:37 +03:00
Zakhar Timoshenko
9baf2bfcd9 Update Material lib 2024-01-17 22:20:13 +03:00
Zakhar Timoshenko
0b4dd5beef Enhance chapter items 2024-01-17 16:09:55 +03:00
Koitharu
12047a85c7 Update parsers 2024-01-17 13:33:55 +02:00
Koitharu
48808f8a7d Improve scrobbling selection 2024-01-17 11:41:03 +02:00
Koitharu
18e573b6b8 Merge remote-tracking branch 'weblate/devel' into devel 2024-01-16 15:37:40 +02:00
Koitharu
c80dc08d6c Fix details activity tabs 2024-01-16 15:30:56 +02:00
Koitharu
61b5b8aa73 Fixes 2024-01-16 15:30:55 +02:00
Koitharu
5e79809326 Support reverse alphabetic order in history and favorties 2024-01-16 15:30:55 +02:00
Koitharu
dcbd7c2117 Fix history sorting 2024-01-16 15:30:55 +02:00
Koitharu
1e134b109a Fix favorites sort order 2024-01-16 15:30:55 +02:00
Koitharu
7f9b6a67af Reduce workers retry frequency 2024-01-16 15:30:55 +02:00
Koitharu
79448bb01d Workaround for ViewPager2 bug 2024-01-16 15:30:54 +02:00
Koitharu
b94f9e4b01 Fix primary navigation restore state 2024-01-16 15:30:54 +02:00
Koitharu
ae8b48d733 Migrate favorites to ViewPager2 2024-01-16 15:30:53 +02:00
Koitharu
313013dccd Mark as completed in history 2024-01-16 15:24:58 +02:00
NA0341
c36d23ec06 Update CONTRIBUTING.md
Improved readability & fixed spelling.
2024-01-16 15:24:41 +02:00
Paramjot singj
ebe71476d1 Translated using Weblate (Punjabi)
Currently translated at 4.1% (23 of 556 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pa/
2024-01-15 19:06:18 +01:00
Paramjot singj
ca2ae9bc83 Translated using Weblate (Hindi)
Currently translated at 22.8% (127 of 556 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
2024-01-15 19:06:18 +01:00
Alex Georgiou
f2898aba85 Translated using Weblate (Greek)
Currently translated at 99.8% (555 of 556 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
2024-01-15 19:06:18 +01:00
Dimas oppo
1b73f19ae1 Translated using Weblate (Indonesian)
Currently translated at 96.2% (535 of 556 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
2024-01-15 19:06:18 +01:00
Paramjot singj
4b4aea0410 Translated using Weblate (Italian)
Currently translated at 100.0% (7 of 7 strings)

Translation: Kotatsu/plurals
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
2024-01-15 19:06:17 +01:00
Çınar
b9f8e3978a Translated using Weblate (Turkish)
Currently translated at 100.0% (556 of 556 strings)

Translation: Kotatsu/Strings
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
2024-01-15 19:06:17 +01:00
Weblate (bot)
89eed10508 Translations update from Hosted Weblate (#652)
* Translated using Weblate (Greek)

Currently translated at 99.8% (555 of 556 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Translated using Weblate (Greek)

Currently translated at 45.8% (255 of 556 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals

* Translated using Weblate (German)

Currently translated at 99.6% (554 of 556 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Bela K <na0341-dev@posteo.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translation: Kotatsu/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (556 of 556 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (French)

Currently translated at 96.2% (535 of 556 strings)

Co-authored-by: Quentin VERDIER <quent1.verdier@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings

* Translated using Weblate (Serbian)

Currently translated at 99.1% (551 of 556 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: Judith Smith <i@razuritta.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings

* Translated using Weblate (Malay)

Currently translated at 56.1% (312 of 556 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (7 of 7 strings)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Co-authored-by: Megat Al Zhahir Daniel <megatalzhahir@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings

* Translated using Weblate (Arabic)

Currently translated at 51.0% (284 of 556 strings)

Co-authored-by: Khaled <ashen.cv@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings

* Translated using Weblate (Greek)

Currently translated at 99.8% (555 of 556 strings)

Co-authored-by: Theodore Visvikis <theodoros.visvikis@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translation: Kotatsu/Strings

* Translated using Weblate (Italian)

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Paramjot singj <paramjotsingh2852@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings

* Translated using Weblate (Italian)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Paper Jack <paperjack@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translation: Kotatsu/plurals

* Added translation using Weblate (Punjabi (Pakistan))

Added translation using Weblate (Punjabi)

Translated using Weblate (Italian)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Paramjot singj <paramjotsingh2852@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translation: Kotatsu/plurals

* Translated using Weblate (Greek)

Currently translated at 99.8% (555 of 556 strings)

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translation: Kotatsu/Strings

---------

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Co-authored-by: Bela K <na0341-dev@posteo.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Quentin VERDIER <quent1.verdier@gmail.com>
Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Co-authored-by: Anon <anonymousprivate76@gmail.com>
Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Co-authored-by: Judith Smith <i@razuritta.me>
Co-authored-by: Megat Al Zhahir Daniel <megatalzhahir@gmail.com>
Co-authored-by: Khaled <ashen.cv@gmail.com>
Co-authored-by: Theodore Visvikis <theodoros.visvikis@gmail.com>
Co-authored-by: Paramjot singj <paramjotsingh2852@gmail.com>
Co-authored-by: Paper Jack <paperjack@tutanota.com>
2024-01-15 13:47:21 +03:00
Alex Georgiou
799c0910ea Translated using Weblate (Greek)
Currently translated at 99.8% (555 of 556 strings)

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translation: Kotatsu/Strings
2024-01-14 18:18:56 +01:00
Paramjot singj
ed6802344a Added translation using Weblate (Punjabi (Pakistan))
Added translation using Weblate (Punjabi)

Translated using Weblate (Italian)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Paramjot singj <paramjotsingh2852@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translation: Kotatsu/plurals
2024-01-14 18:18:56 +01:00
Paper Jack
2d94688742 Translated using Weblate (Italian)
Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Paper Jack <paperjack@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translation: Kotatsu/plurals
2024-01-14 18:18:56 +01:00
Paramjot singj
d0b44050f5 Translated using Weblate (Italian)
Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Paramjot singj <paramjotsingh2852@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2024-01-14 18:18:56 +01:00
Theodore Visvikis
486ae12d41 Translated using Weblate (Greek)
Currently translated at 99.8% (555 of 556 strings)

Co-authored-by: Theodore Visvikis <theodoros.visvikis@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translation: Kotatsu/Strings
2024-01-14 18:18:56 +01:00
Khaled
cb36f085d7 Translated using Weblate (Arabic)
Currently translated at 51.0% (284 of 556 strings)

Co-authored-by: Khaled <ashen.cv@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-01-14 18:18:56 +01:00
大王叫我来巡山
5af42e5b6b Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-14 18:18:56 +01:00
Megat Al Zhahir Daniel
160fa2c001 Translated using Weblate (Malay)
Currently translated at 56.1% (312 of 556 strings)

Translated using Weblate (Malay)

Currently translated at 100.0% (7 of 7 strings)

Added translation using Weblate (Malay)

Added translation using Weblate (Malay)

Co-authored-by: Megat Al Zhahir Daniel <megatalzhahir@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ms/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-14 18:18:55 +01:00
Judith Smith
f9ba87b8cf Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: Judith Smith <i@razuritta.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
大王叫我来巡山
ee3cf08545 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
Anon
e0497b357b Translated using Weblate (Serbian)
Currently translated at 99.1% (551 of 556 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
Deivinni Silva
6de08cd4fe Translated using Weblate (Portuguese (Brazil))
Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
Quentin VERDIER
4760f1ea35 Translated using Weblate (French)
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (French)

Currently translated at 96.2% (535 of 556 strings)

Co-authored-by: Quentin VERDIER <quent1.verdier@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-14 18:18:55 +01:00
gallegonovato
c64c4643bf Translated using Weblate (Spanish)
Currently translated at 100.0% (556 of 556 strings)

Translated using Weblate (Spanish)

Currently translated at 99.6% (554 of 556 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
Bela K
7bfcdb387c Translated using Weblate (German)
Currently translated at 99.6% (554 of 556 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Bela K <na0341-dev@posteo.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translation: Kotatsu/Strings
2024-01-14 18:18:55 +01:00
Alex Georgiou
503fcf65fb Translated using Weblate (Greek)
Currently translated at 99.8% (555 of 556 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Translated using Weblate (Greek)

Currently translated at 45.8% (255 of 556 strings)

Translated using Weblate (Greek)

Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (English)

Currently translated at 100.0% (556 of 556 strings)

Co-authored-by: Alex Georgiou <alexandrosgeorgiou35@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/el/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-14 18:18:55 +01:00
Koitharu
54fb79dc98 Fix crash on start app update download 2024-01-12 18:18:04 +02:00
Koitharu
ea4c048029 Update parsers 2024-01-12 18:16:32 +02:00
Koitharu
badc826cd3 Hide search on unsupported sources 2024-01-12 18:15:00 +02:00
CakesTwix
f5ece8124e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: CakesTwix <cakestwix1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
Zero O
accdc41d6c Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: Zero O <godarms2010@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
PacoteB2
819730984e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: PacoteB2 <gop88662@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
Anton Prevrhal
01c404f9e5 Translated using Weblate (German)
Currently translated at 88.6% (491 of 554 strings)

Co-authored-by: Anton Prevrhal <anton.prevrhal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
Anon
1fad686733 Translated using Weblate (Serbian)
Currently translated at 99.4% (551 of 554 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
Сергій
396be6008d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: Сергій <sergiy.goncharuk.1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
ssantos
42f7846167 Translated using Weblate (Portuguese)
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-11 19:26:30 +02:00
Макар Разин
dca56a43ee Translated using Weblate (Russian)
Currently translated at 100.0% (554 of 554 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (554 of 554 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-01-11 19:26:30 +02:00
Koitharu
cc91e56e1b Update parsers 2024-01-11 19:08:15 +02:00
Koitharu
627cf73d72 Action to mark manga as completed 2024-01-11 18:53:45 +02:00
Koitharu
514870f71c Filter exceptions reporting via ACRA 2024-01-07 15:51:16 +02:00
Koitharu
adffa800e8 Update parsers 2024-01-07 12:08:04 +02:00
Koitharu
3acca44b5e Update parsers 2024-01-06 15:33:49 +02:00
Koitharu
c7da4feb8f Fix details bottom sheet menu 2024-01-06 15:18:49 +02:00
Koitharu
baee9bee0e Update shikimori domain 2024-01-06 14:27:07 +02:00
Koitharu
ec41d36508 Fix converting bitmaps in local manga 2024-01-06 14:13:56 +02:00
Koitharu
8b63d227a7 Translated using Weblate (Russian)
Currently translated at 99.2% (550 of 554 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
dont wana say
c9b48c8207 Translated using Weblate (Estonian)
Currently translated at 79.7% (442 of 554 strings)

Translated using Weblate (Estonian)

Currently translated at 85.7% (6 of 7 strings)

Co-authored-by: dont wana say <273ex2vl6@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/et/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-01-05 16:48:58 +02:00
Haithem Dhiaeddine
6d7ce5205e Translated using Weblate (Arabic)
Currently translated at 51.4% (285 of 554 strings)

Co-authored-by: Haithem Dhiaeddine <haithemdjabi@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
Ridhoardhiansyah7
5a02d534c9 Translated using Weblate (Indonesian)
Currently translated at 97.6% (540 of 553 strings)

Co-authored-by: Ridhoardhiansyah7 <Zxx97607@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
gallegonovato
6128e5b699 Translated using Weblate (Spanish)
Currently translated at 100.0% (554 of 554 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (553 of 553 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
InfinityDouki56
717a0ad4fb Translated using Weblate (Filipino)
Currently translated at 99.8% (553 of 554 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (548 of 548 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
Anon
dee94ac0c4 Translated using Weblate (Serbian)
Currently translated at 100.0% (548 of 548 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-01-05 16:48:58 +02:00
Koitharu
9eec9a9957 Fix favorites backup #621 2024-01-05 16:40:48 +02:00
Koitharu
a4966b4661 Update parsers 2024-01-05 16:29:22 +02:00
Koitharu
58e570601d PageLoader improvements 2024-01-05 10:46:32 +02:00
Koitharu
7247cba855 Improve pages preview on details screen 2024-01-04 16:59:39 +02:00
Koitharu
d6012f9ddd Reset filter menu action 2024-01-04 11:05:09 +02:00
Koitharu
2eedd0b4a8 Fix filter chips 2024-01-04 10:37:06 +02:00
Koitharu
5e6da9bb1c Pages thumbnails on details screen 2024-01-03 19:47:35 +02:00
Koitharu
2f2a5b868d Excluded tags and content rating in filter 2024-01-02 20:18:44 +02:00
Isira Seneviratne
3f2e32dcc2 Revert to Java 8 2023-12-31 11:15:42 +02:00
Isira Seneviratne
004109a6bc Switch to java.time 2023-12-31 11:15:42 +02:00
Isira Seneviratne
6159ee36c4 Use TypedValueCompat 2023-12-31 11:15:29 +02:00
Koitharu
3b7d83dd6f Bump version 2023-12-31 10:59:30 +02:00
Koitharu
877a018ced Fix crashes 2023-12-31 10:28:46 +02:00
Koitharu
2e80b330e9 Fix tracker duplicates 2023-12-31 10:17:43 +02:00
Koitharu
42ca38e693 Update dependencies 2023-12-31 10:06:48 +02:00
Feroli
d2fc3354af Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Feroli <feroli@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt_BR/
Translation: Kotatsu/plurals
2023-12-31 10:03:25 +02:00
Koitharu
2a870e6167 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (548 of 548 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (548 of 548 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-12-31 10:03:25 +02:00
gallegonovato
393a9c2791 Translated using Weblate (Spanish)
Currently translated at 100.0% (548 of 548 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-12-31 10:03:25 +02:00
dont wana say
4c69839076 Translated using Weblate (Estonian)
Currently translated at 75.5% (414 of 548 strings)

Added translation using Weblate (Estonian)

Added translation using Weblate (Estonian)

Co-authored-by: dont wana say <273ex2vl6@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/et/
Translation: Kotatsu/Strings
2023-12-31 10:03:25 +02:00
Koitharu
e37455e790 Update parsers and add support for Upcoming state 2023-12-28 17:48:00 +02:00
Paper Jack
36259ba901 Translated using Weblate (Italian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Paper Jack <paperjack@tutanota.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2023-12-28 16:50:47 +02:00
Nicolò Bertazzo
5b041b9a49 Translated using Weblate (Italian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Nicolò Bertazzo <n.bertazzo@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2023-12-28 16:50:47 +02:00
Koitharu
1734e888d6 Move new sources tip to catalog 2023-12-26 20:24:14 +02:00
Koitharu
9108646cea Update parsers 2023-12-25 19:52:37 +02:00
Макар Разин
c6d1cf2f72 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (546 of 546 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (546 of 546 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
Anon
a317236cb0 Translated using Weblate (Serbian)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
Deivinni Silva
3703c07a98 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-12-25 13:26:51 +02:00
Koitharu
b7b32dd447 Update parsers 2023-12-25 13:18:28 +02:00
Koitharu
c103386dc5 Fix crash on file size compution 2023-12-25 10:01:43 +02:00
Koitharu
a9d6ee4a95 Update dependencies 2023-12-25 09:57:01 +02:00
Koitharu
71f2c91e5a Improve sources catalog 2023-12-23 08:44:09 +02:00
Koitharu
b878f358ff Improve explore screen 2023-12-23 07:26:31 +02:00
Deivinni Silva
4c9989da78 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-12-20 13:27:57 +02:00
ssantos
8e8424022a Translated using Weblate (Portuguese)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: ssantos <ssantos@web.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-12-20 13:27:57 +02:00
Koitharu
86504b8bde Update parsers 2023-12-20 13:26:33 +02:00
Zakhar Timoshenko
f082fa084f Recreate splash screen 2023-12-19 21:36:23 +03:00
Zakhar Timoshenko
040fe258e9 Fix ActionMode popup coloring 2023-12-19 20:20:20 +03:00
Koitharu
1076009572 Temporary fix checking for new chapters on large collections #584 2023-12-19 17:37:23 +02:00
Koitharu
40dde71a1d Update parsers 2023-12-19 17:37:23 +02:00
Himawariin
9503aabf78 Translated using Weblate (Filipino)
Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Himawariin <milkytrackz@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-12-19 17:32:43 +02:00
Deivinni Silva
4ee16cfa2f Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Deivinni Silva <deivinnimds3656@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-12-19 17:32:43 +02:00
Koitharu
f1c9eacaf0 Fix color correction apply buttons #597 2023-12-18 18:38:48 +02:00
Koitharu
920e16be10 Merge branch 'master' into devel 2023-12-16 08:46:02 +02:00
Koitharu
4e0e5be726 Update parsers
(cherry picked from commit 451a155e08)
2023-12-16 08:13:24 +02:00
Weblate (bot)
f18eca52af Translations update from Hosted Weblate (#594)
* Translated using Weblate (Thai)

Currently translated at 71.1% (387 of 544 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings

* Translated using Weblate (Turkish)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings

* Translated using Weblate (Russian)

Currently translated at 99.8% (545 of 546 strings)

Co-authored-by: Hotarun <ihotarun@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings

---------

Co-authored-by: Nayuki <me@nayuki.cyou>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Hotarun <ihotarun@proton.me>
(cherry picked from commit 0612a7ad2c)
2023-12-16 08:13:17 +02:00
Koitharu
451a155e08 Update parsers 2023-12-16 08:12:42 +02:00
Weblate (bot)
0612a7ad2c Translations update from Hosted Weblate (#594)
* Translated using Weblate (Thai)

Currently translated at 71.1% (387 of 544 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings

* Translated using Weblate (Turkish)

Currently translated at 100.0% (546 of 546 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings

* Translated using Weblate (Russian)

Currently translated at 99.8% (545 of 546 strings)

Co-authored-by: Hotarun <ihotarun@proton.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings

---------

Co-authored-by: Nayuki <me@nayuki.cyou>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Co-authored-by: Hotarun <ihotarun@proton.me>
2023-12-15 23:26:31 +03:00
Zakhar Timoshenko
9c34f25eda Fix navBar color in details screen 2023-12-15 23:02:21 +03:00
Zakhar Timoshenko
42360c678f Revert BottomNav and CTBLayout styles 2023-12-15 22:25:24 +03:00
HotarunIchijou
04a3d02aa9 Minority UI color changes (#593)
* Transparent navigation bar

* bump com.android.material to 1.11.0 and androidx.activity to 1.8.2

* bunp gradle to 8.2.0

* color changes for dynamic theme

* removed unused things
2023-12-15 22:14:16 +03:00
Koitharu
808fd13ad0 Fix saved chapters mapping 2023-12-14 15:16:15 +02:00
Koitharu
88c8dc4761 Fix crashes 2023-12-14 14:50:56 +02:00
Koitharu
a7eba67a97 Webtoon reader fixes 2023-12-14 14:36:03 +02:00
Koitharu
c27586231a Select which data will be restored from backup 2023-12-13 14:37:05 +02:00
Koitharu
db3db4637c Support manga sources in backups 2023-12-13 12:16:06 +02:00
Koitharu
bb2294f248 UI improvements 2023-12-13 11:33:55 +02:00
Koitharu
afd56c02e6 Update parsers 2023-12-12 19:13:29 +02:00
Koitharu
dcf1ffc976 Translated using Weblate (Russian)
Currently translated at 99.8% (543 of 544 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
Nayuki
91b7028b1a Translated using Weblate (Thai)
Currently translated at 70.4% (382 of 542 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
Anon
734c217c03 Translated using Weblate (Serbian)
Currently translated at 100.0% (542 of 542 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
SENPAi-03
de18c6eb71 Translated using Weblate (Arabic)
Currently translated at 34.3% (186 of 542 strings)

Co-authored-by: SENPAi-03 <naemamro1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
Oğuz Ersen
034be6b44e Translated using Weblate (Turkish)
Currently translated at 100.0% (544 of 544 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (542 of 542 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
gallegonovato
995ff5a764 Translated using Weblate (Spanish)
Currently translated at 100.0% (542 of 542 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
Макар Разин
102bec04d6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (542 of 542 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (542 of 542 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (542 of 542 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-12-12 19:13:17 +02:00
Zakhar Timoshenko
d05d807614 Slightly improve welcome screen 2023-12-12 20:05:59 +03:00
Koitharu
bffd75f4d9 Fix crashes 2023-12-12 18:55:22 +02:00
Koitharu
bdaf3da7e0 New welcome screen 2023-12-12 15:58:45 +02:00
Koitharu
353d856bf5 Fix chips style 2023-12-12 14:18:49 +02:00
Zakhar Timoshenko
fca9ba98cd Update parsers 2023-12-11 20:02:09 +03:00
Zakhar Timoshenko
5df76fd881 Fix crashes 2023-12-11 18:46:35 +03:00
Koitharu
54c646ceb0 Update parsers 2023-12-09 13:54:58 +02:00
Koitharu
3599f2f1b8 Improve onboarding 2023-12-09 13:46:39 +02:00
Koitharu
b2e53d4938 Fix some warnings and remove unused code 2023-12-09 13:31:43 +02:00
Koitharu
0d62408918 Update acra configuration 2023-12-09 11:25:14 +02:00
Koitharu
2ae046d4c5 Update acra configuration 2023-12-09 09:04:13 +02:00
Koitharu
66356dc094 Add doze disable preference for downloads 2023-12-08 12:18:18 +02:00
Koitharu
ae16110a80 Fix LocaleComparator 2023-12-08 08:47:56 +02:00
Zakhar Timoshenko
c3aff60a9c Spinner and EditText enhancements 2023-12-08 00:39:13 +03:00
gallegonovato
cfdca3434b Translated using Weblate (Spanish)
Currently translated at 100.0% (541 of 541 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (540 of 540 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (538 of 538 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (532 of 532 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-12-07 18:16:16 +02:00
Anon
b2c2693aba Translated using Weblate (Serbian)
Currently translated at 96.4% (519 of 538 strings)

Translated using Weblate (Serbian)

Currently translated at 97.5% (518 of 531 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/sr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-12-07 18:16:16 +02:00
Hasanur Rahman Biplob
5901c26ae0 Translated using Weblate (Bengali)
Currently translated at 30.6% (163 of 531 strings)

Co-authored-by: Hasanur Rahman Biplob <hrbiplob100@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
2023-12-07 18:16:16 +02:00
ECBaris
d864c73faf Translated using Weblate (Turkish)
Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: ECBaris <barisaklan@outlook.com.tr>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/tr/
Translation: Kotatsu/plurals
2023-12-07 18:16:16 +02:00
Feroli
30551a56b2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (531 of 531 strings)

Co-authored-by: Feroli <feroli@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-12-07 18:16:16 +02:00
La prière
23cb023a85 Translated using Weblate (Japanese)
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Japanese)

Currently translated at 90.7% (482 of 531 strings)

Co-authored-by: La prière <lapriere@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-12-07 18:16:16 +02:00
Макар Разин
10291d5b29 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (541 of 541 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (541 of 541 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (541 of 541 strings)

Translated using Weblate (Polish)

Currently translated at 90.3% (480 of 531 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (531 of 531 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-12-07 18:16:16 +02:00
InfinityDouki56
e05e09f846 Translated using Weblate (Filipino)
Currently translated at 88.1% (477 of 541 strings)

Translated using Weblate (Filipino)

Currently translated at 88.5% (470 of 531 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-12-07 18:16:16 +02:00
Koitharu
0ce7f7cf6b Missing local manga cleanup #587 2023-12-07 18:11:30 +02:00
Koitharu
4d9d15004c Reader fixes 2023-12-07 16:38:33 +02:00
Koitharu
1908ce3e46 New filter implementation 2023-12-06 17:31:57 +02:00
Koitharu
6c07abec56 New filter sheet draft implementation 2023-12-05 17:14:24 +02:00
Koitharu
64dc646fc5 Language filter support 2023-12-05 15:15:51 +02:00
Koitharu
357669d8b2 Fix list items reorder 2023-12-05 09:59:07 +02:00
Koitharu
21639ddcbc Fix directories manage screen 2023-12-04 16:40:57 +02:00
Koitharu
5183d5e882 Grayscale color filter 2023-12-04 16:21:02 +02:00
Koitharu
3008b7b89a Fix downloader skip action 2023-12-04 14:05:22 +02:00
Koitharu
53e00e4689 Global color filter initial implementation #562 2023-12-02 15:45:49 +02:00
Koitharu
963d7d8d42 Fix concurrent manga downloading 2023-12-02 15:43:47 +02:00
Koitharu
1a7b1e7bdc Ability to skip error in downloader 2023-12-02 15:03:15 +02:00
Koitharu
b1fa9d1d22 Fix mapping local chapters #577 2023-12-02 15:02:48 +02:00
Koitharu
91179ef901 Show chapters in downloads list 2023-12-02 14:18:39 +02:00
Koitharu
a7a9ee9d59 Downloader improvements 2023-12-02 09:11:33 +02:00
Koitharu
ff05f3f79d Upgrade WorkManager 2023-11-30 12:53:44 +02:00
Koitharu
c0062c83c8 Upgrade room 2023-11-30 11:52:17 +02:00
Koitharu
ef0cf4766a Fix webtoon mode 2023-11-29 18:07:53 +02:00
Koitharu
910069ec99 Update parsers 2023-11-29 17:25:13 +02:00
Koitharu
d56107bf1f Remove funding info 2023-11-29 09:53:23 +02:00
Koitharu
03426694c8 Bump version 2023-11-29 09:26:45 +02:00
Koitharu
385003bcc8 Fix filter chips #572 2023-11-28 16:11:30 +02:00
Koitharu
225aacff43 Temporary disable downsampling in webtoon mode 2023-11-28 15:57:18 +02:00
Макар Разин
208c0a494b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (531 of 531 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (531 of 531 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (531 of 531 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-28 15:56:16 +02:00
Bai
0045c7cf44 Translated using Weblate (Turkish)
Currently translated at 100.0% (531 of 531 strings)

Co-authored-by: Bai <batuhanakkurt000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-11-28 15:56:16 +02:00
J. Lavoie
eed7f89518 Translated using Weblate (French)
Currently translated at 99.8% (530 of 531 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2023-11-28 15:56:16 +02:00
gallegonovato
80c8b9eac0 Translated using Weblate (Spanish)
Currently translated at 100.0% (531 of 531 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-28 15:56:16 +02:00
ECBaris
53a680d13c Translated using Weblate (Turkish)
Currently translated at 98.6% (520 of 527 strings)

Co-authored-by: ECBaris <barisaklan@outlook.com.tr>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-11-28 15:56:16 +02:00
Koitharu
3e77df20a2 Improve manga memory cache usage 2023-11-26 09:07:56 +02:00
Zakhar Timoshenko
7c1c0a38fa Update parsers lib 2023-11-25 19:53:07 +03:00
Koitharu
012eefe4fe Fix NPE in ListConfigViewModel 2023-11-25 17:40:54 +02:00
Oliullah
cb0f0c70d0 Translated using Weblate (Bengali)
Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: Oliullah <shahin1465686@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/bn/
Translation: Kotatsu/plurals
2023-11-25 17:34:07 +02:00
Макар Разин
23111dfef9 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (525 of 525 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (525 of 525 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (525 of 525 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-25 17:34:07 +02:00
gallegonovato
d050c9ad0e Translated using Weblate (Spanish)
Currently translated at 100.0% (525 of 525 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (524 of 524 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-25 17:34:07 +02:00
Koitharu
efd952a91a Localize parsers errors 2023-11-25 17:33:05 +02:00
Koitharu
d3f23ea3a3 Add manga state to filter 2023-11-25 17:25:48 +02:00
Koitharu
acba312e8d Misc fixes 2023-11-25 09:18:08 +02:00
Koitharu
880dd6da27 Load local manga pages directly #552 2023-11-24 18:54:09 +02:00
Koitharu
0c839ce49a Fix locale selection in sources catalog #561 2023-11-24 17:10:58 +02:00
Isira Seneviratne
1afd2d3976 Use file walking APIs 2023-11-24 16:59:01 +02:00
Koitharu
f2d881f9bc Fix crash with locales sorting 2023-11-24 16:58:11 +02:00
Koitharu
c838e57f22 Downsample offscreen pages option 2023-11-24 16:53:44 +02:00
Koitharu
2075b1be19 Add lifecycle for BasePageHolder 2023-11-24 14:59:12 +02:00
Koitharu
cf33cb66c6 Fix release build 2023-11-23 13:25:12 +02:00
Макар Разин
8010c5079b Translated using Weblate (Ukrainian)
Currently translated at 100.0% (524 of 524 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (524 of 524 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (524 of 524 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-23 12:47:59 +02:00
Koitharu
a87a77083e Reader fixes 2023-11-23 12:42:43 +02:00
Koitharu
ca20422344 Support Paused manga state 2023-11-23 12:33:48 +02:00
Koitharu
c213b9d4b5 Update parsers 2023-11-23 12:26:38 +02:00
Koitharu
95fbe496cb Search through all sources in catalog 2023-11-22 16:11:23 +02:00
Koitharu
b9fd2e100d Fallback to local manga on network error 2023-11-22 15:38:39 +02:00
Koitharu
1242a88f8e Fix disabling webtoon scale 2023-11-22 15:10:26 +02:00
Koitharu
55851fb22f Avoid replacing online manga wthin local in database 2023-11-18 16:03:01 +02:00
Koitharu
7801456d17 Enable desugaring to fit Jsoup requirements #553 2023-11-18 15:12:18 +02:00
Koitharu
38a1fafa26 Load local manga if not connection when possible #547 2023-11-18 13:35:12 +02:00
Koitharu
aa02233883 Update parsers 2023-11-18 13:35:12 +02:00
Abay Emes
5405fdb85a Translated using Weblate (Kazakh)
Currently translated at 100.0% (524 of 524 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
2023-11-18 13:35:03 +02:00
InfinityDouki56
38ad7e1fd4 Translated using Weblate (Filipino)
Currently translated at 88.3% (463 of 524 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-11-18 12:31:21 +02:00
gallegonovato
06372083fd Translated using Weblate (Spanish)
Currently translated at 100.0% (524 of 524 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-18 12:31:21 +02:00
Isira Seneviratne
d5d3154074 Avoid accidental link clicks 2023-11-18 12:30:39 +02:00
Koitharu
1a279966d9 Update parsers 2023-11-14 07:54:03 +02:00
Koitharu
3222c2128e Translated using Weblate (Russian)
Currently translated at 99.8% (523 of 524 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-11-14 07:45:12 +02:00
gallegonovato
872c859efe Translated using Weblate (Spanish)
Currently translated at 100.0% (521 of 521 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (519 of 519 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (510 of 510 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-14 07:45:12 +02:00
Макар Разин
b79c00f8df Translated using Weblate (Ukrainian)
Currently translated at 100.0% (510 of 510 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (510 of 510 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (510 of 510 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-14 07:45:12 +02:00
Koitharu
e7d3d9811d Fix reader zoom buttons 2023-11-12 17:45:37 +02:00
Koitharu
4fdfc75833 Try fix strange crashes 2023-11-12 16:57:05 +02:00
Koitharu
9754ebf1bb Reduce main menu while search opened 2023-11-12 16:48:18 +02:00
Koitharu
fee35cceab Sources settings screen 2023-11-12 16:30:11 +02:00
Koitharu
b928c4123c Update explore navigation 2023-11-12 13:16:42 +02:00
Koitharu
b093a885c9 Sources catalog 2023-11-12 12:59:12 +02:00
Koitharu
dd898579c9 Option to lock reader screen rotation 2023-11-11 15:01:08 +02:00
Koitharu
73143d2f94 Rework favourite sheet 2023-11-11 14:40:30 +02:00
Koitharu
563752f6a4 Upgrade gradle 2023-11-11 12:59:16 +02:00
Koitharu
7135902100 Update parsers 2023-11-10 14:55:28 +02:00
Nayuki
969947ef71 Translated using Weblate (Thai)
Currently translated at 73.2% (373 of 509 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
GpixeL
806e4eade6 Translated using Weblate (Indonesian)
Currently translated at 99.4% (506 of 509 strings)

Co-authored-by: GpixeL <gamesfire313@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Abay Emes
063cfbe6b9 Translated using Weblate (Kazakh)
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Kazakh)

Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-11-10 14:48:38 +02:00
InfinityDouki56
7cb94a3baa Translated using Weblate (Filipino)
Currently translated at 88.8% (452 of 509 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Oğuz Ersen
894c584c78 Translated using Weblate (Turkish)
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
gallegonovato
2f65e7776a Translated using Weblate (Spanish)
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Макар Разин
76c56c9119 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (509 of 509 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (509 of 509 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
InfinityDouki56
e0a803399c Translated using Weblate (Filipino)
Currently translated at 88.9% (452 of 508 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
kenewjr
7803f42486 Translated using Weblate (Indonesian)
Currently translated at 96.4% (490 of 508 strings)

Co-authored-by: kenewjr <kenelewatan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Макар Разин
39713b3cf6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (508 of 508 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Nayuki
8ebf5cea62 Translated using Weblate (Thai)
Currently translated at 68.7% (349 of 508 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Abay Emes
663dabe218 Added translation using Weblate (Kazakh)
Translated using Weblate (Kazakh)

Currently translated at 57.4% (292 of 508 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Tommy12pl
3a5d0120bf Translated using Weblate (Chinese (Simplified))
Currently translated at 99.4% (505 of 508 strings)

Co-authored-by: Tommy12pl <tommy12pl@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
gallegonovato
a773f932d4 Translated using Weblate (Spanish)
Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Koitharu
2a5812735f Cubic reader scroll speed 2023-11-05 08:54:07 +02:00
Koitharu
06ec145802 Update parsers 2023-11-02 08:56:43 +02:00
Koitharu
6624778f7f Fix periodical backups 2023-11-02 08:50:51 +02:00
Koitharu
1af1f071ad Fix crashes 2023-11-01 17:25:55 +02:00
Koitharu
f87db4e6d3 Update dependencies 2023-11-01 16:38:10 +02:00
Crono
07bd66fb39 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: Crono <cronoreader@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-11-01 16:32:38 +02:00
Koitharu
4bb0d52217 Fix downloading 2023-10-28 16:39:43 +03:00
Koitharu
66de4bd49e Translated using Weblate (Russian)
Currently translated at 100.0% (508 of 508 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Bai
ff12d63696 Translated using Weblate (Turkish)
Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: Bai <batuhanakkurt000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
InfinityDouki56
c168a841f3 Translated using Weblate (Filipino)
Currently translated at 88.9% (451 of 507 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
pro maxime
8bfb676e6a Translated using Weblate (Arabic)
Currently translated at 36.0% (183 of 507 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: pro maxime <promaxime45@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-10-28 16:16:39 +03:00
gallegonovato
d5c0ce280e Translated using Weblate (Spanish)
Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Vinícius Saturnino
b34627c361 Translated using Weblate (Portuguese)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Vinícius Saturnino <saturninodepaulavinicius62@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Paulo Oliveira
cbc3be056a Translated using Weblate (Portuguese)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Paulo Oliveira <junior.literasas@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Koitharu
d9acc4ec18 Fix periodical backups to external directory 2023-10-28 16:14:47 +03:00
Koitharu
577cc848ee Scroll lists to top atomatically 2023-10-28 15:26:22 +03:00
Koitharu
8a64c88a07 (Temporary) remove chapters list from downloads 2023-10-28 14:44:58 +03:00
Koitharu
1cd7745e38 Update parsers 2023-10-28 13:26:02 +03:00
Koitharu
395b3f7200 Fix proguard rules 2023-10-27 17:27:40 +03:00
Koitharu
b8db4c81d8 Handle up navigation from reader 2023-10-27 16:44:40 +03:00
Koitharu
98bd42f3ae Remove deletions from sync process 2023-10-27 15:02:10 +03:00
Koitharu
db8835a7b8 Fix history restoring 2023-10-27 14:18:14 +03:00
Koitharu
afe50a9ed6 Fixes 2023-10-27 13:58:04 +03:00
Koitharu
beba818f57 Periodic backups 2023-10-26 17:24:11 +03:00
Koitharu
beb17ef442 Pause autoscroll while touch down 2023-10-26 16:13:30 +03:00
Koitharu
24f1546019 Fix pagination 2023-10-26 12:45:32 +03:00
ngocanhtve
1b0fed5c56 Translated using Weblate (Vietnamese)
Currently translated at 84.1% (419 of 498 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-10-26 12:29:29 +03:00
Zakhar Timoshenko
fd01367601 Move Kitsu scrobbler to kotlin dir 2023-06-05 17:33:12 +03:00
Zakhar Timoshenko
cb64740349 Merge branch 'devel' into feature/kitsu
# Conflicts:
#	app/src/main/res/values/strings.xml
2023-06-05 17:21:56 +03:00
Zakhar Timoshenko
6fdcaf0d02 Merge branch 'devel' into feature/kitsu
# Conflicts:
#	app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
#	app/src/main/res/values/strings.xml
2023-05-29 17:29:05 +03:00
Koitharu
56de725cf1 Open Kitsu auth activity 2023-05-06 18:15:24 +03:00
Koitharu
7a2ad47405 Merge branch 'devel' into feature/kitsu 2023-05-06 18:07:02 +03:00
Zakhar Timoshenko
41551451b0 Part 1 2023-05-06 16:15:17 +03:00
Zakhar Timoshenko
d5c24cd5c8 Initial adding of Kitsu scrobbler 2023-05-03 13:54:19 +03:00
577 changed files with 18211 additions and 6010 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
ko_fi: xtimms
custom: ["https://yoomoney.ru/to/410012543938752"]

2
.gitignore vendored
View File

@@ -10,11 +10,13 @@
/.idea/compiler.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/ktlint-plugin.xml
/.idea/assetWizardSettings.xml
/.idea/kotlinScripting.xml
/.idea/kotlinc.xml
/.idea/deploymentTargetDropDown.xml
/.idea/androidTestResultsUserPreferences.xml
/.idea/deploymentTargetSelector.xml
/.idea/render.experimental.xml
/.idea/inspectionProfiles/
.DS_Store

View File

@@ -1,11 +1,12 @@
## Kotatsu contribution guidelines
- If you want to fix bug or implement a new feature, that already mention in the [issues](https://github.com/KotatsuApp/Kotatsu/issues), please, assign this issue to you and/or comment about it.
- Whether you have to implement new feature, please, open an issue or discussion regarding it to ensure it will be accepted.
- Translations have to be managed using the [Weblate](https://hosted.weblate.org/engage/kotatsu/) platform.
- In case you want to add a new manga source, refer to the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers).
+ If you want to **fix bugs** or **implement new features** that **already have an [issue card](https://github.com/KotatsuApp/Kotatsu/issues):** please assign this issue to you and/or comment about it.
+ If you want to **implement a new feature:** open an issue or discussion regarding it to ensure it will be accepted.
+ **Translations** have to be managed using the [Weblate](https://hosted.weblate.org/engage/kotatsu/) platform.
+ In case you want to **add a new manga source,** refer to the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers).
Refactoring or some dev-faces improvements are also might be accepted, however please stick to the following principles:
- Performance matters. In the case of choosing between source code beauty and performance, performance should be a priority.
- Please, do not modify readme and other information files (except for typos).
- Avoid adding new dependencies unless required. APK size is important.
**Refactoring** or some **dev-faces improvements** might also be accepted. However, please stick to the following principles:
+ **Performance matters.** In the case of choosing between source code beauty and performance, performance should be a priority.
+ Please, **do not modify readme and other information files** (except for typos).
+ **Avoid adding new dependencies** unless required. APK size is important.

View File

@@ -19,7 +19,7 @@ Kotatsu is a free and open source manga reader for Android.
* Tablet-optimized Material You UI
* Standard and Webtoon-optimized reader
* Notifications about new chapters with updates feed
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
* Password/fingerprint protect access to the app
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices

View File

@@ -16,13 +16,13 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 590
versionName = '6.2.3'
versionCode = 624
versionName = '6.7.2'
generatedDensities = []
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
// arg("room.generateKotlin", "true") TODO: enable later
arg("room.schemaLocation", "$projectDir/schemas")
arg('room.generateKotlin', 'true')
arg('room.schemaLocation', "$projectDir/schemas")
}
androidResources {
generateLocaleConfig true
@@ -33,7 +33,6 @@ android {
applicationIdSuffix = '.debug'
}
release {
multiDexEnabled false
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@@ -48,11 +47,12 @@ android {
main.java.srcDirs += 'src/main/kotlin/'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += [
'-opt-in=kotlin.ExperimentalStdlibApi',
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
@@ -82,32 +82,32 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:6bf0ae92e4') {
implementation('com.github.KotatsuApp:kotatsu-parsers:3ff028c4e9') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.10'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.activity:activity-ktx:1.8.0'
implementation 'androidx.fragment:fragment-ktx:1.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-service:2.6.2'
implementation 'androidx.lifecycle:lifecycle-process:2.6.2'
implementation 'androidx.activity:activity-ktx:1.8.2'
implementation 'androidx.fragment:fragment-ktx:1.6.2'
implementation 'androidx.collection:collection:1.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta02'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.6.2'
implementation 'com.google.android.material:material:1.12.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.7.0'
// TODO https://issuetracker.google.com/issues/254846063
implementation 'androidx.work:work-runtime-ktx:2.8.1'
implementation 'androidx.work:work-runtime:2.9.0'
//noinspection GradleDependency
implementation('com.google.guava:guava:32.0.1-android') {
exclude group: 'com.google.guava', module: 'failureaccess'
@@ -115,47 +115,51 @@ dependencies {
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
}
implementation 'androidx.room:room-runtime:2.6.0'
implementation 'androidx.room:room-ktx:2.6.0'
ksp 'androidx.room:room-compiler:2.6.0'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-ktx:2.6.1'
ksp 'androidx.room:room-compiler:2.6.1'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
implementation 'com.squareup.okio:okio:3.6.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0'
implementation 'com.squareup.okio:okio:3.8.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation 'com.google.dagger:hilt-android:2.48.1'
kapt 'com.google.dagger:hilt-compiler:2.48.1'
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'com.google.dagger:hilt-android:2.50'
kapt 'com.google.dagger:hilt-compiler:2.50'
implementation 'androidx.hilt:hilt-work:1.1.0'
kapt 'androidx.hilt:hilt-compiler:1.1.0'
implementation 'io.coil-kt:coil-base:2.4.0'
implementation 'io.coil-kt:coil-svg:2.4.0'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:cf089a264d'
implementation 'io.coil-kt:coil-base:2.5.0'
implementation 'io.coil-kt:coil-svg:2.5.0'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:02e6d6cfe9'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'ch.acra:acra-http:5.11.2'
implementation 'ch.acra:acra-dialog:5.11.2'
implementation 'ch.acra:acra-http:5.11.3'
implementation 'ch.acra:acra-dialog:5.11.3'
compileOnly 'com.google.auto.service:auto-service-annotations:1.1.1'
ksp 'dev.zacsweers.autoservice:auto-service-ksp:1.1.0'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
implementation 'org.conscrypt:conscrypt-android:2.5.2'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.13'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20230618'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'org.json:json:20240205'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0'
androidTestImplementation 'androidx.room:room-testing:2.6.0'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
androidTestImplementation 'androidx.room:room-testing:2.6.1'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.48.1'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.48.1'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.50'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.50'
}

View File

@@ -18,3 +18,6 @@
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
-keep class org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy { *; }
-keep class org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment { *; }
-keep class org.jsoup.parser.Tag
-keep class org.jsoup.internal.StringUtil

View File

@@ -36,7 +36,7 @@ class KotatsuApp : BaseApp() {
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
.penaltyDeath()
.detectFragmentReuse()
// .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
.detectWrongFragmentContainer()
.detectRetainInstanceUsage()
.detectSetUserVisibleHint()
.detectFragmentTagUsage()

View File

@@ -10,6 +10,8 @@ class CurlLoggingInterceptor(
private val curlOptions: String? = null
) : Interceptor {
private val escapeRegex = Regex("([\\[\\]\"])")
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var isCompressed = false
@@ -40,7 +42,7 @@ class CurlLoggingInterceptor(
if (isCompressed) {
curlCmd.append(" --compressed")
}
curlCmd.append(" \"").append(request.url).append('"')
curlCmd.append(" \"").append(request.url.toString().escape()).append('"')
log("---cURL (" + request.url + ")")
log(curlCmd.toString())
@@ -48,7 +50,12 @@ class CurlLoggingInterceptor(
return chain.proceed(request)
}
private fun String.escape() = replace("\"", "\\\"")
private fun String.escape() = replace(escapeRegex) { match ->
"\\" + match.value
}
// .replace("\"", "\\\"")
// .replace("[", "\\[")
// .replace("]", "\\]")
private fun log(msg: String) {
Log.d("CURL", msg)

View File

@@ -5,6 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
@@ -19,19 +20,14 @@ class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSourc
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("")
override val sortOrders: Set<SortOrder>
override val availableSortOrders: Set<SortOrder>
get() = EnumSet.allOf(SortOrder::class.java)
override suspend fun getDetails(manga: Manga): Manga {
TODO("Not yet implemented")
}
override suspend fun getList(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
TODO("Not yet implemented")
}
@@ -39,7 +35,7 @@ class DummyParser(context: MangaLoaderContext) : MangaParser(context, MangaSourc
TODO("Not yet implemented")
}
override suspend fun getTags(): Set<MangaTag> {
override suspend fun getAvailableTags(): Set<MangaTag> {
TODO("Not yet implemented")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.bookmarks.data
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.Date
import java.time.Instant
fun BookmarkEntity.toBookmark(manga: Manga) = Bookmark(
manga = manga,
@@ -11,7 +11,7 @@ fun BookmarkEntity.toBookmark(manga: Manga) = Bookmark(
page = page,
scroll = scroll,
imageUrl = imageUrl,
createdAt = Date(createdAt),
createdAt = Instant.ofEpochMilli(createdAt),
percent = percent,
)
@@ -22,7 +22,7 @@ fun Bookmark.toEntity() = BookmarkEntity(
page = page,
scroll = scroll,
imageUrl = imageUrl,
createdAt = createdAt.time,
createdAt = createdAt.toEpochMilli(),
percent = percent,
)

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.bookmarks.domain
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.local.data.ImageFileFilter
import org.koitharu.kotatsu.local.data.hasImageExtension
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import java.util.Date
import java.time.Instant
data class Bookmark(
val manga: Manga,
@@ -13,7 +13,7 @@ data class Bookmark(
val page: Int,
val scroll: Int,
val imageUrl: String,
val createdAt: Date,
val createdAt: Instant,
val percent: Float,
) : ListModel {
@@ -38,7 +38,6 @@ data class Bookmark(
)
private fun isImageUrlDirect(): Boolean {
val extension = imageUrl.substringAfterLast('.')
return extension.isNotEmpty() && ImageFileFilter().isExtensionValid(extension)
return hasImageExtension(imageUrl)
}
}

View File

@@ -13,7 +13,6 @@ import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.listHeaderAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
class BookmarksAdapter(
@@ -32,13 +31,6 @@ class BookmarksAdapter(
}
override fun getSectionText(context: Context, position: Int): CharSequence? {
val list = items
for (i in (0..position).reversed()) {
val item = list.getOrNull(i) ?: continue
if (item is ListHeader) {
return item.getText(context)
}
}
return null
return findHeader(position)?.getText(context)
}
}

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
class CaptchaNotifier(
private val context: Context,
@@ -58,16 +59,27 @@ class CaptchaNotifier(
manager.notify(TAG, exception.source.hashCode(), notification)
}
fun dismiss(source: MangaSource) {
NotificationManagerCompat.from(context).cancel(TAG, source.hashCode())
}
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
val e = result.throwable
if (e is CloudFlareProtectedException) {
if (e is CloudFlareProtectedException && request.parameters.value<Boolean>(PARAM_IGNORE_CAPTCHA) != true) {
notify(e)
}
}
private companion object {
companion object {
fun ImageRequest.Builder.ignoreCaptchaErrors() = setParameter(
key = PARAM_IGNORE_CAPTCHA,
value = true,
memoryCacheKey = null,
)
private const val PARAM_IGNORE_CAPTCHA = "ignore_captcha"
private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID
}

View File

@@ -48,8 +48,8 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
if (!catchingWebViewUnavailability {
setContentView(
ActivityBrowserBinding.inflate(
layoutInflater
)
layoutInflater,
),
)
}) {
return
@@ -82,9 +82,11 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
}
override fun onDestroy() {
viewBinding.webView.run {
stopLoading()
destroy()
runCatching {
viewBinding.webView
}.onSuccess {
it.stopLoading()
it.destroy()
}
super.onDestroy()
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core
import android.app.Application
import android.content.Context
import android.os.Build
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
@@ -11,6 +12,7 @@ import androidx.work.WorkManager
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.dialog
@@ -18,6 +20,7 @@ import org.acra.config.httpSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.conscrypt.Conscrypt
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -26,6 +29,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.settings.work.WorkScheduleManager
import java.security.Security
import javax.inject.Inject
import javax.inject.Provider
@@ -39,7 +43,7 @@ open class BaseApp : Application(), Configuration.Provider {
lateinit var activityLifecycleCallbacks: Set<@JvmSuppressWildcards ActivityLifecycleCallbacks>
@Inject
lateinit var database: MangaDatabase
lateinit var database: Provider<MangaDatabase>
@Inject
lateinit var settings: AppSettings
@@ -56,12 +60,26 @@ open class BaseApp : Application(), Configuration.Provider {
@Inject
lateinit var workManagerProvider: Provider<WorkManager>
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
ACRA.errorReporter.putCustomData("isOriginalApp", appValidator.isOriginalApp.toString())
AppCompatDelegate.setDefaultNightMode(settings.theme)
AppCompatDelegate.setApplicationLocales(settings.appLocales)
// TLS 1.3 support for Android < 10
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Security.insertProviderAt(Conscrypt.newProvider(), 1)
}
setupActivityLifecycleCallbacks()
processLifecycleScope.launch {
val isOriginalApp = withContext(Dispatchers.Default) {
appValidator.isOriginalApp
}
ACRA.errorReporter.putCustomData("isOriginalApp", isOriginalApp.toString())
}
processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()
}
@@ -74,13 +92,6 @@ open class BaseApp : Application(), Configuration.Provider {
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.JSON
excludeMatchingSharedPreferencesKeys = listOf(
"sources_\\w+",
AppSettings.KEY_APP_PASSWORD,
AppSettings.KEY_PROXY_LOGIN,
AppSettings.KEY_PROXY_ADDRESS,
AppSettings.KEY_PROXY_PASSWORD,
)
httpSender {
uri = getString(R.string.url_error_report)
basicAuthLogin = getString(R.string.acra_login)
@@ -97,7 +108,6 @@ open class BaseApp : Application(), Configuration.Provider {
ReportField.STACK_TRACE,
ReportField.CRASH_CONFIGURATION,
ReportField.CUSTOM_DATA,
ReportField.SHARED_PREFERENCES,
)
dialog {
@@ -110,15 +120,9 @@ open class BaseApp : Application(), Configuration.Provider {
}
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
@WorkerThread
private fun setupDatabaseObservers() {
val tracker = database.invalidationTracker
val tracker = database.get().invalidationTracker
databaseObservers.forEach {
tracker.addObserver(it)
}

View File

@@ -0,0 +1,24 @@
package org.koitharu.kotatsu.core
import android.content.Context
import com.google.auto.service.AutoService
import org.acra.builder.ReportBuilder
import org.acra.config.CoreConfiguration
import org.acra.config.ReportingAdministrator
@AutoService(ReportingAdministrator::class)
class ErrorReportingAdmin : ReportingAdministrator {
override fun shouldStartCollecting(
context: Context,
config: CoreConfiguration,
reportBuilder: ReportBuilder
): Boolean {
return reportBuilder.exception?.isDeadOs() != true
}
private fun Throwable.isDeadOs(): Boolean {
val className = javaClass.simpleName
return className == "DeadSystemException" || className == "DeadSystemRuntimeException" || cause?.isDeadOs() == true
}
}

View File

@@ -3,17 +3,20 @@ package org.koitharu.kotatsu.core.backup
import org.json.JSONArray
class BackupEntry(
val name: String,
val name: Name,
val data: JSONArray
) {
companion object Names {
enum class Name(
val key: String,
) {
const val INDEX = "index"
const val HISTORY = "history"
const val CATEGORIES = "categories"
const val FAVOURITES = "favourites"
const val SETTINGS = "settings"
const val BOOKMARKS = "bookmarks"
INDEX("index"),
HISTORY("history"),
CATEGORIES("categories"),
FAVOURITES("favourites"),
SETTINGS("settings"),
BOOKMARKS("bookmarks"),
SOURCES("sources"),
}
}

View File

@@ -7,8 +7,10 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.util.Date
import javax.inject.Inject
private const val PAGE_SIZE = 10
@@ -20,7 +22,7 @@ class BackupRepository @Inject constructor(
suspend fun dumpHistory(): BackupEntry {
var offset = 0
val entry = BackupEntry(BackupEntry.HISTORY, JSONArray())
val entry = BackupEntry(BackupEntry.Name.HISTORY, JSONArray())
while (true) {
val history = db.getHistoryDao().findAll(offset, PAGE_SIZE)
if (history.isEmpty()) {
@@ -41,7 +43,7 @@ class BackupRepository @Inject constructor(
}
suspend fun dumpCategories(): BackupEntry {
val entry = BackupEntry(BackupEntry.CATEGORIES, JSONArray())
val entry = BackupEntry(BackupEntry.Name.CATEGORIES, JSONArray())
val categories = db.getFavouriteCategoriesDao().findAll()
for (item in categories) {
entry.data.put(JsonSerializer(item).toJson())
@@ -51,9 +53,9 @@ class BackupRepository @Inject constructor(
suspend fun dumpFavourites(): BackupEntry {
var offset = 0
val entry = BackupEntry(BackupEntry.FAVOURITES, JSONArray())
val entry = BackupEntry(BackupEntry.Name.FAVOURITES, JSONArray())
while (true) {
val favourites = db.getFavouritesDao().findAll(offset, PAGE_SIZE)
val favourites = db.getFavouritesDao().findAllRaw(offset, PAGE_SIZE)
if (favourites.isEmpty()) {
break
}
@@ -72,7 +74,7 @@ class BackupRepository @Inject constructor(
}
suspend fun dumpBookmarks(): BackupEntry {
val entry = BackupEntry(BackupEntry.BOOKMARKS, JSONArray())
val entry = BackupEntry(BackupEntry.Name.BOOKMARKS, JSONArray())
val all = db.getBookmarksDao().findAll()
for ((m, b) in all) {
val json = JSONObject()
@@ -90,7 +92,7 @@ class BackupRepository @Inject constructor(
}
fun dumpSettings(): BackupEntry {
val entry = BackupEntry(BackupEntry.SETTINGS, JSONArray())
val entry = BackupEntry(BackupEntry.Name.SETTINGS, JSONArray())
val settingsDump = settings.getAllValues().toMutableMap()
settingsDump.remove(AppSettings.KEY_APP_PASSWORD)
settingsDump.remove(AppSettings.KEY_PROXY_PASSWORD)
@@ -101,8 +103,18 @@ class BackupRepository @Inject constructor(
return entry
}
suspend fun dumpSources(): BackupEntry {
val entry = BackupEntry(BackupEntry.Name.SOURCES, JSONArray())
val all = db.getSourcesDao().findAll()
for (source in all) {
val json = JsonSerializer(source).toJson()
entry.data.put(json)
}
return entry
}
fun createIndex(): BackupEntry {
val entry = BackupEntry(BackupEntry.INDEX, JSONArray())
val entry = BackupEntry(BackupEntry.Name.INDEX, JSONArray())
val json = JSONObject()
json.put("app_id", BuildConfig.APPLICATION_ID)
json.put("app_version", BuildConfig.VERSION_CODE)
@@ -111,6 +123,11 @@ class BackupRepository @Inject constructor(
return entry
}
fun getBackupDate(entry: BackupEntry?): Date? {
val timestamp = entry?.data?.optJSONObject(0)?.getLongOrDefault("created_at", 0) ?: 0
return if (timestamp == 0L) null else Date(timestamp)
}
suspend fun restoreHistory(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
@@ -184,6 +201,17 @@ class BackupRepository @Inject constructor(
return result
}
suspend fun restoreSources(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val source = JsonDeserializer(item).toMangaSourceEntity()
result += runCatchingCancellable {
db.getSourcesDao().upsert(source)
}
}
return result
}
fun restoreSettings(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {

View File

@@ -1,25 +1,44 @@
package org.koitharu.kotatsu.core.backup
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.json.JSONArray
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import java.io.File
import java.util.EnumSet
import java.util.zip.ZipFile
class BackupZipInput(val file: File) : Closeable {
private val zipFile = ZipFile(file)
suspend fun getEntry(name: String): BackupEntry? = runInterruptible(Dispatchers.IO) {
val entry = zipFile.getEntry(name) ?: return@runInterruptible null
suspend fun getEntry(name: BackupEntry.Name): BackupEntry? = runInterruptible(Dispatchers.IO) {
val entry = zipFile.getEntry(name.key) ?: return@runInterruptible null
val json = zipFile.getInputStream(entry).use {
JSONArray(it.bufferedReader().readText())
}
BackupEntry(name, json)
}
suspend fun entries(): Set<BackupEntry.Name> = runInterruptible(Dispatchers.IO) {
zipFile.entries().toList().mapNotNullTo(EnumSet.noneOf(BackupEntry.Name::class.java)) { ze ->
BackupEntry.Name.entries.find { it.key == ze.name }
}
}
override fun close() {
zipFile.close()
}
fun cleanupAsync() {
processLifecycleScope.launch(Dispatchers.IO, CoroutineStart.ATOMIC) {
runCatching {
close()
file.delete()
}
}
}
}

View File

@@ -5,10 +5,10 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.format
import org.koitharu.kotatsu.core.zip.ZipOutput
import java.io.File
import java.util.Date
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.zip.Deflater
@@ -17,7 +17,7 @@ class BackupZipOutput(val file: File) : Closeable {
private val output = ZipOutput(file, Deflater.BEST_COMPRESSION)
suspend fun put(entry: BackupEntry) = runInterruptible(Dispatchers.IO) {
output.put(entry.name, entry.data.toString(2))
output.put(entry.name.key, entry.data.toString(2))
}
suspend fun finish() = runInterruptible(Dispatchers.IO) {
@@ -29,7 +29,7 @@ class BackupZipOutput(val file: File) : Closeable {
}
}
private const val DIR_BACKUPS = "backups"
const val DIR_BACKUPS = "backups"
suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptible(Dispatchers.IO) {
val dir = context.run {
@@ -39,7 +39,7 @@ suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptibl
val filename = buildString {
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
append('_')
append(Date().format("ddMMyyyy"))
append(LocalDate.now().format(DateTimeFormatter.ofPattern("ddMMyyyy")))
append(".bk.zip")
}
BackupZipOutput(File(dir, filename))

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
@@ -78,6 +79,12 @@ class JsonDeserializer(private val json: JSONObject) {
percent = json.getDouble("percent").toFloat(),
)
fun toMangaSourceEntity() = MangaSourceEntity(
source = json.getString("source"),
isEnabled = json.getBoolean("enabled"),
sortKey = json.getInt("sort_key"),
)
fun toMap(): Map<String, Any?> {
val map = mutableMapOf<String, Any?>()
val keys = json.keys()

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
@@ -82,6 +83,14 @@ class JsonSerializer private constructor(private val json: JSONObject) {
},
)
constructor(e: MangaSourceEntity) : this(
JSONObject().apply {
put("source", e.source)
put("enabled", e.isEnabled)
put("sort_key", e.sortKey)
},
)
constructor(m: Map<String, *>) : this(
JSONObject(m),
)

View File

@@ -12,7 +12,7 @@ class ExpiringLruCache<T>(
private val cache = LruCache<ContentCache.Key, ExpiringValue<T>>(maxSize)
operator fun get(key: ContentCache.Key): T? {
val value = cache.get(key) ?: return null
val value = cache[key] ?: return null
if (value.isExpired) {
cache.remove(key)
}

View File

@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration13To14
import org.koitharu.kotatsu.core.db.migrations.Migration14To15
import org.koitharu.kotatsu.core.db.migrations.Migration15To16
import org.koitharu.kotatsu.core.db.migrations.Migration16To17
import org.koitharu.kotatsu.core.db.migrations.Migration17To18
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
@@ -53,7 +54,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TracksDao
const val DATABASE_VERSION = 17
const val DATABASE_VERSION = 18
@Database(
entities = [
@@ -108,6 +109,7 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
Migration14To15(),
Migration15To16(),
Migration16To17(context),
Migration17To18(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.db.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
@@ -23,6 +24,10 @@ abstract class MangaDao {
@Query("SELECT * FROM manga WHERE public_url = :publicUrl")
abstract suspend fun findByPublicUrl(publicUrl: String): MangaWithTags?
@Transaction
@Query("SELECT * FROM manga WHERE source = :source")
abstract suspend fun findAllBySource(source: String): List<MangaWithTags>
@Transaction
@Query("SELECT * FROM manga WHERE (title LIKE :query OR alt_title LIKE :query) AND manga_id IN (SELECT manga_id FROM favourites UNION SELECT manga_id FROM history) LIMIT :limit")
abstract suspend fun searchByTitle(query: String, limit: Int): List<MangaWithTags>
@@ -43,6 +48,10 @@ abstract class MangaDao {
@Query("DELETE FROM manga_tags WHERE manga_id = :mangaId")
abstract suspend fun clearTagRelation(mangaId: Long)
@Transaction
@Delete
abstract suspend fun delete(subjects: Collection<MangaEntity>)
@Transaction
open suspend fun upsert(manga: MangaEntity, tags: Iterable<TagEntity>? = null) {
upsert(manga)

View File

@@ -4,10 +4,15 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Transaction
import androidx.room.Upsert
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import kotlinx.coroutines.flow.Flow
import org.intellij.lang.annotations.Language
import org.koitharu.kotatsu.core.db.entity.MangaSourceEntity
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
@Dao
abstract class MangaSourcesDao {
@@ -15,11 +20,11 @@ abstract class MangaSourcesDao {
@Query("SELECT * FROM sources ORDER BY sort_key")
abstract suspend fun findAll(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 1 ORDER BY sort_key")
abstract suspend fun findAllEnabled(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key")
abstract suspend fun findAllDisabled(): List<MangaSourceEntity>
@Query("SELECT * FROM sources WHERE enabled = 1 ORDER BY sort_key")
abstract fun observeEnabled(): Flow<List<MangaSourceEntity>>
@Query("SELECT * FROM sources WHERE enabled = 0")
abstract fun observeDisabled(): Flow<List<MangaSourceEntity>>
@Query("SELECT * FROM sources ORDER BY sort_key")
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
@@ -40,6 +45,22 @@ abstract class MangaSourcesDao {
@Upsert
abstract suspend fun upsert(entry: MangaSourceEntity)
fun observeEnabled(order: SourcesSortOrder): Flow<List<MangaSourceEntity>> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
return observeImpl(query)
}
suspend fun findAllEnabled(order: SourcesSortOrder): List<MangaSourceEntity> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
return findAllImpl(query)
}
@Transaction
open suspend fun setEnabled(source: String, isEnabled: Boolean) {
if (updateIsEnabled(source, isEnabled) == 0) {
@@ -54,4 +75,16 @@ abstract class MangaSourcesDao {
@Query("UPDATE sources SET enabled = :isEnabled WHERE source = :source")
protected abstract suspend fun updateIsEnabled(source: String, isEnabled: Boolean): Int
@RawQuery(observedEntities = [MangaSourceEntity::class])
protected abstract fun observeImpl(query: SupportSQLiteQuery): Flow<List<MangaSourceEntity>>
@RawQuery
protected abstract suspend fun findAllImpl(query: SupportSQLiteQuery): List<MangaSourceEntity>
private fun getOrderBy(order: SourcesSortOrder) = when (order) {
SourcesSortOrder.ALPHABETIC -> "source ASC"
SourcesSortOrder.POPULARITY -> "(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC"
SourcesSortOrder.MANUAL -> "sort_key ASC"
}
}

View File

@@ -31,6 +31,16 @@ abstract class TagsDao {
)
abstract suspend fun findPopularTags(source: String, limit: Int): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id
WHERE tags.source = :source
GROUP BY tags.title
ORDER BY COUNT(manga_id) ASC
LIMIT :limit""",
)
abstract suspend fun findRareTags(source: String, limit: Int): List<TagEntity>
@Query(
"""SELECT tags.* FROM tags
LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id

View File

@@ -24,4 +24,5 @@ data class MangaPrefsEntity(
@ColumnInfo(name = "cf_brightness") val cfBrightness: Float,
@ColumnInfo(name = "cf_contrast") val cfContrast: Float,
@ColumnInfo(name = "cf_invert") val cfInvert: Boolean,
@ColumnInfo(name = "cf_grayscale") val cfGrayscale: Boolean,
)

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration17To18 : Migration(17, 18) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE preferences ADD COLUMN `cf_grayscale` INTEGER NOT NULL DEFAULT 0")
}
}

View File

@@ -1,8 +0,0 @@
package org.koitharu.kotatsu.core.exceptions
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
class CompositeException(val errors: Collection<Throwable>) : Exception() {
override val message: String = errors.mapNotNullToSet { it.message }.joinToString()
}

View File

@@ -1,13 +1,13 @@
package org.koitharu.kotatsu.core.exceptions
import okio.IOException
import java.util.Date
import java.time.Instant
import java.time.temporal.ChronoUnit
class TooManyRequestExceptions(
val url: String,
val retryAt: Date?,
val retryAt: Instant?,
) : IOException() {
val retryAfter: Long
get() = if (retryAt == null) 0 else (retryAt.time - System.currentTimeMillis()).coerceAtLeast(0)
get() = retryAt?.until(Instant.now(), ChronoUnit.MILLIS) ?: 0
}

View File

@@ -20,8 +20,9 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Locale
import java.util.concurrent.ConcurrentLinkedQueue
@@ -41,11 +42,7 @@ class FileLogger(
}
val isEnabled: Boolean
get() = settings.isLoggingEnabled
private val dateFormat = SimpleDateFormat.getDateTimeInstance(
SimpleDateFormat.SHORT,
SimpleDateFormat.SHORT,
Locale.ROOT,
)
private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(Locale.ROOT)
private val buffer = ConcurrentLinkedQueue<String>()
private val mutex = Mutex()
private var flushJob: Job? = null
@@ -55,7 +52,7 @@ class FileLogger(
return
}
val text = buildString {
append(dateFormat.format(Date()))
append(dateTimeFormatter.format(LocalDateTime.now()))
append(": ")
if (e != null) {
append("E!")

View File

@@ -5,7 +5,7 @@ import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import java.util.Date
import java.time.Instant
@Parcelize
data class FavouriteCategory(
@@ -13,7 +13,7 @@ data class FavouriteCategory(
val title: String,
val sortKey: Int,
val order: ListSortOrder,
val createdAt: Date,
val createdAt: Instant,
val isTrackingEnabled: Boolean,
val isVisibleInLibrary: Boolean,
) : Parcelable, ListModel {

View File

@@ -1,13 +1,22 @@
package org.koitharu.kotatsu.core.model
import android.net.Uri
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.collection.MutableObjectIntMap
import androidx.core.os.LocaleListCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.iterator
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.mapToSet
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import com.google.android.material.R as materialR
@JvmName("mangaIds")
fun Collection<Manga>.ids() = mapToSet { it.id }
@@ -23,14 +32,44 @@ fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
if (size <= 1) {
return size
}
val acc = HashMap<String?, Int>()
val acc = MutableObjectIntMap<String?>()
for (item in this) {
val branch = item.chapter.branch
acc[branch] = (acc[branch] ?: 0) + 1
acc[branch] = acc.getOrDefault(branch, 0) + 1
}
return acc.values.max()
var max = 0
acc.forEachValue { x -> if (x > max) max = x }
return max
}
@get:StringRes
val MangaState.titleResId: Int
get() = when (this) {
MangaState.ONGOING -> R.string.state_ongoing
MangaState.FINISHED -> R.string.state_finished
MangaState.ABANDONED -> R.string.state_abandoned
MangaState.PAUSED -> R.string.state_paused
MangaState.UPCOMING -> R.string.state_upcoming
}
@get:DrawableRes
val MangaState.iconResId: Int
get() = when (this) {
MangaState.ONGOING -> R.drawable.ic_play
MangaState.FINISHED -> R.drawable.ic_state_finished
MangaState.ABANDONED -> R.drawable.ic_state_abandoned
MangaState.PAUSED -> R.drawable.ic_action_pause
MangaState.UPCOMING -> materialR.drawable.ic_clock_black_24dp
}
@get:StringRes
val ContentRating.titleResId: Int
get() = when (this) {
ContentRating.SAFE -> R.string.rating_safe
ContentRating.SUGGESTIVE -> R.string.rating_suggestive
ContentRating.ADULT -> R.string.rating_adult
}
fun Manga.findChapter(id: Long): MangaChapter? {
return chapters?.findById(id)
}
@@ -79,3 +118,16 @@ val Manga.appUrl: Uri
.appendQueryParameter("name", title)
.appendQueryParameter("url", url)
.build()
private val chaptersNumberFormat = DecimalFormat("#.#").also { f ->
f.decimalFormatSymbols = DecimalFormatSymbols.getInstance().also {
it.decimalSeparator = '.'
}
}
fun MangaChapter.formatNumber(): String? {
if (number <= 0f) {
return null
}
return chaptersNumberFormat.format(number.toDouble())
}

View File

@@ -2,14 +2,14 @@ package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.util.*
import java.time.Instant
@Parcelize
data class MangaHistory(
val createdAt: Date,
val updatedAt: Date,
val createdAt: Instant,
val updatedAt: Instant,
val chapterId: Long,
val page: Int,
val scroll: Int,
val percent: Float,
) : Parcelable
) : Parcelable

View File

@@ -1,14 +1,23 @@
package org.koitharu.kotatsu.core.model
import android.content.Context
import android.graphics.Color
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.text.style.SuperscriptSpan
import androidx.annotation.StringRes
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getDisplayName
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.toLocale
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale
fun MangaSource.getLocaleTitle(): String? {
val lc = Locale(locale ?: return null)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
}
import com.google.android.material.R as materialR
fun MangaSource(name: String): MangaSource {
MangaSource.entries.forEach {
@@ -18,3 +27,36 @@ fun MangaSource(name: String): MangaSource {
}
fun MangaSource.isNsfw() = contentType == ContentType.HENTAI
@get:StringRes
val ContentType.titleResId
get() = when (this) {
ContentType.MANGA -> R.string.content_type_manga
ContentType.HENTAI -> R.string.content_type_hentai
ContentType.COMICS -> R.string.content_type_comics
ContentType.OTHER -> R.string.content_type_other
}
fun MangaSource.getSummary(context: Context): String {
val type = context.getString(contentType.titleResId)
val locale = locale?.toLocale().getDisplayName(context)
return context.getString(R.string.source_summary_pattern, type, locale)
}
fun MangaSource.getTitle(context: Context): CharSequence = if (isNsfw()) {
buildSpannedString {
append(title)
append(' ')
appendNsfwLabel(context)
}
} else {
title
}
private fun SpannableStringBuilder.appendNsfwLabel(context: Context) = inSpans(
ForegroundColorSpan(context.getThemeColor(materialR.attr.colorError, Color.RED)),
RelativeSizeSpan(0.74f),
SuperscriptSpan(),
) {
append(context.getString(R.string.nsfw))
}

View File

@@ -19,7 +19,8 @@ data class ParcelableChapter(
MangaChapter(
id = parcel.readLong(),
name = parcel.readString().orEmpty(),
number = parcel.readInt(),
number = parcel.readFloat(),
volume = parcel.readInt(),
url = parcel.readString().orEmpty(),
scanlator = parcel.readString(),
uploadDate = parcel.readLong(),
@@ -31,7 +32,8 @@ data class ParcelableChapter(
override fun ParcelableChapter.write(parcel: Parcel, flags: Int) = with(chapter) {
parcel.writeLong(id)
parcel.writeString(name)
parcel.writeInt(number)
parcel.writeFloat(number)
parcel.writeInt(volume)
parcel.writeString(url)
parcel.writeString(scanlator)
parcel.writeLong(uploadDate)

View File

@@ -14,8 +14,8 @@ class CloudFlareInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
val content = response.body?.source()?.peek()?.use {
Jsoup.parse(it.inputStream(), Charsets.UTF_8.name(), response.request.url.toString())
val content = response.body?.let { response.peekBody(Long.MAX_VALUE) }?.byteStream()?.use {
Jsoup.parse(it, Charsets.UTF_8.name(), response.request.url.toString())
} ?: return response
if (content.getElementById("challenge-error-title") != null) {
val request = response.request

View File

@@ -7,6 +7,7 @@ import coil.request.ErrorResult
import coil.request.ImageResult
import coil.request.SuccessResult
import coil.size.Dimension
import coil.size.isOriginal
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
@@ -46,11 +47,13 @@ class ImageProxyInterceptor @Inject constructor(
.scheme("https")
.host("wsrv.nl")
.addQueryParameter("url", url.toString())
.addQueryParameter("fit", "outside")
.addQueryParameter("we", null)
val size = request.sizeResolver.size()
(size.height as? Dimension.Pixels)?.let { newUrl.addQueryParameter("h", it.toString()) }
(size.width as? Dimension.Pixels)?.let { newUrl.addQueryParameter("w", it.toString()) }
if (!size.isOriginal) {
newUrl.addQueryParameter("crop", "cover")
(size.height as? Dimension.Pixels)?.let { newUrl.addQueryParameter("h", it.toString()) }
(size.width as? Dimension.Pixels)?.let { newUrl.addQueryParameter("w", it.toString()) }
}
val newRequest = request.newBuilder()
.data(newUrl.build())

View File

@@ -63,6 +63,9 @@ class MirrorSwitchInterceptor @Inject constructor(
}
synchronized(obtainLock(repository.source)) {
val currentMirror = repository.domain
if (currentMirror !in mirrors) {
return@synchronized false
}
addToBlacklist(repository.source, currentMirror)
val newMirror = mirrors.firstOrNull { x ->
x != currentMirror && !isBlacklisted(repository.source, x)

View File

@@ -4,15 +4,11 @@ import okhttp3.Interceptor
import okhttp3.Response
import okhttp3.internal.closeQuietly
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
class RateLimitInterceptor : Interceptor {
private val dateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss ZZZ", Locale.ENGLISH)
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request())
if (response.code == 429) {
@@ -27,10 +23,8 @@ class RateLimitInterceptor : Interceptor {
return response
}
private fun String.parseRetryDate(): Date? {
toIntOrNull()?.let {
return Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(it.toLong()))
}
return dateFormat.parse(this)
private fun String.parseRetryDate(): Instant? {
return toLongOrNull()?.let { Instant.now().plusSeconds(it) }
?: ZonedDateTime.parse(this, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant()
}
}

View File

@@ -1,16 +1,9 @@
package org.koitharu.kotatsu.core.os
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.pm.PackageInfoCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.util.byte2HexFormatted
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.inject.Inject
import javax.inject.Singleton
@@ -18,29 +11,13 @@ import javax.inject.Singleton
class AppValidator @Inject constructor(
@ApplicationContext private val context: Context,
) {
@Suppress("NewApi")
val isOriginalApp by lazy {
getCertificateSHA1Fingerprint() == CERT_SHA1
val certificates = mapOf(CERT_SHA256.hexToByteArray() to PackageManager.CERT_INPUT_SHA256)
PackageInfoCompat.hasSignatures(context.packageManager, context.packageName, certificates, false)
}
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures")
private fun getCertificateSHA1Fingerprint(): String? = runCatching {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val signatures = requireNotNull(packageInfo?.signatures)
val cert: ByteArray = signatures.first().toByteArray()
val input: InputStream = ByteArrayInputStream(cert)
val cf = CertificateFactory.getInstance("X509")
val c = cf.generateCertificate(input) as X509Certificate
val md: MessageDigest = MessageDigest.getInstance("SHA1")
val publicKey: ByteArray = md.digest(c.encoded)
return publicKey.byte2HexFormatted()
}.onFailure { error ->
error.printStackTraceDebug()
}.getOrNull()
private companion object {
private const val CERT_SHA1 = "2C:19:C7:E8:07:61:2B:8E:94:51:1B:FD:72:67:07:64:5D:C2:58:AE"
private const val CERT_SHA256 = "67e15100bb809301783edcb6348fa3bbf83034d91e62868a91053dbd70db3f18"
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.parser
import androidx.core.net.toUri
import androidx.room.withTransaction
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
@@ -11,7 +12,9 @@ import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
@@ -77,10 +80,18 @@ class MangaDataRepository @Inject constructor(
}
suspend fun storeManga(manga: Manga) {
val tags = manga.tags.toEntities()
db.withTransaction {
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
// avoid storing local manga if remote one is already stored
val existing = if (manga.isLocal) {
db.getMangaDao().find(manga.id)?.manga
} else {
null
}
if (existing == null || existing.source == manga.source.name) {
val tags = manga.tags.toEntities()
db.getTagsDao().upsert(tags)
db.getMangaDao().upsert(manga.toEntity(), tags)
}
}
}
@@ -88,9 +99,18 @@ class MangaDataRepository @Inject constructor(
return db.getTagsDao().findTags(source.name).toMangaTags()
}
suspend fun cleanupLocalManga() {
val dao = db.getMangaDao()
val broken = dao.findAllBySource(MangaSource.LOCAL.name)
.filter { x -> x.manga.url.toUri().toFileOrNull()?.exists() == false }
if (broken.isNotEmpty()) {
dao.delete(broken.map { it.manga })
}
}
private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert) {
ReaderColorFilter(cfBrightness, cfContrast, cfInvert)
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale) {
ReaderColorFilter(cfBrightness, cfContrast, cfInvert, cfGrayscale)
} else {
null
}
@@ -102,5 +122,6 @@ class MangaDataRepository @Inject constructor(
cfBrightness = 0f,
cfContrast = 0f,
cfInvert = false,
cfGrayscale = false,
)
}

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.parser
import android.net.Uri
import coil.request.CachePolicy
import dagger.Reusable
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
@@ -8,6 +9,7 @@ import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.almostEquals
import org.koitharu.kotatsu.parsers.util.levenshteinDistance
@@ -58,7 +60,7 @@ class MangaLinkResolver @Inject constructor(
private suspend fun MangaRepository.findExact(url: String?, title: String?): Manga? {
if (!title.isNullOrEmpty()) {
val list = getList(0, title)
val list = getList(0, MangaListFilter.Search(title))
if (url != null) {
list.find { it.url == url }?.let {
return it
@@ -77,14 +79,14 @@ class MangaLinkResolver @Inject constructor(
}.ifNullOrEmpty {
seed.author
} ?: return@runCatchingCancellable null
val seedList = getList(0, seedTitle)
val seedList = getList(0, MangaListFilter.Search(seedTitle))
seedList.first { x -> x.url == url }
}.getOrThrow()
}
private suspend fun MangaRepository.getDetailsNoCache(manga: Manga): Manga {
return if (this is RemoteMangaRepository) {
getDetails(manga, withCache = false)
getDetails(manga, CachePolicy.READ_ONLY)
} else {
getDetails(manga)
}

View File

@@ -5,14 +5,18 @@ import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.network.MirrorSwitchInterceptor
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.lang.ref.WeakReference
import java.util.EnumMap
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.set
@@ -23,11 +27,19 @@ interface MangaRepository {
val sortOrders: Set<SortOrder>
val states: Set<MangaState>
val contentRatings: Set<ContentRating>
var defaultSortOrder: SortOrder
suspend fun getList(offset: Int, query: String): List<Manga>
val isMultipleTagsSupported: Boolean
suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga>
val isTagsExclusionSupported: Boolean
val isSearchSupported: Boolean
suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga>
suspend fun getDetails(manga: Manga): Manga
@@ -37,6 +49,8 @@ interface MangaRepository {
suspend fun getTags(): Set<MangaTag>
suspend fun getLocales(): Set<Locale>
suspend fun getRelated(seed: Manga): List<Manga>
@Singleton

View File

@@ -1,6 +1,8 @@
package org.koitharu.kotatsu.core.parser
import android.util.Log
import androidx.collection.MutableLongSet
import coil.request.CachePolicy
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -20,15 +22,19 @@ import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Favicons
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.util.Locale
class RemoteMangaRepository(
private val parser: MangaParser,
@@ -40,7 +46,13 @@ class RemoteMangaRepository(
get() = parser.source
override val sortOrders: Set<SortOrder>
get() = parser.sortOrders
get() = parser.availableSortOrders
override val states: Set<MangaState>
get() = parser.availableStates
override val contentRatings: Set<ContentRating>
get() = parser.availableContentRating
override var defaultSortOrder: SortOrder
get() = getConfig().defaultSortOrder ?: sortOrders.first()
@@ -48,6 +60,15 @@ class RemoteMangaRepository(
getConfig().defaultSortOrder = value
}
override val isMultipleTagsSupported: Boolean
get() = parser.isMultipleTagsSupported
override val isSearchSupported: Boolean
get() = parser.isSearchSupported
override val isTagsExclusionSupported: Boolean
get() = parser.isTagsExclusionSupported
var domain: String
get() = parser.domain
set(value) {
@@ -68,19 +89,13 @@ class RemoteMangaRepository(
}
}
override suspend fun getList(offset: Int, query: String): List<Manga> {
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
return mirrorSwitchInterceptor.withMirrorSwitching {
parser.getList(offset, query)
parser.getList(offset, filter)
}
}
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
return mirrorSwitchInterceptor.withMirrorSwitching {
parser.getList(offset, tags, sortOrder)
}
}
override suspend fun getDetails(manga: Manga): Manga = getDetails(manga, withCache = true)
override suspend fun getDetails(manga: Manga): Manga = getDetails(manga, CachePolicy.ENABLED)
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
cache.getPages(source, chapter.url)?.let { return it }
@@ -98,7 +113,11 @@ class RemoteMangaRepository(
}
override suspend fun getTags(): Set<MangaTag> = mirrorSwitchInterceptor.withMirrorSwitching {
parser.getTags()
parser.getAvailableTags()
}
override suspend fun getLocales(): Set<Locale> {
return parser.getAvailableLocales()
}
suspend fun getFavicons(): Favicons = mirrorSwitchInterceptor.withMirrorSwitching {
@@ -114,17 +133,18 @@ class RemoteMangaRepository(
return related.await()
}
suspend fun getDetails(manga: Manga, withCache: Boolean): Manga {
if (!withCache) {
return parser.getDetails(manga)
suspend fun getDetails(manga: Manga, cachePolicy: CachePolicy): Manga {
if (cachePolicy.readEnabled) {
cache.getDetails(source, manga.url)?.let { return it }
}
cache.getDetails(source, manga.url)?.let { return it }
val details = asyncSafe {
mirrorSwitchInterceptor.withMirrorSwitching {
parser.getDetails(manga)
}
}
cache.putDetails(source, manga.url, details)
if (cachePolicy.writeEnabled) {
cache.putDetails(source, manga.url, details)
}
return details.await()
}
@@ -133,7 +153,7 @@ class RemoteMangaRepository(
}
suspend fun find(manga: Manga): Manga? {
val list = getList(0, manga.title)
val list = getList(0, MangaListFilter.Search(manga.title))
return list.find { x -> x.id == manga.id }
}
@@ -147,6 +167,10 @@ class RemoteMangaRepository(
return parser.configKeyDomain.presetValues.toList()
}
fun isSlowdownEnabled(): Boolean {
return getConfig().isSlowdownEnabled
}
private fun getConfig() = parser.config as SourceSettings
private suspend fun <T> asyncSafe(block: suspend CoroutineScope.() -> T): SafeDeferred<T> {
@@ -166,7 +190,7 @@ class RemoteMangaRepository(
return emptyList()
}
val result = ArrayList<MangaPage>(size)
val set = HashSet<Long>(size)
val set = MutableLongSet(size)
for (page in this) {
if (set.add(page.id)) {
result.add(page)
@@ -203,6 +227,5 @@ class RemoteMangaRepository(
}
}
private fun Result<*>.isValidResult() = exceptionOrNull() !is ParseException
&& (getOrNull() as? Collection<*>)?.isEmpty() != true
private fun Result<*>.isValidResult() = isSuccess && (getOrNull() as? Collection<*>)?.isEmpty() != true
}

View File

@@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.ArraySet
import androidx.core.content.edit
import androidx.core.os.LocaleListCompat
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ApplicationContext
import org.json.JSONArray
@@ -22,11 +23,13 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.explore.data.SourcesSortOrder
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.find
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import java.io.File
import java.net.Proxy
import java.util.Locale
@@ -68,6 +71,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
}
}
val isNavLabelsVisible: Boolean
get() = prefs.getBoolean(KEY_NAV_LABELS, true)
var gridSize: Int
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
@@ -99,14 +105,24 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
}
}
val readerPageSwitch: Set<String>
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
var isReaderDoubleOnLandscape: Boolean
get() = prefs.getBoolean(KEY_READER_DOUBLE_PAGES, false)
set(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_PAGES, value) }
val isReaderVolumeButtonsEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_VOLUME_BUTTONS, false)
val isReaderZoomButtonsEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false)
val isReaderTapsAdaptive: Boolean
get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
val isReaderControlAlwaysLTR: Boolean
get() = prefs.getBoolean(KEY_READER_CONTROL_LTR, false)
val isReaderFullscreenEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_FULLSCREEN, true)
val isReaderOptimizationEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_OPTIMIZE, false)
var isTrafficWarningEnabled: Boolean
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
@@ -199,6 +215,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isUnstableUpdatesAllowed: Boolean
get() = prefs.getBoolean(KEY_UPDATES_UNSTABLE, false)
val defaultDetailsTab: Int
get() = prefs.getString(KEY_DETAILS_TAB, null)?.toIntOrNull()?.coerceIn(0, 1) ?: 0
val isContentPrefetchEnabled: Boolean
get() {
if (isBackgroundNetworkRestricted()) {
@@ -209,6 +228,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
return policy.isNetworkAllowed(connectivityManager)
}
var sourcesSortOrder: SourcesSortOrder
get() = prefs.getEnumValue(KEY_SOURCES_ORDER, SourcesSortOrder.MANUAL)
set(value) = prefs.edit { putEnumValue(KEY_SOURCES_ORDER, value) }
var isSourcesGridMode: Boolean
get() = prefs.getBoolean(KEY_SOURCES_GRID, false)
set(value) = prefs.edit { putBoolean(KEY_SOURCES_GRID, value) }
@@ -251,9 +274,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
}
}
val isDownloadsSlowdownEnabled: Boolean
get() = prefs.getBoolean(KEY_DOWNLOADS_SLOWDOWN, false)
val isDownloadsWiFiOnly: Boolean
get() = prefs.getBoolean(KEY_DOWNLOADS_WIFI, false)
@@ -288,6 +308,24 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isReaderKeepScreenOn: Boolean
get() = prefs.getBoolean(KEY_READER_SCREEN_ON, true)
var readerColorFilter: ReaderColorFilter?
get() = runCatching {
val brightness = prefs.getFloat(KEY_CF_BRIGHTNESS, ReaderColorFilter.EMPTY.brightness)
val contrast = prefs.getFloat(KEY_CF_CONTRAST, ReaderColorFilter.EMPTY.contrast)
val inverted = prefs.getBoolean(KEY_CF_INVERTED, ReaderColorFilter.EMPTY.isInverted)
val grayscale = prefs.getBoolean(KEY_CF_GRAYSCALE, ReaderColorFilter.EMPTY.isGrayscale)
ReaderColorFilter(brightness, contrast, inverted, grayscale).takeUnless { it.isEmpty }
}.getOrNull()
set(value) {
prefs.edit {
val cf = value ?: ReaderColorFilter.EMPTY
putFloat(KEY_CF_BRIGHTNESS, cf.brightness)
putFloat(KEY_CF_CONTRAST, cf.contrast)
putBoolean(KEY_CF_INVERTED, cf.isInverted)
putBoolean(KEY_CF_GRAYSCALE, cf.isGrayscale)
}
}
val isImagesProxyEnabled: Boolean
get() = prefs.getBoolean(KEY_IMAGES_PROXY, false)
@@ -320,15 +358,23 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
var historySortOrder: ListSortOrder
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.UPDATED)
get() = prefs.getEnumValue(KEY_HISTORY_ORDER, ListSortOrder.LAST_READ)
set(value) = prefs.edit { putEnumValue(KEY_HISTORY_ORDER, value) }
var allFavoritesSortOrder: ListSortOrder
get() = prefs.getEnumValue(KEY_FAVORITES_ORDER, ListSortOrder.NEWEST)
set(value) = prefs.edit { putEnumValue(KEY_FAVORITES_ORDER, value) }
val isRelatedMangaEnabled: Boolean
get() = prefs.getBoolean(KEY_RELATED_MANGA, true)
val isWebtoonZoomEnable: Boolean
get() = prefs.getBoolean(KEY_WEBTOON_ZOOM, true)
@get:FloatRange(from = 0.0, to = 0.5)
val defaultWebtoonZoomOut: Float
get() = prefs.getInt(KEY_WEBTOON_ZOOM_OUT, 0).coerceIn(0, 50) / 100f
@get:FloatRange(from = 0.0, to = 1.0)
var readerAutoscrollSpeed: Float
get() = prefs.getFloat(KEY_READER_AUTOSCROLL_SPEED, 0f)
@@ -354,6 +400,22 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val is32BitColorsEnabled: Boolean
get() = prefs.getBoolean(KEY_32BIT_COLOR, false)
val isPeriodicalBackupEnabled: Boolean
get() = prefs.getBoolean(KEY_BACKUP_PERIODICAL_ENABLED, false)
val periodicalBackupFrequency: Long
get() = prefs.getString(KEY_BACKUP_PERIODICAL_FREQUENCY, null)?.toLongOrNull() ?: 7L
var periodicalBackupOutput: Uri?
get() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()
set(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }
val isReadingTimeEstimationEnabled: Boolean
get() = prefs.getBoolean(KEY_READING_TIME, true)
val isPagesSavingAskEnabled: Boolean
get() = prefs.getBoolean(KEY_PAGES_SAVE_ASK, true)
fun isTipEnabled(tip: String): Boolean {
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
}
@@ -366,6 +428,15 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
prefs.edit { putStringSet(KEY_TIPS_CLOSED, closedTips + tip) }
}
fun getPagesSaveDir(context: Context): DocumentFile? =
prefs.getString(KEY_PAGES_SAVE_DIR, null)?.toUriOrNull()?.let {
DocumentFile.fromTreeUri(context, it)
}
fun setPagesSaveDir(uri: Uri?) {
prefs.edit { putString(KEY_PAGES_SAVE_DIR, uri?.toString()) }
}
fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
prefs.registerOnSharedPreferenceChangeListener(listener)
}
@@ -412,7 +483,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
companion object {
const val PAGE_SWITCH_TAPS = "taps"
const val PAGE_SWITCH_VOLUME_KEYS = "volume"
const val TRACK_HISTORY = "history"
@@ -435,8 +505,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_GRID_SIZE = "grid_size"
const val KEY_REMOTE_SOURCES = "remote_sources"
const val KEY_LOCAL_STORAGE = "local_storage"
const val KEY_READER_SWITCHERS = "reader_switchers"
const val KEY_READER_DOUBLE_PAGES = "reader_double_pages"
const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons"
const val KEY_READER_CONTROL_LTR = "reader_taps_ltr"
const val KEY_READER_FULLSCREEN = "reader_fullscreen"
const val KEY_READER_VOLUME_BUTTONS = "reader_volume_buttons"
const val KEY_TRACKER_ENABLED = "tracker_enabled"
const val KEY_TRACKER_WIFI_ONLY = "tracker_wifi"
const val KEY_TRACK_SOURCES = "track_sources"
@@ -458,6 +531,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_ZOOM_MODE = "zoom_mode"
const val KEY_BACKUP = "backup"
const val KEY_RESTORE = "restore"
const val KEY_BACKUP_PERIODICAL_ENABLED = "backup_periodic"
const val KEY_BACKUP_PERIODICAL_FREQUENCY = "backup_periodic_freq"
const val KEY_BACKUP_PERIODICAL_OUTPUT = "backup_periodic_output"
const val KEY_BACKUP_PERIODICAL_LAST = "backup_periodic_last"
const val KEY_HISTORY_GROUPING = "history_grouping"
const val KEY_READING_INDICATORS = "reading_indicators"
const val KEY_REVERSE_CHAPTERS = "reverse_chapters"
@@ -473,7 +550,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SHIKIMORI = "shikimori"
const val KEY_ANILIST = "anilist"
const val KEY_MAL = "mal"
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
const val KEY_KITSU = "kitsu"
const val KEY_DOWNLOADS_WIFI = "downloads_wifi"
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
const val KEY_DOH = "doh"
@@ -486,10 +563,13 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_READER_BACKGROUND = "reader_background"
const val KEY_READER_SCREEN_ON = "reader_screen_on"
const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
const val KEY_READER_TAP_ACTIONS = "reader_tap_actions"
const val KEY_READER_OPTIMIZE = "reader_optimize"
const val KEY_LOCAL_LIST_ORDER = "local_order"
const val KEY_HISTORY_ORDER = "history_order"
const val KEY_FAVORITES_ORDER = "fav_order"
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
const val KEY_WEBTOON_ZOOM_OUT = "webtoon_zoom_out"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"
const val KEY_LOGGING_ENABLED = "logging"
@@ -513,7 +593,19 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_DISABLE_NSFW = "no_nsfw"
const val KEY_RELATED_MANGA = "related_manga"
const val KEY_NAV_MAIN = "nav_main"
const val KEY_NAV_LABELS = "nav_labels"
const val KEY_32BIT_COLOR = "enhanced_colors"
const val KEY_SOURCES_ORDER = "sources_sort_order"
const val KEY_SOURCES_CATALOG = "sources_catalog"
const val KEY_CF_BRIGHTNESS = "cf_brightness"
const val KEY_CF_CONTRAST = "cf_contrast"
const val KEY_CF_INVERTED = "cf_inverted"
const val KEY_CF_GRAYSCALE = "cf_grayscale"
const val KEY_IGNORE_DOZE = "ignore_dose"
const val KEY_DETAILS_TAB = "details_tab"
const val KEY_READING_TIME = "reading_time"
const val KEY_PAGES_SAVE_DIR = "pages_dir"
const val KEY_PAGES_SAVE_ASK = "pages_dir_ask"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -4,13 +4,12 @@ import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.list.ui.model.ListModel
enum class NavItem(
@IdRes val id: Int,
@StringRes val title: Int,
@DrawableRes val icon: Int,
) : ListModel {
) {
HISTORY(R.id.nav_history, R.string.history, R.drawable.ic_history_selector),
FAVORITES(R.id.nav_favorites, R.string.favourites, R.drawable.ic_favourites_selector),
@@ -21,10 +20,6 @@ enum class NavItem(
BOOKMARKS(R.id.nav_bookmarks, R.string.bookmarks, R.drawable.ic_bookmark_selector),
;
override fun areItemsTheSame(other: ListModel): Boolean {
return other is NavItem && ordinal == other.ordinal
}
fun isAvailable(settings: AppSettings): Boolean = when (this) {
SUGGESTIONS -> settings.isSuggestionsEnabled
FEED -> settings.isTrackerEnabled

View File

@@ -4,7 +4,9 @@ enum class ReaderMode(val id: Int) {
STANDARD(1),
REVERSED(3),
WEBTOON(2);
VERTICAL(4),
WEBTOON(2),
;
companion object {

View File

@@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
private const val KEY_SORT_ORDER = "sort_order"
private const val KEY_SLOWDOWN = "slowdown"
class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {
@@ -20,6 +21,9 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
get() = prefs.getEnumValue(KEY_SORT_ORDER, SortOrder::class.java)
set(value) = prefs.edit { putEnumValue(KEY_SORT_ORDER, value) }
val isSlowdownEnabled: Boolean
get() = prefs.getBoolean(KEY_SLOWDOWN, false)
@Suppress("UNCHECKED_CAST")
override fun <T> get(key: ConfigKey<T>): T {
return when (key) {

View File

@@ -6,7 +6,6 @@ import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
@@ -96,11 +95,10 @@ abstract class BaseActivity<B : ViewBinding> :
insetsDelegate.onViewCreated(binding.root)
}
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
// TODO: navigateUpTo
true
} else super.onOptionsItemSelected(item)
override fun onSupportNavigateUp(): Boolean {
dispatchNavigateUp()
return true
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
@@ -151,6 +149,17 @@ abstract class BaseActivity<B : ViewBinding> :
window.statusBarColor = defaultStatusBarColor
}
protected open fun dispatchNavigateUp() {
val upIntent = parentActivityIntent
if (upIntent != null) {
if (!navigateUpTo(upIntent)) {
startActivity(upIntent)
}
} else {
finishAfterTransition()
}
}
private fun putDataToExtras(intent: Intent?) {
intent?.putExtra(EXTRA_DATA, intent.data)
}

View File

@@ -29,7 +29,6 @@ abstract class BaseFullscreenActivity<B : ViewBinding> :
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
// insetsControllerCompat.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
systemUiController.setSystemUiVisible(true)
}
}

View File

@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.FlowCollector
import org.koitharu.kotatsu.core.util.ContinuationResumeRunnable
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.coroutines.suspendCoroutine
@@ -35,4 +36,15 @@ open class BaseListAdapter<T : ListModel> : AsyncListDifferDelegationAdapter<T>(
fun removeListListener(listListener: ListListener<T>) {
differ.removeListListener(listListener)
}
fun findHeader(position: Int): ListHeader? {
val snapshot = items
for (i in (0..position).reversed()) {
val item = snapshot.getOrNull(i) ?: continue
if (item is ListHeader) {
return item
}
}
return null
}
}

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.core.ui
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.annotation.CallSuper
@@ -8,7 +10,9 @@ import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.core.ui.util.WindowInsetsDelegate
@@ -62,4 +66,12 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
protected fun setTitle(title: CharSequence?) {
(activity as? SettingsActivity)?.setSectionTitle(title)
}
protected fun startActivitySafe(intent: Intent) {
try {
startActivity(intent)
} catch (_: ActivityNotFoundException) {
Snackbar.make(listView, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
}
}
}

View File

@@ -0,0 +1,75 @@
package org.koitharu.kotatsu.core.ui
import androidx.recyclerview.widget.AsyncListDiffer.ListListener
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListModel
import java.util.Collections
import java.util.LinkedList
open class ReorderableListAdapter<T : ListModel> : ListDelegationAdapter<List<T>>(), FlowCollector<List<T>?> {
private val listListeners = LinkedList<ListListener<T>>()
override suspend fun emit(value: List<T>?) {
val oldList = items.orEmpty()
val newList = value.orEmpty()
val diffResult = withContext(Dispatchers.Default) {
val diffCallback = DiffCallback(oldList, newList)
DiffUtil.calculateDiff(diffCallback)
}
super.setItems(newList)
diffResult.dispatchUpdatesTo(this)
listListeners.forEach { it.onCurrentListChanged(oldList, newList) }
}
@Deprecated("Use emit() to dispatch list updates", level = DeprecationLevel.ERROR)
override fun setItems(items: List<T>?) {
super.setItems(items)
}
fun reorderItems(oldPos: Int, newPos: Int) {
Collections.swap(items ?: return, oldPos, newPos)
notifyItemMoved(oldPos, newPos)
}
fun addDelegate(type: ListItemType, delegate: AdapterDelegate<List<T>>): ReorderableListAdapter<T> {
delegatesManager.addDelegate(type.ordinal, delegate)
return this
}
fun addListListener(listListener: ListListener<T>) {
listListeners.add(listListener)
}
fun removeListListener(listListener: ListListener<T>) {
listListeners.remove(listListener)
}
protected class DiffCallback<T : ListModel>(
val oldList: List<T>,
val newList: List<T>,
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return newItem.areItemsTheSame(oldItem)
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return newItem == oldItem
}
}
}

View File

@@ -4,11 +4,11 @@ import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.text.method.LinkMovementMethod
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.text.HtmlCompat
import androidx.core.text.htmlEncode
import androidx.core.text.method.LinkMovementMethodCompat
import androidx.core.text.parseAsHtml
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -37,7 +37,7 @@ class ErrorDetailsDialog : AlertDialogFragment<DialogErrorDetailsBinding>() {
override fun onViewBindingCreated(binding: DialogErrorDetailsBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
with(binding.textViewMessage) {
movementMethod = LinkMovementMethod.getInstance()
movementMethod = LinkMovementMethodCompat.getInstance()
text = context.getString(
R.string.manga_error_description_pattern,
exception.message?.htmlEncode().orEmpty(),

View File

@@ -1,100 +0,0 @@
package org.koitharu.kotatsu.core.ui.drawable
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import androidx.annotation.ColorInt
import androidx.annotation.Px
import androidx.annotation.StyleRes
import androidx.core.graphics.withTranslation
import com.google.android.material.resources.TextAppearance
import com.google.android.material.resources.TextAppearanceFontCallback
import org.koitharu.kotatsu.core.util.ext.getThemeColor
class TextDrawable(
val text: CharSequence,
) : Drawable() {
private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
private var cachedLayout: StaticLayout? = null
@SuppressLint("RestrictedApi")
constructor(context: Context, text: CharSequence, @StyleRes textAppearanceId: Int) : this(text) {
val ta = TextAppearance(context, textAppearanceId)
paint.color = ta.textColor?.defaultColor ?: context.getThemeColor(android.R.attr.textColorPrimary, Color.BLACK)
paint.typeface = ta.fallbackFont
ta.getFontAsync(
context, paint,
object : TextAppearanceFontCallback() {
override fun onFontRetrieved(typeface: Typeface?, fontResolvedSynchronously: Boolean) = Unit
override fun onFontRetrievalFailed(reason: Int) = Unit
},
)
paint.letterSpacing = ta.letterSpacing
}
var alignment = Layout.Alignment.ALIGN_NORMAL
var lineSpacingMultiplier = 1f
@Px
var lineSpacingExtra = 0f
@get:ColorInt
var textColor: Int
get() = paint.color
set(@ColorInt value) {
paint.color = value
}
override fun draw(canvas: Canvas) {
val b = bounds
if (b.isEmpty) {
return
}
canvas.withTranslation(x = b.left.toFloat(), y = b.top.toFloat()) {
obtainLayout().draw(canvas)
}
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.setColorFilter(colorFilter)
}
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Deprecated in Java")
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
private fun obtainLayout(): StaticLayout {
val width = bounds.width()
cachedLayout?.let {
if (it.width == width) {
return it
}
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
StaticLayout.Builder.obtain(text, 0, text.length, paint, width)
.setAlignment(alignment)
.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
.setIncludePad(true)
.build()
} else {
@Suppress("DEPRECATION")
StaticLayout(text, paint, width, alignment, lineSpacingMultiplier, lineSpacingExtra, true)
}.also { cachedLayout = it }
}
}

View File

@@ -38,4 +38,12 @@ abstract class BoundsScrollListener(
firstVisibleItemPosition: Int,
visibleItemCount: Int
) = Unit
fun invalidate(recyclerView: RecyclerView) {
onScrolled(recyclerView, 0, 0)
}
fun postInvalidate(recyclerView: RecyclerView) = recyclerView.post {
invalidate(recyclerView)
}
}

View File

@@ -1,64 +0,0 @@
package org.koitharu.kotatsu.core.ui.list
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import androidx.core.os.BundleCompat
import androidx.core.view.doOnNextLayout
import androidx.recyclerview.widget.RecyclerView
import java.util.Collections
import java.util.WeakHashMap
class NestedScrollStateHandle(
savedInstanceState: Bundle?,
private val key: String,
) {
private val storage: SparseArray<Parcelable?> = savedInstanceState?.let {
BundleCompat.getSparseParcelableArray(it, key, Parcelable::class.java)
} ?: SparseArray<Parcelable?>()
private val controllers = Collections.newSetFromMap<Controller>(WeakHashMap())
fun attach(recycler: RecyclerView) = Controller(recycler).also(controllers::add)
fun onSaveInstanceState(outState: Bundle) {
controllers.forEach {
it.saveState()
}
outState.putSparseParcelableArray(key, storage)
}
inner class Controller(
private val recycler: RecyclerView
) {
private var lastPosition: Int = -1
fun onBind(position: Int) {
if (position != lastPosition) {
saveState()
lastPosition = position
storage[position]?.let {
restoreState(it)
}
}
}
fun onRecycled() {
saveState()
lastPosition = -1
}
fun saveState() {
if (lastPosition != -1) {
storage[lastPosition] = recycler.layoutManager?.onSaveInstanceState()
}
}
private fun restoreState(state: Parcelable) {
recycler.doOnNextLayout {
recycler.layoutManager?.onRestoreInstanceState(state)
}
}
}
}

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.core.ui.list
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
class RecyclerScrollKeeper(
private val rv: RecyclerView,
) : AdapterDataObserver() {
private val scrollUpRunnable = Runnable {
(rv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)
}
fun attach() {
rv.adapter?.registerAdapterDataObserver(this)
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
if (positionStart == 0 && isScrolledToTop()) {
postScrollUp()
}
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
if (toPosition == 0 && isScrolledToTop()) {
postScrollUp()
}
}
private fun postScrollUp() {
rv.postDelayed(scrollUpRunnable, 500L)
}
private fun isScrolledToTop(): Boolean {
return (rv.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() == 0
}
}

View File

@@ -1,237 +1,4 @@
package org.koitharu.kotatsu.core.ui.list
import android.app.Activity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.collection.ArrayMap
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
import kotlin.coroutines.EmptyCoroutineContext
private const val PROVIDER_NAME = "selection_decoration_sectioned"
class SectionedSelectionController<T : Any>(
private val activity: Activity,
private val owner: SavedStateRegistryOwner,
private val callback: Callback<T>,
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null
private var pendingData: MutableMap<String, Collection<Long>>? = null
private val decorations = ArrayMap<T, AbstractSelectionItemDecoration>()
val count: Int
get() = decorations.values.sumOf { it.checkedItemsCount }
init {
owner.lifecycle.addObserver(StateEventObserver())
}
fun snapshot(): Map<T, Set<Long>> {
return decorations.mapValues { it.value.checkedItemsIds.toSet() }
}
fun peekCheckedIds(): Map<T, Set<Long>> {
return decorations.mapValues { it.value.checkedItemsIds }
}
fun clear() {
decorations.values.forEach {
it.clearSelection()
}
notifySelectionChanged()
}
fun attachToRecyclerView(section: T, recyclerView: RecyclerView) {
val decoration = getDecoration(section)
val pendingIds = pendingData?.remove(section.toString())
if (!pendingIds.isNullOrEmpty()) {
decoration.checkAll(pendingIds)
startActionMode()
notifySelectionChanged()
}
var shouldAddDecoration = true
for (i in (0 until recyclerView.itemDecorationCount).reversed()) {
val decor = recyclerView.getItemDecorationAt(i)
if (decor === decoration) {
shouldAddDecoration = false
break
} else if (decor.javaClass == decoration.javaClass) {
recyclerView.removeItemDecorationAt(i)
}
}
if (shouldAddDecoration) {
recyclerView.addItemDecoration(decoration)
}
if (pendingData?.isEmpty() == true) {
pendingData = null
}
}
override fun saveState(): Bundle {
val bundle = Bundle(decorations.size)
for ((k, v) in decorations) {
bundle.putLongArray(k.toString(), v.checkedItemsIds.toLongArray())
}
return bundle
}
fun onItemClick(section: T, id: Long): Boolean {
val decoration = getDecoration(section)
if (isInSelectionMode()) {
decoration.toggleItemChecked(id)
if (isInSelectionMode()) {
actionMode?.invalidate()
} else {
actionMode?.finish()
}
notifySelectionChanged()
return true
}
return false
}
fun onItemLongClick(section: T, id: Long): Boolean {
val decoration = getDecoration(section)
startActionMode()
return actionMode?.also {
decoration.setItemIsChecked(id, true)
notifySelectionChanged()
} != null
}
fun getSectionCount(section: T): Int {
return decorations[section]?.checkedItemsCount ?: 0
}
fun addToSelection(section: T, ids: Collection<Long>): Boolean {
val decoration = getDecoration(section)
startActionMode()
return actionMode?.also {
decoration.checkAll(ids)
notifySelectionChanged()
} != null
}
fun clearSelection(section: T) {
decorations[section]?.clearSelection() ?: return
notifySelectionChanged()
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(this, mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onPrepareActionMode(this, mode, menu)
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return callback.onActionItemClicked(this, mode, item)
}
override fun onDestroyActionMode(mode: ActionMode) {
callback.onDestroyActionMode(this, mode)
clear()
actionMode = null
}
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
}
private fun isInSelectionMode(): Boolean {
return decorations.values.any { x -> x.checkedItemsCount > 0 }
}
private fun notifySelectionChanged() {
val count = this.count
callback.onSelectionChanged(this, count)
if (count == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
}
private fun restoreState(ids: MutableMap<String, Collection<Long>>) {
if (ids.isEmpty() || isInSelectionMode()) {
return
}
for ((k, v) in decorations) {
val items = ids.remove(k.toString())
if (!items.isNullOrEmpty()) {
v.checkAll(items)
}
}
pendingData = ids
if (isInSelectionMode()) {
startActionMode()
notifySelectionChanged()
}
}
private fun getDecoration(section: T): AbstractSelectionItemDecoration {
return decorations.getOrPut(section) {
callback.onCreateItemDecoration(this, section)
}
}
interface Callback<T : Any> {
fun onSelectionChanged(controller: SectionedSelectionController<T>, count: Int)
fun onCreateActionMode(controller: SectionedSelectionController<T>, mode: ActionMode, menu: Menu): Boolean
fun onPrepareActionMode(controller: SectionedSelectionController<T>, mode: ActionMode, menu: Menu): Boolean {
mode.title = controller.count.toString()
return true
}
fun onDestroyActionMode(controller: SectionedSelectionController<T>, mode: ActionMode) = Unit
fun onActionItemClicked(
controller: SectionedSelectionController<T>,
mode: ActionMode,
item: MenuItem,
): Boolean
fun onCreateItemDecoration(
controller: SectionedSelectionController<T>,
section: T,
): AbstractSelectionItemDecoration
}
private inner class StateEventObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_CREATE) {
val registry = owner.savedStateRegistry
registry.registerSavedStateProvider(PROVIDER_NAME, this@SectionedSelectionController)
val state = registry.consumeRestoredStateForKey(PROVIDER_NAME)
if (state != null) {
Dispatchers.Main.dispatch(EmptyCoroutineContext) { // == Handler.post
if (source.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
restoreState(
state.keySet()
.associateWithTo(HashMap()) { state.getLongArray(it)?.toList().orEmpty() },
)
}
}
}
}
}
}
}

View File

@@ -91,7 +91,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {
canvas.restoreToCount(checkpoint)
}
protected open fun getItemId(parent: RecyclerView, child: View) = parent.getChildItemId(child)
abstract fun getItemId(parent: RecyclerView, child: View): Long
protected open fun onDrawBackground(
canvas: Canvas,

View File

@@ -4,8 +4,10 @@ import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import androidx.annotation.AttrRes
import androidx.core.view.ancestors
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import org.koitharu.kotatsu.R
class FastScrollRecyclerView @JvmOverloads constructor(
@@ -15,6 +17,16 @@ class FastScrollRecyclerView @JvmOverloads constructor(
) : RecyclerView(context, attrs, defStyleAttr) {
val fastScroller = FastScroller(context, attrs)
var isVP2BugWorkaroundEnabled = false
set(value) {
field = value
if (value && isAttachedToWindow) {
checkIfInVP2()
} else if (!value) {
applyVP2Workaround = false
}
}
private var applyVP2Workaround = false
var isFastScrollerEnabled: Boolean = true
set(value) {
@@ -43,10 +55,29 @@ class FastScrollRecyclerView @JvmOverloads constructor(
override fun onAttachedToWindow() {
super.onAttachedToWindow()
fastScroller.attachRecyclerView(this)
if (isVP2BugWorkaroundEnabled) {
checkIfInVP2()
}
}
override fun onDetachedFromWindow() {
fastScroller.detachRecyclerView()
super.onDetachedFromWindow()
applyVP2Workaround = false
}
override fun isLayoutRequested(): Boolean {
return if (applyVP2Workaround) false else super.isLayoutRequested()
}
override fun requestLayout() {
super.requestLayout()
if (applyVP2Workaround && parent?.isLayoutRequested == true) {
parent?.requestLayout()
}
}
private fun checkIfInVP2() {
applyVP2Workaround = ancestors.any { it is ViewPager2 } == true
}
}

View File

@@ -12,7 +12,12 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.*
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.DimenRes
import androidx.annotation.DrawableRes
import androidx.annotation.Px
import androidx.annotation.StyleableRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.coordinatorlayout.widget.CoordinatorLayout
@@ -131,19 +136,19 @@ class FastScroller @JvmOverloads constructor(
var showTrack = false
context.withStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr) {
bubbleColor = getColor(R.styleable.FastScroller_bubbleColor, bubbleColor)
handleColor = getColor(R.styleable.FastScroller_thumbColor, handleColor)
trackColor = getColor(R.styleable.FastScroller_trackColor, trackColor)
textColor = getColor(R.styleable.FastScroller_bubbleTextColor, textColor)
hideScrollbar = getBoolean(R.styleable.FastScroller_hideScrollbar, hideScrollbar)
showBubble = getBoolean(R.styleable.FastScroller_showBubble, showBubble)
showBubbleAlways = getBoolean(R.styleable.FastScroller_showBubbleAlways, showBubbleAlways)
showTrack = getBoolean(R.styleable.FastScroller_showTrack, showTrack)
bubbleSize = getBubbleSize(R.styleable.FastScroller_bubbleSize, BubbleSize.NORMAL)
val textSize = getDimension(R.styleable.FastScroller_bubbleTextSize, bubbleSize.textSize)
context.withStyledAttributes(attrs, R.styleable.FastScrollRecyclerView, defStyleAttr) {
bubbleColor = getColor(R.styleable.FastScrollRecyclerView_bubbleColor, bubbleColor)
handleColor = getColor(R.styleable.FastScrollRecyclerView_thumbColor, handleColor)
trackColor = getColor(R.styleable.FastScrollRecyclerView_trackColor, trackColor)
textColor = getColor(R.styleable.FastScrollRecyclerView_bubbleTextColor, textColor)
hideScrollbar = getBoolean(R.styleable.FastScrollRecyclerView_hideScrollbar, hideScrollbar)
showBubble = getBoolean(R.styleable.FastScrollRecyclerView_showBubble, showBubble)
showBubbleAlways = getBoolean(R.styleable.FastScrollRecyclerView_showBubbleAlways, showBubbleAlways)
showTrack = getBoolean(R.styleable.FastScrollRecyclerView_showTrack, showTrack)
bubbleSize = getBubbleSize(R.styleable.FastScrollRecyclerView_bubbleSize, BubbleSize.NORMAL)
val textSize = getDimension(R.styleable.FastScrollRecyclerView_bubbleTextSize, bubbleSize.textSize)
binding.bubble.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
offset = getDimensionPixelOffset(R.styleable.FastScroller_scrollerOffset, offset)
offset = getDimensionPixelOffset(R.styleable.FastScrollRecyclerView_scrollerOffset, offset)
}
setTrackColor(trackColor)

View File

@@ -0,0 +1,88 @@
package org.koitharu.kotatsu.core.ui.list.lifecycle
import android.view.View
import androidx.annotation.CallSuper
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView
abstract class LifecycleAwareViewHolder(
itemView: View,
private val parentLifecycleOwner: LifecycleOwner,
) : RecyclerView.ViewHolder(itemView), LifecycleOwner {
@Suppress("LeakingThis")
final override val lifecycle = LifecycleRegistry(this)
private var isCurrent = false
init {
parentLifecycleOwner.lifecycle.addObserver(ParentLifecycleObserver())
if (parentLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)) {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
}
fun setIsCurrent(value: Boolean) {
isCurrent = value
dispatchResumed()
}
@CallSuper
open fun onStart() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
@CallSuper
open fun onResume() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
@CallSuper
open fun onPause() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
@CallSuper
open fun onStop() = lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun dispatchResumed() {
val isParentResumed = parentLifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
if (isCurrent && isParentResumed) {
if (!isResumed()) {
onResume()
}
} else {
if (isResumed()) {
onPause()
}
}
}
protected fun isResumed(): Boolean {
return lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
}
private inner class ParentLifecycleObserver : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun onStart(owner: LifecycleOwner) {
onStart()
}
override fun onResume(owner: LifecycleOwner) {
dispatchResumed()
}
override fun onPause(owner: LifecycleOwner) {
dispatchResumed()
}
override fun onStop(owner: LifecycleOwner) {
onStop()
}
override fun onDestroy(owner: LifecycleOwner) {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
owner.lifecycle.removeObserver(this)
}
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.core.ui.list.lifecycle
import androidx.core.view.children
import androidx.viewpager2.widget.ViewPager2
import org.koitharu.kotatsu.core.util.ext.recyclerView
class PagerLifecycleDispatcher(
private val pager: ViewPager2,
) : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
val rv = pager.recyclerView ?: return
for (child in rv.children) {
val wh = rv.getChildViewHolder(child) ?: continue
(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition == position)
}
}
fun invalidate() {
onPageSelected(pager.currentItem)
}
}

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.core.ui.list.lifecycle
import androidx.core.view.children
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
class RecyclerViewLifecycleDispatcher : RecyclerView.OnScrollListener() {
private var prevFirst = NO_POSITION
private var prevLast = NO_POSITION
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
invalidate(recyclerView)
}
fun invalidate(recyclerView: RecyclerView) {
val lm = recyclerView.layoutManager as? LinearLayoutManager ?: return
val first = lm.findFirstVisibleItemPosition()
val last = lm.findLastVisibleItemPosition()
if (first == prevFirst && last == prevLast) {
return
}
prevFirst = first
prevLast = last
if (first == NO_POSITION || last == NO_POSITION) {
return
}
for (child in recyclerView.children) {
val wh = recyclerView.getChildViewHolder(child) ?: continue
(wh as? LifecycleAwareViewHolder)?.setIsCurrent(wh.absoluteAdapterPosition in first..last)
}
}
}

View File

@@ -2,9 +2,8 @@ package org.koitharu.kotatsu.core.ui.model
import android.content.res.Resources
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.daysDiff
import org.koitharu.kotatsu.core.util.ext.format
import java.util.Date
import java.time.LocalDate
import java.time.format.DateTimeFormatter
sealed class DateTimeAgo {
@@ -74,28 +73,22 @@ sealed class DateTimeAgo {
}
}
class Absolute(private val date: Date) : DateTimeAgo() {
private val day = date.daysDiff(0)
data class Absolute(private val date: LocalDate) : DateTimeAgo() {
override fun format(resources: Resources): String {
return date.format("d MMMM")
return if (date == EPOCH_DATE) {
resources.getString(R.string.unknown)
} else {
date.format(formatter)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
override fun toString() = "abs_${date.toEpochDay()}"
other as Absolute
return day == other.day
companion object {
// TODO: Use Java 9's LocalDate.EPOCH.
private val EPOCH_DATE = LocalDate.of(1970, 1, 1)
private val formatter = DateTimeFormatter.ofPattern("d MMMM")
}
override fun hashCode(): Int {
return day
}
override fun toString() = "abs_$day"
}
object LongAgo : DateTimeAgo() {

View File

@@ -12,4 +12,5 @@ val SortOrder.titleRes: Int
SortOrder.RATING -> R.string.by_rating
SortOrder.NEWEST -> R.string.newest
SortOrder.ALPHABETICAL -> R.string.by_name
SortOrder.ALPHABETICAL_DESC -> R.string.by_name_reverse
}

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.core.ui.util
import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
class PopupMenuMediator(
private val provider: MenuProvider,
) : View.OnLongClickListener, PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
override fun onLongClick(v: View): Boolean {
val menu = PopupMenu(v.context, v)
provider.onCreateMenu(menu.menu, menu.menuInflater)
provider.onPrepareMenu(menu.menu)
if (!menu.menu.hasVisibleItems()) {
return false
}
menu.setOnMenuItemClickListener(this)
menu.setOnDismissListener(this)
menu.show()
return true
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return provider.onMenuItemSelected(item)
}
override fun onDismiss(menu: PopupMenu) {
provider.onMenuClosed(menu.menu)
}
fun attach(view: View) {
view.setOnLongClickListener(this)
view.setOnContextClickListenerCompat(this)
}
}

View File

@@ -7,12 +7,16 @@ import org.koitharu.kotatsu.R
class ReversibleActionObserver(
private val snackbarHost: View,
private val snackbarAnchor: View? = null,
) : FlowCollector<ReversibleAction> {
override suspend fun emit(value: ReversibleAction) {
val handle = value.handle
val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG
val snackbar = Snackbar.make(snackbarHost, value.stringResId, length)
if (snackbarAnchor?.isShown == true) {
snackbar.anchorView = snackbarAnchor
}
if (handle != null) {
snackbar.setAction(R.string.undo) { handle.reverseAsync() }
}

View File

@@ -70,7 +70,7 @@ class WindowInsetsDelegate : OnApplyWindowInsetsListener, View.OnLayoutChangeLis
lastInsets = null
}
interface WindowInsetsListener {
fun interface WindowInsetsListener {
fun onWindowInsetsChanged(insets: Insets)
}

View File

@@ -1,21 +1,16 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.View.OnClickListener
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import androidx.core.content.res.getColorStateListOrThrow
import androidx.core.view.children
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.castOrNull
import com.google.android.material.R as materialR
class ChipsView @JvmOverloads constructor(
context: Context,
@@ -31,9 +26,7 @@ class ChipsView @JvmOverloads constructor(
private val chipOnCloseListener = OnClickListener {
onChipCloseClickListener?.onChipCloseClick(it as Chip, it.tag)
}
private val defaultChipStrokeColor: ColorStateList
private val defaultChipTextColor: ColorStateList
private val defaultChipIconTint: ColorStateList
private val chipStyle: Int
var onChipClickListener: OnChipClickListener? = null
set(value) {
field = value
@@ -48,12 +41,17 @@ class ChipsView @JvmOverloads constructor(
}
init {
@SuppressLint("CustomViewStyleable")
val a = context.obtainStyledAttributes(null, materialR.styleable.Chip, 0, R.style.Widget_Kotatsu_Chip)
defaultChipStrokeColor = a.getColorStateListOrThrow(materialR.styleable.Chip_chipStrokeColor)
defaultChipTextColor = a.getColorStateListOrThrow(materialR.styleable.Chip_android_textColor)
defaultChipIconTint = a.getColorStateListOrThrow(materialR.styleable.Chip_chipIconTint)
a.recycle()
val ta = context.obtainStyledAttributes(attrs, R.styleable.ChipsView, defStyleAttr, 0)
chipStyle = ta.getResourceId(R.styleable.ChipsView_chipStyle, R.style.Widget_Kotatsu_Chip)
ta.recycle()
if (isInEditMode) {
setChips(
List(5) {
ChipModel(0, "Chip $it", 0, isCheckable = false, isChecked = false)
},
)
}
}
override fun requestLayout() {
@@ -91,15 +89,6 @@ class ChipsView @JvmOverloads constructor(
private fun bindChip(chip: Chip, model: ChipModel) {
chip.text = model.title
val tint = if (model.tint == 0) {
null
} else {
ContextCompat.getColorStateList(context, model.tint)
}
chip.chipIconTint = tint ?: defaultChipIconTint
chip.checkedIconTint = tint ?: defaultChipIconTint
chip.chipStrokeColor = tint ?: defaultChipStrokeColor
chip.setTextColor(tint ?: defaultChipTextColor)
chip.isClickable = onChipClickListener != null || model.isCheckable
chip.isCheckable = model.isCheckable
if (model.icon == 0) {
@@ -115,12 +104,10 @@ class ChipsView @JvmOverloads constructor(
private fun addChip(): Chip {
val chip = Chip(context)
val drawable = ChipDrawable.createFromAttributes(context, null, 0, R.style.Widget_Kotatsu_Chip)
val drawable = ChipDrawable.createFromAttributes(context, null, 0, chipStyle)
chip.setChipDrawable(drawable)
chip.isCheckedIconVisible = true
chip.isChipIconVisible = false
chip.setCheckedIconResource(R.drawable.ic_check)
chip.checkedIconTint = defaultChipIconTint
chip.isCloseIconVisible = onChipCloseClickListener != null
chip.setOnCloseIconClickListener(chipOnCloseListener)
chip.setEnsureMinTouchTargetSize(false)

View File

@@ -0,0 +1,74 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.util.AttributeSet
import androidx.collection.MutableScatterMap
import com.google.android.material.slider.Slider
import kotlin.math.cbrt
import kotlin.math.pow
class CubicSlider @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : Slider(context, attrs) {
private val changeListeners = MutableScatterMap<OnChangeListener, OnChangeListenerMapper>(1)
override fun setValue(value: Float) {
super.setValue(value.unmap())
}
override fun getValue(): Float {
return super.getValue().map()
}
override fun getValueFrom(): Float {
return super.getValueFrom().map()
}
override fun setValueFrom(valueFrom: Float) {
super.setValueFrom(valueFrom.unmap())
}
override fun getValueTo(): Float {
return super.getValueTo().map()
}
override fun setValueTo(valueTo: Float) {
super.setValueTo(valueTo.unmap())
}
override fun addOnChangeListener(listener: OnChangeListener) {
val mapper = OnChangeListenerMapper(listener)
super.addOnChangeListener(mapper)
changeListeners[listener] = mapper
}
override fun removeOnChangeListener(listener: OnChangeListener) {
changeListeners.remove(listener)?.let {
super.removeOnChangeListener(it)
}
}
override fun clearOnChangeListeners() {
super.clearOnChangeListeners()
changeListeners.clear()
}
private fun Float.map(): Float {
return this.pow(3)
}
private fun Float.unmap(): Float {
return cbrt(this)
}
private inner class OnChangeListenerMapper(
private val delegate: OnChangeListener,
) : OnChangeListener {
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
delegate.onValueChange(slider, value.map(), fromUser)
}
}
}

View File

@@ -0,0 +1,44 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.core.content.withStyledAttributes
import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.R
class NestedRecyclerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : RecyclerView(context, attrs) {
private var maxHeight: Int = 0
init {
context.withStyledAttributes(attrs, R.styleable.NestedRecyclerView) {
maxHeight = getDimensionPixelSize(R.styleable.NestedRecyclerView_maxHeight, maxHeight)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(e: MotionEvent?): Boolean {
if (e?.actionMasked == MotionEvent.ACTION_UP) {
requestDisallowInterceptTouchEvent(false)
} else {
requestDisallowInterceptTouchEvent(true)
}
return super.onTouchEvent(e)
}
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
super.onMeasure(
widthSpec,
if (maxHeight == 0) {
heightSpec
} else {
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
},
)
}
}

View File

@@ -11,6 +11,7 @@ import android.view.View
import android.view.ViewOutlineProvider
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.collection.MutableFloatList
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
@@ -25,7 +26,7 @@ class SegmentedBarView @JvmOverloads constructor(
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val segmentsData = ArrayList<Segment>()
private val segmentsSizes = ArrayList<Float>()
private val segmentsSizes = MutableFloatList()
private var cornerSize = 0f
private var scaleFactor = 1f
private var scaleAnimator: ValueAnimator? = null

View File

@@ -22,7 +22,6 @@ import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ViewTipBinding
import com.google.android.material.R as materialR
class TipView @JvmOverloads constructor(
context: Context,

View File

@@ -9,10 +9,8 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks
import org.acra.ACRA
import org.koitharu.kotatsu.core.ui.DefaultActivityLifecycleCallbacks
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.time.LocalTime
import java.time.temporal.ChronoUnit
import java.util.WeakHashMap
import javax.inject.Inject
import javax.inject.Singleton
@@ -20,7 +18,6 @@ import javax.inject.Singleton
@Singleton
class AcraScreenLogger @Inject constructor() : FragmentLifecycleCallbacks(), DefaultActivityLifecycleCallbacks {
private val timeFormat = SimpleDateFormat.getTimeInstance(DateFormat.DEFAULT, Locale.ROOT)
private val keys = WeakHashMap<Any, String>()
override fun onFragmentAttached(fm: FragmentManager, f: Fragment, context: Context) {
@@ -47,11 +44,10 @@ class AcraScreenLogger @Inject constructor() : FragmentLifecycleCallbacks(), Def
}
private fun Any.key() = keys.getOrPut(this) {
"${time()}: ${javaClass.simpleName}"
val time = LocalTime.now().truncatedTo(ChronoUnit.SECONDS)
"$time: ${javaClass.simpleName}"
}
private fun time() = timeFormat.format(Date())
@Suppress("DEPRECATION")
private fun Bundle?.contentToString() = this?.keySet()?.joinToString { k ->
val v = get(k)

View File

@@ -26,7 +26,7 @@ class CompositeMutex<T : Any> : Set<T> {
}
override fun isEmpty(): Boolean {
return state.isEmpty
return state.isEmpty()
}
override fun iterator(): Iterator<T> {

View File

@@ -19,7 +19,7 @@ class CompositeMutex2<T : Any> : Set<T> {
}
override fun isEmpty(): Boolean {
return delegates.isEmpty
return delegates.isEmpty()
}
override fun iterator(): Iterator<T> {

View File

@@ -1,70 +0,0 @@
package org.koitharu.kotatsu.core.util
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import kotlin.math.roundToInt
class GridTouchHelper(
context: Context,
private val listener: OnGridTouchListener,
) : GestureDetector.SimpleOnGestureListener() {
private val detector = GestureDetector(context, this)
private val width = context.resources.displayMetrics.widthPixels
private val height = context.resources.displayMetrics.heightPixels
private var isDispatching = false
init {
detector.setIsLongpressEnabled(true)
detector.setOnDoubleTapListener(this)
}
fun dispatchTouchEvent(event: MotionEvent) {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
isDispatching = listener.onProcessTouch(event.rawX.toInt(), event.rawY.toInt())
}
detector.onTouchEvent(event)
}
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
if (!isDispatching) {
return true
}
val xIndex = (event.rawX * 2f / width).roundToInt()
val yIndex = (event.rawY * 2f / height).roundToInt()
listener.onGridTouch(
when (xIndex) {
0 -> AREA_LEFT
1 -> {
when (yIndex) {
0 -> AREA_TOP
1 -> AREA_CENTER
2 -> AREA_BOTTOM
else -> return false
}
}
2 -> AREA_RIGHT
else -> return false
},
)
return true
}
companion object {
const val AREA_CENTER = 1
const val AREA_LEFT = 2
const val AREA_RIGHT = 3
const val AREA_TOP = 4
const val AREA_BOTTOM = 5
}
interface OnGridTouchListener {
fun onGridTouch(area: Int)
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.core.util
import androidx.core.os.LocaleListCompat
import org.koitharu.kotatsu.core.util.ext.map
import java.util.Locale
class LocaleComparator : Comparator<Locale> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()//LocaleManagerCompat.getSystemLocales(context)
.map { it.language }
.distinct()
override fun compare(a: Locale, b: Locale): Int {
val indexA = deviceLocales.indexOf(a.language)
val indexB = deviceLocales.indexOf(b.language)
return when {
indexA < 0 && indexB < 0 -> compareValues(a.language, b.language)
indexA < 0 -> 1
indexB < 0 -> -1
else -> compareValues(indexA, indexB)
}
}
}

View File

@@ -6,13 +6,16 @@ import android.content.res.Configuration
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
import dagger.hilt.android.scopes.ActivityScoped
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
class ScreenOrientationHelper(private val activity: Activity) {
@ActivityScoped
class ScreenOrientationHelper @Inject constructor(private val activity: Activity) {
val isAutoRotationEnabled: Boolean
get() = Settings.System.getInt(
@@ -31,9 +34,15 @@ class ScreenOrientationHelper(private val activity: Activity) {
}
}
fun toggleOrientation() {
isLandscape = !isLandscape
}
var isLocked: Boolean
get() = activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED
set(value) {
activity.requestedOrientation = if (value) {
ActivityInfo.SCREEN_ORIENTATION_LOCKED
} else {
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
fun observeAutoOrientation() = callbackFlow {
val observer = object : ContentObserver(Handler(activity.mainLooper)) {

View File

@@ -21,7 +21,11 @@ class ViewBadge(
get() = badgeDrawable?.number ?: 0
set(value) {
val badge = badgeDrawable ?: initBadge()
badge.number = value
if (maxCharacterCount != 0) {
badge.number = value
} else {
badge.clearNumber()
}
badge.isVisible = value > 0
}
@@ -51,7 +55,13 @@ class ViewBadge(
fun setMaxCharacterCount(value: Int) {
maxCharacterCount = value
badgeDrawable?.maxCharacterCount = value
badgeDrawable?.let {
if (value == 0) {
it.clearNumber()
} else {
it.maxCharacterCount = value
}
}
}
private fun initBadge(): BadgeDrawable {

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.core.util
import android.annotation.SuppressLint
import androidx.lifecycle.asFlow
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
@@ -27,8 +26,7 @@ class WorkServiceStopHelper(
fun setup() {
processLifecycleScope.launch(Dispatchers.Default) {
workManagerProvider.get()
.getWorkInfosLiveData(WorkQuery.fromStates(WorkInfo.State.RUNNING))
.asFlow()
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
.map { it.isEmpty() }
.distinctUntilChanged()
.collectLatest {

View File

@@ -17,6 +17,7 @@ import android.content.SyncResult
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.database.SQLException
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Build
@@ -29,6 +30,7 @@ import android.view.Window
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes
import androidx.annotation.WorkerThread
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
@@ -37,6 +39,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.work.CoroutineWorker
import com.google.android.material.elevation.ElevationOverlayProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
@@ -45,7 +48,9 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible
import okio.IOException
import okio.use
import org.json.JSONException
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.BuildConfig
@@ -53,6 +58,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.File
import kotlin.math.roundToLong
val Context.activityManager: ActivityManager?
@@ -83,7 +89,7 @@ fun <I> ActivityResultLauncher<I>.tryLaunch(
e.printStackTraceDebug()
}.isSuccess
fun SharedPreferences.observe() = callbackFlow<String?> {
fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
trySendBlocking(key)
}
@@ -130,7 +136,7 @@ fun Window.setNavigationBarTransparentCompat(context: Context, elevation: Float,
} else {
// Set navbar scrim 70% of navigationBarColor
ElevationOverlayProvider(context).compositeOverlayIfNeeded(
context.getThemeColor(android.R.attr.navigationBarColor, alphaFactor),
context.getThemeColor(R.attr.m3ColorBottomMenuBackground, alphaFactor),
elevation,
)
}
@@ -230,3 +236,18 @@ fun Context.checkNotificationPermission(): Boolean = if (Build.VERSION.SDK_INT >
} else {
NotificationManagerCompat.from(this).areNotificationsEnabled()
}
@WorkerThread
suspend fun Bitmap.compressToPNG(output: File) = runInterruptible(Dispatchers.IO) {
output.outputStream().use { os ->
if (!compress(Bitmap.CompressFormat.PNG, 100, os)) {
throw IOException("Failed to encode bitmap into PNG format")
}
}
}
fun Context.ensureRamAtLeast(requiredSize: Long) {
if (ramAvailable < requiredSize) {
throw IllegalStateException("Not enough free memory")
}
}

View File

@@ -24,11 +24,15 @@ inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String)
}
inline fun <reified T : Serializable> Intent.getSerializableExtraCompat(key: String): T? {
return getSerializableExtra(key) as T?
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
getSerializableExtra(key, T::class.java)
} else {
getSerializableExtra(key) as T?
}
}
inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
getSerializable(key, T::class.java)
} else {
getSerializable(key) as T?

View File

@@ -23,9 +23,9 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
return null
}
}
disposeImageRequest()
// disposeImageRequest()
return ImageRequest.Builder(context)
.data(data)
.data(data?.takeUnless { it == "" })
.lifecycle(lifecycleOwner)
.crossfade(context)
.target(this)

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.util.ext
import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import org.koitharu.kotatsu.BuildConfig
import java.util.Collections
import java.util.EnumSet
@@ -57,3 +58,13 @@ inline fun <reified E : Enum<E>> Collection<E>.toEnumSet(): EnumSet<E> = if (isE
}
fun <E : Enum<E>> Collection<E>.sortedByOrdinal() = sortedBy { it.ordinal }
fun <T> Iterable<T>.sortedWithSafe(comparator: Comparator<in T>): List<T> = try {
sortedWith(comparator)
} catch (e: IllegalArgumentException) {
if (BuildConfig.DEBUG) {
throw e
} else {
toList()
}
}

View File

@@ -50,7 +50,7 @@ private fun getVolumePathBeforeAndroid11(volumeId: String, context: Context): St
val length = ArrayReflect.getLength(checkNotNull(result))
(0 until length).firstNotNullOfOrNull { i ->
val storageVolumeElement = ArrayReflect.get(result, i)
val uuid = getUuid.invoke(storageVolumeElement) as String
val uuid = getUuid.invoke(storageVolumeElement) as String?
val primary = isPrimary.invoke(storageVolumeElement) as Boolean
when {
primary && volumeId == PRIMARY_VOLUME_NAME -> getPath.invoke(storageVolumeElement) as String

View File

@@ -1,30 +1,32 @@
package org.koitharu.kotatsu.core.util.ext
import android.annotation.SuppressLint
import android.text.format.DateUtils
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.temporal.ChronoUnit
@SuppressLint("SimpleDateFormat")
fun Date.format(pattern: String): String = SimpleDateFormat(pattern).format(this)
fun calculateTimeAgo(instant: Instant, showMonths: Boolean = false): DateTimeAgo {
// TODO: Use Java 9's LocalDate.ofInstant().
val localDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate()
val now = LocalDate.now()
val diffDays = localDate.until(now, ChronoUnit.DAYS)
fun Date.formatRelative(minResolution: Long): CharSequence = DateUtils.getRelativeTimeSpanString(
time, System.currentTimeMillis(), minResolution,
)
fun Date.daysDiff(other: Long): Int {
val thisDay = time / TimeUnit.DAYS.toMillis(1L)
val otherDay = other / TimeUnit.DAYS.toMillis(1L)
return (thisDay - otherDay).toInt()
}
fun Date.startOfDay(): Long {
val calendar = Calendar.getInstance()
calendar.time = this
calendar[Calendar.HOUR_OF_DAY] = 0
calendar[Calendar.MINUTE] = 0
calendar[Calendar.SECOND] = 0
calendar[Calendar.MILLISECOND] = 0
return calendar.timeInMillis
return when {
diffDays == 0L -> {
if (instant.until(Instant.now(), ChronoUnit.MINUTES) < 3) DateTimeAgo.JustNow
else DateTimeAgo.Today
}
diffDays == 1L -> DateTimeAgo.Yesterday
diffDays < 6 -> DateTimeAgo.DaysAgo(diffDays.toInt())
else -> {
val diffMonths = localDate.until(now, ChronoUnit.MONTHS)
if (showMonths && diffMonths <= 6) {
DateTimeAgo.MonthsAgo(diffMonths.toInt())
} else {
DateTimeAgo.Absolute(localDate)
}
}
}
}

View File

@@ -7,7 +7,6 @@ import android.os.Build
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.OpenableColumns
import androidx.annotation.WorkerThread
import androidx.core.database.getStringOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
@@ -19,7 +18,9 @@ import java.io.FileFilter
import java.nio.file.attribute.BasicFileAttributes
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.readAttributes
import kotlin.io.path.walk
fun File.subdir(name: String) = File(this, name).also {
if (!it.exists()) it.mkdirs()
@@ -49,7 +50,7 @@ fun File.getStorageName(context: Context): String = runCatching {
}
}.getOrNull() ?: context.getString(R.string.other_storage)
fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null
fun Uri.toFileOrNull() = if (scheme == URI_SCHEME_FILE) path?.let(::File) else null
suspend fun File.deleteAwait() = withContext(Dispatchers.IO) {
delete() || deleteRecursively()
@@ -71,31 +72,7 @@ fun ContentResolver.resolveName(uri: Uri): String? {
}
suspend fun File.computeSize(): Long = runInterruptible(Dispatchers.IO) {
computeSizeInternal(this)
}
@WorkerThread
private fun computeSizeInternal(file: File): Long {
return if (file.isDirectory) {
file.children().sumOf { computeSizeInternal(it) }
} else {
file.length()
}
}
fun File.listFilesRecursive(filter: FileFilter? = null): Sequence<File> = sequence {
listFilesRecursiveImpl(this@listFilesRecursive, filter)
}
private suspend fun SequenceScope<File>.listFilesRecursiveImpl(root: File, filter: FileFilter?) {
val ss = root.children()
for (f in ss) {
if (f.isDirectory) {
listFilesRecursiveImpl(f, filter)
} else if (filter == null || filter.accept(f)) {
yield(f)
}
}
walkCompat().sumOf { it.length() }
}
fun File.children() = FileSequence(this)
@@ -108,3 +85,12 @@ val File.creationTime
} else {
lastModified()
}
@OptIn(ExperimentalPathApi::class)
fun File.walkCompat() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Use lazy loading on Android 8.0 and later
toPath().walk().map { it.toFile() }
} else {
// Directories are excluded by default in Path.walk(), so do it here as well
walk().filter { it.isFile }
}

View File

@@ -11,8 +11,10 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transform
import kotlinx.coroutines.flow.transformLatest
import org.koitharu.kotatsu.R
import java.util.concurrent.atomic.AtomicInteger
fun <T> Flow<T>.onFirst(action: suspend (T) -> Unit): Flow<T> {
var isFirstCall = true
@@ -37,6 +39,14 @@ fun <T> Flow<T>.onEachWhile(action: suspend (T) -> Boolean): Flow<T> {
}
}
fun <T> Flow<T>.onEachIndexed(action: suspend (index: Int, T) -> Unit): Flow<T> {
val counter = AtomicInteger(0)
return transform { value ->
action(counter.getAndIncrement(), value)
return@transform emit(value)
}
}
inline fun <T, R> Flow<List<T>>.mapItems(crossinline transform: (T) -> R): Flow<List<R>> {
return map { list -> list.map(transform) }
}

View File

@@ -8,6 +8,7 @@ import okhttp3.Response
import okhttp3.internal.closeQuietly
import okio.IOException
import org.json.JSONObject
import org.jsoup.HttpStatusException
import java.net.HttpURLConnection
private val TYPE_JSON = "application/json".toMediaType()
@@ -34,9 +35,8 @@ val HttpUrl.isHttpOrHttps: Boolean
fun Response.ensureSuccess() = apply {
if (!isSuccessful || code == HttpURLConnection.HTTP_NO_CONTENT) {
val message = "Invalid response: $code $message at ${request.url}"
closeQuietly()
throw IllegalStateException(message)
throw HttpStatusException(message, code, request.url.toString())
}
}

View File

@@ -1,6 +1,9 @@
package org.koitharu.kotatsu.core.util.ext
import android.content.Context
import androidx.core.os.LocaleListCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.Locale
operator fun LocaleListCompat.iterator(): ListIterator<Locale> = LocaleListCompatIterator(this)
@@ -17,6 +20,15 @@ inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
fun String.toLocale() = Locale(this)
fun Locale?.getDisplayName(context: Context): String {
if (this == null) {
return context.getString(R.string.various_languages)
}
return getDisplayLanguage(this).toTitleCase(this)
}
private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {
private var index = 0

View File

@@ -3,18 +3,18 @@ package org.koitharu.kotatsu.core.util.ext
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.util.TypedValue
import androidx.annotation.Px
import androidx.core.util.TypedValueCompat
import kotlin.math.roundToInt
@Px
fun Resources.resolveDp(dp: Int) = (dp * displayMetrics.density).roundToInt()
fun Resources.resolveDp(dp: Int) = resolveDp(dp.toFloat()).roundToInt()
@Px
fun Resources.resolveDp(dp: Float) = dp * displayMetrics.density
fun Resources.resolveDp(dp: Float) = TypedValueCompat.dpToPx(dp, displayMetrics)
@Px
fun Resources.resolveSp(sp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, displayMetrics)
fun Resources.resolveSp(sp: Float) = TypedValueCompat.spToPx(sp, displayMetrics)
@SuppressLint("DiscouragedApi")
fun Context.getSystemBoolean(resName: String, fallback: Boolean): Boolean {

View File

@@ -8,9 +8,11 @@ import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.annotation.Px
import androidx.annotation.StyleRes
import androidx.core.content.ContextCompat
import androidx.core.content.res.use
import androidx.core.graphics.ColorUtils
import com.google.android.material.R as materialR
fun Context.getThemeDrawable(
@AttrRes resId: Int,
@@ -75,3 +77,7 @@ fun TypedArray.getDrawableCompat(context: Context, index: Int): Drawable? {
val resId = getResourceId(index, 0)
return if (resId != 0) ContextCompat.getDrawable(context, resId) else null
}
@get:StyleRes
val DIALOG_THEME_CENTERED: Int
inline get() = materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered

View File

@@ -19,6 +19,11 @@ import org.koitharu.kotatsu.core.exceptions.SyncApiException
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_GENRES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.FILTER_MULTIPLE_STATES_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.ErrorMessages.SEARCH_NOT_SUPPORTED
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -27,7 +32,7 @@ import java.net.SocketTimeoutException
import java.net.UnknownHostException
private const val MSG_NO_SPACE_LEFT = "No space left on device"
private const val IMAGE_FORMAT_NO_SUPPORTED = "Image format not supported"
private const val IMAGE_FORMAT_NOT_SUPPORTED = "Image format not supported"
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required)
@@ -56,8 +61,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is HttpException -> getHttpDisplayMessage(response.code, resources)
is HttpStatusException -> getHttpDisplayMessage(statusCode, resources)
is IOException -> getDisplayMessage(message, resources) ?: localizedMessage
else -> localizedMessage
else -> getDisplayMessage(message, resources) ?: localizedMessage
}.ifNullOrEmpty {
resources.getString(R.string.error_occurred)
}
@@ -82,7 +86,12 @@ private fun getHttpDisplayMessage(statusCode: Int, resources: Resources): String
private fun getDisplayMessage(msg: String?, resources: Resources): String? = when {
msg.isNullOrEmpty() -> null
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
msg.contains(IMAGE_FORMAT_NO_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)
msg.contains(IMAGE_FORMAT_NOT_SUPPORTED) -> resources.getString(R.string.error_corrupted_file)
msg == FILTER_MULTIPLE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_genres_not_supported)
msg == FILTER_MULTIPLE_STATES_NOT_SUPPORTED -> resources.getString(R.string.error_multiple_states_not_supported)
msg == SEARCH_NOT_SUPPORTED -> resources.getString(R.string.error_search_not_supported)
msg == FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_locale_genre_not_supported)
msg == FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED -> resources.getString(R.string.error_filter_states_genre_not_supported)
else -> null
}

View File

@@ -0,0 +1,50 @@
package org.koitharu.kotatsu.core.util.ext
import android.net.Uri
import androidx.core.net.toFile
import okio.Source
import okio.source
import okio.use
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
import java.io.File
import java.util.zip.ZipFile
const val URI_SCHEME_FILE = "file"
const val URI_SCHEME_ZIP = "file+zip"
fun Uri.exists(): Boolean = when (scheme) {
URI_SCHEME_FILE -> toFile().exists()
URI_SCHEME_ZIP -> {
val file = File(requireNotNull(schemeSpecificPart))
file.exists() && ZipFile(file).use { it.getEntry(fragment) != null }
}
else -> unsupportedUri(this)
}
fun Uri.isTargetNotEmpty(): Boolean = when (scheme) {
URI_SCHEME_FILE -> toFile().isNotEmpty()
URI_SCHEME_ZIP -> {
val file = File(requireNotNull(schemeSpecificPart))
file.exists() && ZipFile(file).use { (it.getEntry(fragment)?.size ?: 0L) != 0L }
}
else -> unsupportedUri(this)
}
fun Uri.source(): Source = when (scheme) {
URI_SCHEME_FILE -> toFile().source()
URI_SCHEME_ZIP -> {
val zip = ZipFile(schemeSpecificPart)
val entry = zip.getEntry(fragment)
zip.getInputStream(entry).source().withExtraCloseable(zip)
}
else -> unsupportedUri(this)
}
fun File.toZipUri(entryName: String): Uri = Uri.parse("$URI_SCHEME_ZIP://$absolutePath#$entryName")
private fun unsupportedUri(uri: Uri): Nothing {
throw IllegalArgumentException("Bad uri $uri: only schemes $URI_SCHEME_FILE and $URI_SCHEME_ZIP are supported")
}

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