Compare commits

...

331 Commits
v3.1 ... v3.4.8

Author SHA1 Message Date
Koitharu
14f5d5daa4 Update parsers 2022-08-01 17:14:35 +03:00
Koitharu
f342cd6b56 Fix crash on widgets update 2022-08-01 17:00:00 +03:00
Koitharu
8faacab53a Fix github url 2022-07-30 16:10:03 +03:00
Koitharu
659c327a6d Update parsers and version 2022-07-30 16:05:01 +03:00
Koitharu
bcc2f531c3 Ability to resume download after IOException 2022-07-30 16:02:13 +03:00
Koitharu
020df5c1f7 Fix saving pages from cbz 2022-07-30 14:15:12 +03:00
Koitharu
d6781e1d14 Yet another attempt to make webtoon reader great again 2022-07-29 15:39:04 +03:00
Zakhar Timoshenko
d42cd59880 Fix enabling disabled new sources
Co-authored-by: Koitharu <8948226+nv95@users.noreply.github.com>
2022-07-28 09:49:30 +03:00
Koitharu
be19c32fea Update prasers 2022-07-27 17:36:39 +03:00
Koitharu
8da0e98d23 Fix FragmentManager leak 2022-07-27 17:36:39 +03:00
Koitharu
73a2f05509 Fix FadingSnackbar text color 2022-07-27 17:36:39 +03:00
Koitharu
bb23f998e0 Fix crash on description selection 2022-07-27 17:36:35 +03:00
TheDawnOvO
75915ff366 Update activity_main.xml 2022-07-27 15:24:39 +03:00
Dpper
517e801580 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (323 of 323 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-07-27 08:47:39 +03:00
J. Lavoie
12474e23f9 Translated using Weblate (Finnish)
Currently translated at 96.2% (311 of 323 strings)

Translated using Weblate (French)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Italian)

Currently translated at 99.0% (320 of 323 strings)

Translated using Weblate (German)

Currently translated at 97.8% (316 of 323 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-07-27 08:47:39 +03:00
kuragehime
00bdd859a7 Translated using Weblate (Japanese)
Currently translated at 100.0% (323 of 323 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-07-27 08:47:39 +03:00
Oğuz Ersen
3a3af9ea00 Translated using Weblate (Turkish)
Currently translated at 100.0% (323 of 323 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-07-27 08:47:39 +03:00
Koitharu
1803b1a2ee Remove unused WebViewClientCompat 2022-07-25 11:25:43 +03:00
Koitharu
4175c84363 Move create category button in bs to toolbar 2022-07-22 19:04:43 +03:00
Koitharu
1840d7b50e Fix get current page #165 2022-07-20 20:32:01 +03:00
Zakhar Timoshenko
37b69833b3 Update parsers 2022-07-20 19:52:56 +03:00
Dpper
093f766d1d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (321 of 321 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-07-20 18:34:50 +03:00
Sergio Varela
69d8459b1c Translated using Weblate (Spanish)
Currently translated at 100.0% (321 of 321 strings)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-07-20 18:34:50 +03:00
kuragehime
fa8a526642 Translated using Weblate (Japanese)
Currently translated at 100.0% (321 of 321 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-07-20 18:34:50 +03:00
Oğuz Ersen
1d35d951e6 Translated using Weblate (Turkish)
Currently translated at 100.0% (322 of 322 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (321 of 321 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-07-20 18:34:50 +03:00
J. Lavoie
3c0420f42f Translated using Weblate (Finnish)
Currently translated at 96.2% (310 of 322 strings)

Translated using Weblate (French)

Currently translated at 100.0% (322 of 322 strings)

Translated using Weblate (Italian)

Currently translated at 99.0% (319 of 322 strings)

Translated using Weblate (German)

Currently translated at 97.8% (315 of 322 strings)

Translated using Weblate (French)

Currently translated at 100.0% (321 of 321 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (319 of 321 strings)

Translated using Weblate (German)

Currently translated at 97.8% (314 of 321 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-07-20 18:34:50 +03:00
Koitharu
d000a825d3 Update parsers 2022-07-20 18:32:38 +03:00
Koitharu
23b28672d4 Fix sharing files from sdcard 2022-07-20 18:03:46 +03:00
Koitharu
a076c9f420 Make NotFoundException resolvable 2022-07-20 17:32:22 +03:00
Koitharu
bdc7a8f5ed Fallback to system dnf if DoH failed 2022-07-20 15:13:45 +03:00
Koitharu
bdcc3bb1f5 Update parsers and adjust ParseException reporting 2022-07-19 12:07:21 +03:00
Koitharu
18d45aa1a3 Chapters range selection 2022-07-19 10:29:39 +03:00
Koitharu
b5bb8efe0a Improve database tests 2022-07-19 10:01:23 +03:00
Koitharu
f18c18230b Update favourites categories empty state image 2022-07-18 18:29:29 +03:00
Koitharu
2fd1e998f4 Fix unmark chapter as new 2022-07-18 14:30:07 +03:00
Koitharu
c5a1980e0d Fix tests for api21 2022-07-18 14:12:24 +03:00
Koitharu
d470ca4b47 Test for update checking 2022-07-18 13:09:11 +03:00
Koitharu
35f450e444 Fix android tests 2022-07-18 12:57:41 +03:00
Koitharu
206fb4e584 Update test data 2022-07-18 12:42:15 +03:00
Koitharu
62088b36a4 Update app shortcuts using InvalidationTracker.Callback 2022-07-18 12:16:39 +03:00
Koitharu
aa5fd530d3 Fix database test 2022-07-18 11:34:00 +03:00
Koitharu
f0ee64bafa Unit test for BackupAgent 2022-07-18 11:27:56 +03:00
Koitharu
dfa413da6f Observe database updates using InvalidationTracker 2022-07-18 09:55:49 +03:00
Koitharu
9eb5e699e1 Unit tests for json (de)serialization 2022-07-18 09:19:29 +03:00
Koitharu
2d4c1b751e Update dependencies 2022-07-14 14:48:26 +03:00
Koitharu
91b17ef4a2 Fix global search parallelism 2022-07-14 14:23:04 +03:00
Koitharu
9b748f7334 Update parsers and version 2022-07-14 14:12:47 +03:00
Koitharu
2deaed2067 Optimize image loading in lists 2022-07-13 14:48:33 +03:00
Koitharu
fb608ed30a Fix findProgress function not suspend 2022-07-12 12:49:49 +03:00
Koitharu
8e43afe408 Prohibit empty names in favourite categories 2022-07-12 12:02:49 +03:00
Koitharu
73df680214 Add domain validation in source settings 2022-07-12 11:13:33 +03:00
Koitharu
fa4aa154a3 Update parsers and version 2022-07-12 10:53:23 +03:00
Koitharu
cf7cdbc41b Fix CloudFlare resolve url 2022-07-12 10:46:45 +03:00
Koitharu
c2561a1de0 Fix CompositeMutex concurrent locking 2022-07-12 10:46:45 +03:00
Koitharu
a36abe0272 Fix missed git conflicts 2022-07-12 10:46:44 +03:00
Koitharu
5b10d697f6 Fix ReadActivity error reporting 2022-07-12 10:46:44 +03:00
Koitharu
e0f07ccc3b Update issue templates 2022-07-12 10:43:38 +03:00
DevCoz
938ea8fb73 Translated using Weblate (Chinese (Simplified))
Currently translated at 98.7% (316 of 320 strings)

Co-authored-by: DevCoz <qq851428876@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-07-12 10:43:28 +03:00
Sergio Varela
ea6a338128 Translated using Weblate (Spanish)
Currently translated at 100.0% (320 of 320 strings)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-07-12 10:43:28 +03:00
Koitharu
ce3a668103 Fix category edit activity recreation 2022-07-09 16:59:12 +03:00
Koitharu
557c2b018a Update version and parsers 2022-07-08 12:33:05 +03:00
Koitharu
3add01d57e Improve reporting of caught exceptions 2022-07-08 10:35:18 +03:00
Koitharu
2ad1ea98f1 Fix crash in SourceSettingsFragment 2022-07-08 10:27:11 +03:00
Koitharu
3121532217 Translated using Weblate (Russian)
Currently translated at 100.0% (320 of 320 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-07-07 11:35:56 +03:00
Koitharu
20ac12ca0d Update readme and metadata 2022-07-07 11:06:20 +03:00
Koitharu
f0b222140e Update parsers 2022-07-07 10:17:58 +03:00
Koitharu
2a35ca6094 Fix landscape details view #197 2022-07-07 09:14:50 +03:00
Koitharu
93f9636916 Improve webtoon reader performance 2022-07-06 19:43:43 +03:00
Koitharu
2c24aba558 Refactor update checker 2022-07-06 16:15:25 +03:00
Koitharu
a35d7dc5ae Ability to resolve errors in manga source settings 2022-07-06 15:13:54 +03:00
J. Lavoie
1e9e7e4cd7 Translated using Weblate (Finnish)
Currently translated at 96.5% (309 of 320 strings)

Translated using Weblate (French)

Currently translated at 100.0% (320 of 320 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (318 of 320 strings)

Translated using Weblate (German)

Currently translated at 97.8% (313 of 320 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-07-06 15:12:08 +03:00
kuragehime
cfd97ebd3d Translated using Weblate (Japanese)
Currently translated at 100.0% (320 of 320 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (319 of 319 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-07-06 13:12:47 +03:00
Oğuz Ersen
3ac8dc5558 Translated using Weblate (Turkish)
Currently translated at 100.0% (320 of 320 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (319 of 319 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-07-06 13:12:47 +03:00
Anupam Malhotra
6f93440b11 Translated using Weblate (Spanish)
Currently translated at 95.2% (304 of 319 strings)

Co-authored-by: Anupam Malhotra <anpm.malhotra@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-07-06 13:12:47 +03:00
Koitharu
9283f419ba Support images in manga description 2022-07-06 13:12:58 +03:00
Koitharu
be67b36b6a Refactor extensions 2022-07-06 12:49:21 +03:00
Koitharu
6934daecff Preselect new sources based on locale 2022-07-06 12:20:48 +03:00
Koitharu
3d74d027c1 Remove duplicate item from history settings 2022-07-06 12:13:27 +03:00
Koitharu
e048235dad Update dependencies 2022-07-06 12:12:52 +03:00
Koitharu
d21dff08b8 Invalidate DiskLruCache if broken 2022-07-06 11:59:52 +03:00
Koitharu
5f06c4c3c0 Fix selection state restoring 2022-07-04 14:21:19 +03:00
Koitharu
b693b34fe7 Add "Show all" button to search results 2022-07-04 14:05:23 +03:00
Koitharu
b7f469957c TrimTransformation for images 2022-07-04 13:48:51 +03:00
Koitharu
4a7b415635 Fix search suggestions manga ShapeAppearance 2022-07-04 13:08:15 +03:00
Koitharu
d9985d03ab Save state of selection in lists 2022-07-04 13:03:11 +03:00
Koitharu
6e324fd5ab Merge branch 'feature/percent' into devel 2022-07-02 14:51:50 +03:00
Koitharu
167498dd2c Merge branch 'feature/empty_states' into devel 2022-07-02 14:48:31 +03:00
Koitharu
688eaf6aab Merge branch 'devel' of github.com:KotatsuApp/Kotatsu into devel 2022-07-02 14:47:53 +03:00
kuragehime
d7df105e04 Translated using Weblate (Japanese)
Currently translated at 100.0% (305 of 305 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-07-02 14:45:55 +03:00
Oğuz Ersen
ea5c4cd027 Translated using Weblate (Turkish)
Currently translated at 100.0% (305 of 305 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-07-02 14:45:55 +03:00
Aliaksiej Razumaŭ
9589706df9 Translated using Weblate (Belarusian)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (304 of 304 strings)

Co-authored-by: Aliaksiej Razumaŭ <belarusaed@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-07-02 14:45:55 +03:00
Koitharu
e6e37aec47 Merge branch 'devel' into feature/shikimori 2022-07-02 14:39:03 +03:00
Koitharu
fc2d5fe00e Update ScrobblingInfoBottomSheet 2022-07-02 14:33:16 +03:00
Zakhar Timoshenko
a73d3d375a Unregistering scrobbler 2022-07-02 12:33:23 +03:00
Koitharu
ce97c8f7d9 Ability to report non-fatal exceptions 2022-07-02 11:02:03 +03:00
Koitharu
a48abc56dd Catch HttpException in widgets as well 2022-07-01 16:02:33 +03:00
Koitharu
1044d7a8d1 Show checkmark on reading indicator if full 2022-07-01 15:32:47 +03:00
Zakhar Timoshenko
fb0a075c50 Fix acknowledgment 2022-06-30 18:42:53 +03:00
Zakhar Timoshenko
8a8c785a31 Add issue moderator action 2022-06-30 18:40:31 +03:00
Zakhar Timoshenko
4e976fc4ec Merge remote-tracking branch 'origin/devel' into devel 2022-06-30 18:24:56 +03:00
Zakhar Timoshenko
cdf06578c1 [Issue template] Add old version of issue templates
This is need for some GitHub clients like Octodroid and something else
2022-06-30 18:24:44 +03:00
Koitharu
4a131d6215 Update empty favourites image 2022-06-30 09:03:34 +03:00
Koitharu
45f71cdcc1 Empty status vector images 2022-06-29 16:43:25 +03:00
Koitharu
5192175ddc Merge branch 'devel' 2022-06-29 12:22:37 +03:00
Koitharu
274a672637 Increase version 2022-06-29 12:21:44 +03:00
Koitharu
c281ab5a39 Fix widgets size and background 2022-06-29 12:01:59 +03:00
Koitharu
0106afc93c Fix warnings 2022-06-29 11:38:33 +03:00
Koitharu
d69b091858 Translated using Weblate (Russian)
Currently translated at 99.6% (303 of 304 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-06-29 11:01:05 +03:00
Koitharu
f71274d90d Update translations 2022-06-29 10:54:17 +03:00
kuragehime
a37b9a1036 Translated using Weblate (Japanese)
Currently translated at 100.0% (304 of 304 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-06-29 10:35:18 +03:00
Oğuz Ersen
a9b8174b10 Translated using Weblate (Turkish)
Currently translated at 100.0% (304 of 304 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-06-29 10:35:18 +03:00
J. Lavoie
20a0eda5db Translated using Weblate (Finnish)
Currently translated at 99.6% (303 of 304 strings)

Translated using Weblate (French)

Currently translated at 100.0% (304 of 304 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (304 of 304 strings)

Translated using Weblate (German)

Currently translated at 100.0% (304 of 304 strings)

Translated using Weblate (Spanish)

Currently translated at 99.3% (302 of 304 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-06-29 10:35:18 +03:00
Artem
e231dba0b0 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (301 of 301 strings)

Co-authored-by: Artem <artem@molotov.work>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-06-29 10:35:18 +03:00
Koitharu
d9459dc8fa Ui adjustments 2022-06-28 12:44:59 +03:00
Koitharu
a55ff5ce5a Option to disable reading progress indicators 2022-06-28 12:35:55 +03:00
Koitharu
f2ea1cde46 Reading progress indicator on details screen 2022-06-28 12:10:39 +03:00
Koitharu
04dd8003f7 Show reading progress indicators in lists 2022-06-28 11:39:54 +03:00
Koitharu
b82b46f7d7 Save reading percent to database 2022-06-27 16:21:53 +03:00
Koitharu
33bccd10fe Update widgets for Android 12 2022-06-27 14:44:43 +03:00
Koitharu
9956f1ae4f Fix detailed list item 2022-06-27 13:11:08 +03:00
Koitharu
09a3c5da23 Update dialogs 2022-06-27 12:26:23 +03:00
Koitharu
b1d51bbefb Migrate to MaterialSwitch component 2022-06-27 12:11:22 +03:00
Koitharu
9e7aaa6c91 Option to disable fingerprint authentication 2022-06-27 11:54:33 +03:00
Koitharu
9686c97731 Add tracker settings to option menu 2022-06-26 11:21:47 +03:00
Koitharu
663718ece4 Add ScrollView to list mode dialog 2022-06-26 11:09:58 +03:00
Koitharu
e3048d1eb2 Compact chapters list 2022-06-26 10:21:47 +03:00
Koitharu
c6785bfda0 Merge branch 'devel' into feature/shikimori 2022-06-26 10:13:57 +03:00
kuragehime
781438d83a Translated using Weblate (Japanese)
Currently translated at 100.0% (301 of 301 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-06-25 20:10:32 +02:00
Oğuz Ersen
f5625cc6b9 Translated using Weblate (Turkish)
Currently translated at 100.0% (301 of 301 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-06-25 20:10:32 +02:00
Allan Nordhøy
132d375d81 Translated using Weblate (Norwegian Bokmål)
Currently translated at 92.6% (279 of 301 strings)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translation: Kotatsu/Strings
2022-06-25 20:10:32 +02:00
J. Lavoie
54007ecd28 Translated using Weblate (Finnish)
Currently translated at 75.0% (6 of 8 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (300 of 301 strings)

Translated using Weblate (French)

Currently translated at 100.0% (301 of 301 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (301 of 301 strings)

Translated using Weblate (German)

Currently translated at 100.0% (301 of 301 strings)

Translated using Weblate (Spanish)

Currently translated at 99.3% (299 of 301 strings)

Added translation using Weblate (Finnish)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-06-25 20:10:31 +02:00
Koitharu
03c2b55776 Replace done menu item with button 2022-06-25 21:11:24 +03:00
Koitharu
c6e46384f8 Fix switches on Lollipop 2022-06-25 21:11:24 +03:00
Zakhar Timoshenko
ce1c607f05 [Issue template] Update 2022-06-24 23:12:24 +03:00
DevCoz
19bbe51f01 Translated using Weblate (Chinese (Simplified))
Currently translated at 99.0% (297 of 300 strings)

Co-authored-by: DevCoz <qq851428876@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-06-24 14:37:15 +03:00
Artem
ff4a4eaf78 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (300 of 300 strings)

Co-authored-by: Artem <artem@molotov.work>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-06-24 14:37:15 +03:00
Koitharu
2cf08f74db Option to disable all sources 2022-06-24 14:10:51 +03:00
Koitharu
cc6bbc9869 Change defult list mode to grid 2022-06-24 13:56:27 +03:00
Koitharu
8d962516f4 Update dependencies 2022-06-24 13:46:18 +03:00
Koitharu
ced4ca01cb Add icon to metadata 2022-06-23 16:04:46 +03:00
Koitharu
1195323acd Add icon to metadata 2022-06-23 14:54:30 +03:00
Koitharu
87b62aef70 Merge branch 'feature/shikimori' of github.com:SkyfaceD/Kotatsu into feature/shikimori 2022-06-22 14:53:12 +03:00
Koitharu
ec89ba0155 Shikimori interaction implementation 2022-06-22 13:24:07 +03:00
Koitharu
0695103589 Merge branch 'devel' into feature/shikimori 2022-06-21 13:31:44 +03:00
Koitharu
8d47b09e80 Add biometric authentication 2022-06-21 12:51:24 +03:00
J. Lavoie
3cd156ae42 Translated using Weblate (Finnish)
Currently translated at 99.3% (298 of 300 strings)

Translated using Weblate (French)

Currently translated at 100.0% (300 of 300 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (300 of 300 strings)

Translated using Weblate (German)

Currently translated at 100.0% (300 of 300 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (297 of 298 strings)

Translated using Weblate (French)

Currently translated at 100.0% (298 of 298 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (298 of 298 strings)

Translated using Weblate (German)

Currently translated at 100.0% (298 of 298 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Luiz-bro
4a22f62ec5 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (298 of 298 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Oğuz Ersen
9ad37b4412 Translated using Weblate (Turkish)
Currently translated at 100.0% (300 of 300 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (298 of 298 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
kuragehime
9fcabcb05e Translated using Weblate (Japanese)
Currently translated at 100.0% (300 of 300 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (298 of 298 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Artem
db1ae6020d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (298 of 298 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (296 of 296 strings)

Co-authored-by: Artem <artem@molotov.work>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Tsukino Nami
7e039c9055 Translated using Weblate (Chinese (Simplified))
Currently translated at 3.7% (11 of 296 strings)

Added translation using Weblate (Chinese (Simplified))

Co-authored-by: Tsukino Nami <zhangyongyi666@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
I. Musthafa
db7482ff12 Translated using Weblate (Indonesian)
Currently translated at 94.2% (279 of 296 strings)

Translated using Weblate (Indonesian)

Currently translated at 92.5% (274 of 296 strings)

Translated using Weblate (Indonesian)

Currently translated at 26.3% (78 of 296 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Indonesian)

Added translation using Weblate (Indonesian)

Co-authored-by: I. Musthafa <i.musthafa66@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
2022-06-21 11:01:38 +03:00
Dpper
d63ae7cadd Translated using Weblate (Ukrainian)
Currently translated at 100.0% (296 of 296 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Sergio Varela
c4fa0d405e Translated using Weblate (Spanish)
Currently translated at 100.0% (296 of 296 strings)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-06-21 11:01:38 +03:00
Koitharu
63fef3ab7c Fix empty chapters placeholder #176 2022-06-21 11:00:18 +03:00
Koitharu
7019c07a6d Update parsers 2022-06-21 10:42:25 +03:00
Koitharu
006ea9844a Update readme 2022-06-21 10:40:11 +03:00
Koitharu
d2bbfe01f1 Add ACRA for crash reports 2022-06-20 10:53:58 +03:00
Koitharu
86d8ff3c68 Refactor AboutLinksPreference 2022-06-18 20:16:49 +03:00
Koitharu
00dacc32df Update parsers 2022-06-18 19:51:13 +03:00
Zakhar Timoshenko
1c1bd9265e Fix links 2022-06-18 17:04:01 +03:00
Zakhar Timoshenko
0bb090eee6 Tweak About view 2022-06-18 17:01:12 +03:00
Koitharu
d2f3bfb2e3 Add ignore battery optimization option 2022-06-18 13:11:14 +03:00
Koitharu
634ce0dddf Tracker tests and fixes 2022-06-18 10:59:01 +03:00
Koitharu
c82bacb037 Refactor tracker and add tests 2022-06-16 15:26:57 +03:00
Koitharu
3edfd0892a Move tracker logic into own class 2022-06-15 13:53:29 +03:00
Koitharu
30c0fd600f Fix global search results order 2022-05-31 16:43:23 +03:00
Koitharu
ccb31de1ba Fix wrong tracker notifications 2022-05-31 16:22:30 +03:00
Koitharu
a74b623c10 Refactor menu providers 2022-05-30 15:45:29 +03:00
Koitharu
5808e8f321 Update dependencies 2022-05-30 13:05:26 +03:00
Koitharu
4f3fef3bfe Update parsers 2022-05-25 10:04:27 +03:00
Zakhar Timoshenko
0c07e649bf [Issue template] Update version 2022-05-21 01:32:27 +03:00
Koitharu
fc2820ec11 Update version to v3.3 2022-05-20 19:12:58 +03:00
Zakhar Timoshenko
312fb033e0 Fix weird toolbar in category edit activity 2022-05-20 18:40:53 +03:00
Koitharu
18bc4dc739 Merge branch 'devel' of github.com:nv95/Kotatsu into devel 2022-05-20 12:16:22 +03:00
Artem
2b61b27271 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Co-authored-by: Artem <artem@molotov.work>
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
2022-05-20 12:15:03 +03:00
Koitharu
58c9f75b91 Fix tags order in filter 2022-05-20 12:14:23 +03:00
Koitharu
790f1fb8a3 Update parsers 2022-05-20 12:06:26 +03:00
Zakhar Timoshenko
5c4f3f7fe4 Revert onCreateDialog method
This thing is really needed
2022-05-18 23:35:58 +03:00
Zakhar Timoshenko
86ead09080 Revert accidentally removed style 2022-05-18 22:20:54 +03:00
Zakhar Timoshenko
0932507346 Bottom sheet improvements 2022-05-18 22:14:14 +03:00
Koitharu
21f7b7120a Use collator for tags sorting 2022-05-18 11:27:07 +03:00
Koitharu
473135bfc5 Apply theme changing without restarting 2022-05-17 16:26:04 +03:00
Koitharu
ce7960e5e9 Recreate all activities on theme changed 2022-05-17 13:23:03 +03:00
SkyfaceD
3be96cf035 Hide shikimori sensitive information 2022-05-17 15:40:34 +06:00
Koitharu
17c440ee43 Fix marking sources as new 2022-05-17 11:31:06 +03:00
Koitharu
5d881ca154 New global search activity 2022-05-17 11:19:55 +03:00
Koitharu
e4b29b3ff9 Cleanup strings 2022-05-15 17:11:40 +03:00
Luiz-bro
046aaa0649 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (297 of 297 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
Dpper
f653c74ce8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
kuragehime
0c73c55b9d Translated using Weblate (Japanese)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (287 of 287 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
Oğuz Ersen
8dec54e96f Translated using Weblate (Turkish)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
J. Lavoie
859ae966c8 Translated using Weblate (Finnish)
Currently translated at 99.6% (296 of 297 strings)

Translated using Weblate (French)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (German)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (294 of 295 strings)

Translated using Weblate (French)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (German)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (287 of 288 strings)

Translated using Weblate (French)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (German)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (French)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
Koitharu
5abf5d3367 Update gradle and app version 2022-05-15 16:36:08 +03:00
Koitharu
0dc4e63b7a Fix large cover 2022-05-14 20:20:04 +03:00
Koitharu
95d7ca5264 Configure default reader mode #160 #142 2022-05-12 14:39:15 +03:00
Koitharu
317252e1dd Fix isLoading LiveData 2022-05-12 13:44:05 +03:00
Koitharu
d9044b2d03 Merge branch 'release/3.2.3' into devel 2022-05-12 13:24:32 +03:00
Koitharu
b6ae4e2b41 Fix empty chapters placeholder 2022-05-12 13:16:46 +03:00
Koitharu
fce31df121 Fix download notification on download finish 2022-05-12 12:50:06 +03:00
Koitharu
d5c1d86313 Mark nsfw notifications as secure 2022-05-12 12:42:27 +03:00
Koitharu
46df41504c Hide feed section if tracker is disabled 2022-05-12 12:27:27 +03:00
Koitharu
48e232e04e Dns over https option #161 2022-05-12 12:19:48 +03:00
Koitharu
58ff7c9235 Fix branches list 2022-05-12 10:35:12 +03:00
Koitharu
730d664b91 Tune ui 2022-05-12 10:20:43 +03:00
Koitharu
36634ecca1 Option to undo removing from favourites 2022-05-11 16:23:37 +03:00
Koitharu
10c03ff01a Update version in github templates 2022-05-11 12:28:50 +03:00
Koitharu
e85b9db118 Show stub if favourite categories empty 2022-05-11 12:20:06 +03:00
Koitharu
f6b0a7c780 Update parsers 2022-05-11 12:11:37 +03:00
Koitharu
3e785a2555 Refactor and optimization 2022-05-11 10:52:21 +03:00
Koitharu
1cbb825892 Bookmarks feature 2022-05-10 15:40:39 +03:00
Koitharu
82efa8298d Cache shikimori user 2022-05-10 10:59:19 +03:00
Koitharu
c2ba716916 Merge branch 'devel' into feature/shikimori 2022-05-09 15:31:15 +03:00
Koitharu
161bc5f69d Show stub if favourite categories empty 2022-05-09 15:27:00 +03:00
Koitharu
b17237eb6b Fix favourite categories edit 2022-05-09 13:02:45 +03:00
Koitharu
f61497ffd9 Shikimori manga tracking selection list 2022-05-09 12:57:38 +03:00
Koitharu
7f3c46942d Merge branch 'devel' into feature/shikimori 2022-05-09 09:42:21 +03:00
Koitharu
4771882f50 Edit favourite category activity 2022-05-09 09:02:58 +03:00
Koitharu
345a1379ae Merge branch 'feature/tracker_categories' into devel 2022-05-07 15:06:30 +03:00
Koitharu
33ab7f4d95 Cleanup preference_toggle_header 2022-05-07 15:06:17 +03:00
Zakhar Timoshenko
2a97cb34d7 Change root view of preference_toggle_header 2022-05-07 14:01:07 +03:00
Koitharu
03cbd8410f Remove travis.ci integration 2022-05-07 09:21:32 +03:00
Koitharu
3c54bdd003 Merge branch 'master' into devel 2022-05-07 09:17:47 +03:00
Koitharu
ba0a94e525 Merge branch 'devel' of github.com:nv95/Kotatsu into devel 2022-05-07 09:16:38 +03:00
Luiz-bro
b439e0c2c2 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (281 of 281 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-05-07 09:15:17 +03:00
J. Lavoie
f9281850ad Translated using Weblate (Finnish)
Currently translated at 99.6% (280 of 281 strings)

Translated using Weblate (French)

Currently translated at 100.0% (281 of 281 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (281 of 281 strings)

Translated using Weblate (German)

Currently translated at 100.0% (281 of 281 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-05-07 09:15:17 +03:00
Dpper
4d5d25834e Translated using Weblate (Ukrainian)
Currently translated at 100.0% (281 of 281 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Ukrainian)

Added translation using Weblate (Ukrainian)

Co-authored-by: Dpper <ruslan20020401@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
2022-05-07 09:15:17 +03:00
kuragehime
9e706ea096 Translated using Weblate (Japanese)
Currently translated at 100.0% (281 of 281 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-05-07 09:15:17 +03:00
Oğuz Ersen
46fe2bb8ac Translated using Weblate (Turkish)
Currently translated at 100.0% (281 of 281 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-05-07 09:15:17 +03:00
Koitharu
6405523232 Fix CategoryListModel equals/hashcode 2022-05-07 08:50:33 +03:00
Zakhar Timoshenko
930819ffa2 Fix setting tracker on favourites screen 2022-05-06 20:59:43 +03:00
Zakhar Timoshenko
fa150e45ff [Issue template] Update version 2022-05-06 20:15:29 +03:00
Zakhar Timoshenko
400a2b14f7 Change a bit preference_toggle_header view 2022-05-06 16:54:41 +03:00
Zakhar Timoshenko
a40322b2e7 Fix crash on first database initialization 2022-05-06 16:53:55 +03:00
Koitharu
de9c1017b3 Update parsers 2022-05-06 15:45:20 +03:00
Koitharu
2709d40fc0 Fix BottomSheet edge-to-edge mode 2022-05-06 13:51:55 +03:00
Koitharu
45b42ad5bd Revert "Fix bottom sheet navbar color"
This reverts commit fdd4f5abca.
2022-05-06 12:57:37 +03:00
Koitharu
878df24a64 Add voice search 2022-05-06 10:52:51 +03:00
Koitharu
b759f8d0a0 Fix pages filename #151 2022-05-05 16:46:44 +03:00
Koitharu
23e7aa2aaa Fix images scale type 2022-05-05 15:57:05 +03:00
Koitharu
fdd4f5abca Fix bottom sheet navbar color 2022-05-05 15:51:30 +03:00
Koitharu
c695468aec Fix cold launch 2022-05-05 15:43:07 +03:00
Koitharu
9166716f2a Update version 2022-05-05 15:21:10 +03:00
Zakhar Timoshenko
3407e74e99 Fix FavouriteCategoriesDialog toolbar in album orientation 2022-05-05 15:16:30 +03:00
Koitharu
6969f40fa0 Merge branch 'devel' of github.com:nv95/Kotatsu into devel 2022-05-05 15:13:12 +03:00
Koitharu
11fc8b6642 Configure manga tracker for each favourite category 2022-05-05 15:11:28 +03:00
Zakhar Timoshenko
4e4024c182 Fix FavouriteCategoriesDialog toolbar in album orientation 2022-05-04 23:00:44 +03:00
Zakhar Timoshenko
1d1931f721 [Issue template] Update version 2022-05-04 19:01:32 +03:00
Koitharu
ffad6a4ae6 Upgrade coil to v2 2022-05-04 13:20:00 +03:00
Koitharu
4c5314fe59 Update parsers 2022-05-02 14:53:31 +03:00
Koitharu
96be49aa83 Update monochrome launcher icon 2022-05-02 09:39:01 +03:00
Koitharu
28b556121b Show new sources on app startup 2022-05-02 09:35:21 +03:00
Koitharu
558c19e526 Update parsers and version 2022-05-01 09:24:45 +03:00
Luiz-bro
59c2d20311 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (280 of 280 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
Luna Jernberg
fa1f2cbf51 Translated using Weblate (Swedish)
Currently translated at 98.5% (275 of 279 strings)

Co-authored-by: Luna Jernberg <droidbittin@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
J. Lavoie
de8739f143 Translated using Weblate (Finnish)
Currently translated at 99.6% (279 of 280 strings)

Translated using Weblate (French)

Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (German)

Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (278 of 279 strings)

Translated using Weblate (French)

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (German)

Currently translated at 100.0% (279 of 279 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
kuragehime
9aa28f6fd2 Translated using Weblate (Japanese)
Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (279 of 279 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
Oğuz Ersen
a2b1699047 Translated using Weblate (Turkish)
Currently translated at 100.0% (280 of 280 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (279 of 279 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
mondstern
2dce65a448 Translated using Weblate (German)
Currently translated at 98.9% (276 of 279 strings)

Co-authored-by: mondstern <mondstern@snopyta.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2022-05-01 09:18:16 +03:00
Koitharu
3d68d7c818 Reuse PageLoader in PagesThumbnailsSheet 2022-04-29 12:45:28 +03:00
Koitharu
4987d43042 Fix pages saving #151 2022-04-29 11:41:14 +03:00
Koitharu
684b494edb Fix concurrent manga downloading #154 2022-04-29 10:07:21 +03:00
Koitharu
714b708fa9 Fix npe on getExternalFilesDirs #158 2022-04-29 09:04:41 +03:00
Koitharu
c462c19a8b Option to hide 'All categories' tab from favourites 2022-04-28 16:46:55 +03:00
Koitharu
e34acf010e Update parsers 2022-04-23 19:31:02 +03:00
Koitharu
0fb29174c5 Fix webview useragent 2022-04-23 19:31:01 +03:00
Xtimms
ca45774cdb Merge remote-tracking branch 'origin/devel' into devel 2022-04-23 19:16:46 +03:00
Xtimms
cccc2c4fe4 Create issue template 2022-04-23 19:16:15 +03:00
Koitharu
c73af2d45f Update version 2022-04-23 15:37:16 +03:00
lowak
acf7102d07 Translated using Weblate (Swedish)
Currently translated at 100.0% (274 of 274 strings)

Co-authored-by: lowak <lowak@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translation: Kotatsu/Strings
2022-04-23 15:33:11 +03:00
Koitharu
75fcd31758 Fix locking app on screen rotation 2022-04-23 15:31:52 +03:00
Koitharu
7bffb5f22d Select source domains using AutoCompleteTextView 2022-04-23 15:20:57 +03:00
Koitharu
c220bd5517 Merge branch 'feature/direct-download' into devel 2022-04-23 13:57:24 +03:00
Koitharu
7c827b45d5 Merge branch 'feature/improve-list' into devel 2022-04-23 13:55:03 +03:00
Koitharu
e91d9ee38e Fix list selection corners 2022-04-23 13:54:48 +03:00
Koitharu
b6a86a6538 Cache and reuse RemoteMangaRepository instances 2022-04-23 10:39:33 +03:00
Koitharu
16b6b6c071 Add dummy manga parser for development 2022-04-23 10:31:10 +03:00
Xtimms
695feef4a6 Improve simple manga list 2022-04-21 20:15:59 +03:00
Koitharu
6bf4e0cf89 Use PageLoader for thumbnails 2022-04-20 12:57:07 +03:00
Koitharu
44d8d0f246 Fix search keyboard #150 2022-04-20 12:17:32 +03:00
Koitharu
e617e8d6d3 Add password toggle to ProtectActivity 2022-04-20 12:06:49 +03:00
Koitharu
1f411b7530 Cleanup temporary files 2022-04-20 11:50:55 +03:00
Koitharu
d64bd9d9d3 Estimate remeaning download time 2022-04-20 11:18:33 +03:00
Koitharu
f33dc8f797 Update feed ui 2022-04-20 09:25:46 +03:00
Koitharu
e63ae12c8c Delete local chapters in a service 2022-04-19 16:04:24 +03:00
Koitharu
cbd3d439cd Support multiple branches in saved manga 2022-04-19 14:35:02 +03:00
Koitharu
83eb0d9f23 Fix isLoading live data 2022-04-19 12:53:45 +03:00
Koitharu
3c739eed8e Fix empty chapters label 2022-04-19 12:43:51 +03:00
Koitharu
d77646adf1 Fix duplicate zip entry error 2022-04-19 11:32:42 +03:00
Koitharu
5b5e6cba57 Fix download error retry 2022-04-19 11:08:55 +03:00
Koitharu
8fc9b27840 Option to slowdown downloads and configure parallelism 2022-04-19 10:28:05 +03:00
Koitharu
fa536220eb Search and parallelism in LocalMangaRepository.getList 2022-04-19 09:25:12 +03:00
Koitharu
98f16774c4 Delete whole manga if all chapters are removed 2022-04-19 08:22:54 +03:00
Koitharu
ce8f57c3ca Disable update checking if not supported #147 2022-04-18 20:11:23 +03:00
Koitharu
be66106336 Removing selected chapters from local storage 2022-04-18 20:00:43 +03:00
Koitharu
16c8641a07 Fix concurrent downloading #146 2022-04-18 16:46:35 +03:00
Koitharu
d3e9ce874a Download manga to cbz directly 2022-04-18 16:42:37 +03:00
Koitharu
aaf9c6a0bf Update parsers 2022-04-18 09:26:48 +03:00
Koitharu
c2276eb2cb Fix cover size changes in details 2022-04-17 10:24:49 +03:00
Koitharu
5fbae1256b Fix branch detection #143 2022-04-17 09:58:52 +03:00
Koitharu
d61ba80bf6 Add additional checks to download task #50 2022-04-17 09:47:21 +03:00
Koitharu
74c9fa9488 Option to save manga from history and favourites lists 2022-04-17 09:15:16 +03:00
Koitharu
ce732ccca0 Merge branch 'weblate-kotatsu-strings' into devel 2022-04-16 10:05:37 +03:00
Sergio Varela
6b99e360e0 Added translation using Weblate (Basque)
Added translation using Weblate (Basque)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
2022-04-16 09:46:16 +03:00
lowak
1c73d54a94 Translated using Weblate (Swedish)
Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Swedish)

Currently translated at 41.6% (114 of 274 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Swedish)

Added translation using Weblate (Swedish)

Co-authored-by: lowak <lowak@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/sv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-04-16 09:46:16 +03:00
Luiz-bro
36e21caf96 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (274 of 274 strings)

Co-authored-by: Luiz-bro <luiznneto1@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
2022-04-16 09:46:16 +03:00
Santiago José Gutiérrez Llanos
f7f9c53466 Translated using Weblate (Spanish)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (274 of 274 strings)

Co-authored-by: Santiago José Gutiérrez Llanos <gutierrezapata17@gmail.com>
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
2022-04-16 09:46:16 +03:00
Allan Nordhøy
3f2ee2a925 Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Norwegian Bokmål)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/nb_NO/
Translation: Kotatsu/plurals
2022-04-16 09:46:16 +03:00
J. Lavoie
b1c069f62f Translated using Weblate (Italian)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (French)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Finnish)

Currently translated at 99.2% (272 of 274 strings)

Translated using Weblate (French)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (German)

Currently translated at 100.0% (274 of 274 strings)

Added translation using Weblate (Italian)

Added translation using Weblate (German)

Added translation using Weblate (French)

Translated using Weblate (Finnish)

Currently translated at 99.6% (270 of 271 strings)

Translated using Weblate (French)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (German)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (French)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (German)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.8% (266 of 269 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-04-16 09:46:16 +03:00
Luiz-bro
22d48fce8f Added translation using Weblate (Portuguese (Brazil))
Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (268 of 269 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-04-16 09:46:16 +03:00
kuragehime
6b2666c701 Translated using Weblate (Japanese)
Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Japanese)

Translated using Weblate (Japanese)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (269 of 269 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
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
2022-04-16 09:46:16 +03:00
Oğuz Ersen
414f438762 Translated using Weblate (Turkish)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (274 of 274 strings)

Added translation using Weblate (Turkish)

Translated using Weblate (Turkish)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (269 of 269 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
2022-04-16 09:46:16 +03:00
Aliaksiej Razumaŭ
54f60040b5 Translated using Weblate (Belarusian)
Currently translated at 100.0% (270 of 270 strings)

Co-authored-by: Aliaksiej Razumaŭ <belarusaed@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2022-04-16 09:46:16 +03:00
Koitharu
b0515033da Increase version 2022-04-16 09:40:50 +03:00
Koitharu
d37eb07301 Fix startForeground for DownloadService 2022-04-16 09:19:10 +03:00
Sergio Varela
c2fa27712c Added translation using Weblate (Basque)
Added translation using Weblate (Basque)

Co-authored-by: Sergio Varela <sergitroll9@gmail.com>
2022-04-15 12:57:32 +02:00
lowak
10a2589c10 Translated using Weblate (Swedish)
Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Swedish)

Currently translated at 41.6% (114 of 274 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Swedish)

Added translation using Weblate (Swedish)

Co-authored-by: lowak <lowak@pm.me>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/sv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sv/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-04-15 12:57:31 +02:00
Luiz-bro
05eb96e7c0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (274 of 274 strings)

Co-authored-by: Luiz-bro <luiznneto1@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
2022-04-15 12:57:31 +02:00
Santiago José Gutiérrez Llanos
15a08ad6ae Translated using Weblate (Spanish)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (274 of 274 strings)

Co-authored-by: Santiago José Gutiérrez Llanos <gutierrezapata17@gmail.com>
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
2022-04-15 12:57:31 +02:00
Allan Nordhøy
3e437c2ecb Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Norwegian Bokmål)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/nb_NO/
Translation: Kotatsu/plurals
2022-04-15 12:57:30 +02:00
J. Lavoie
fea667b87c Translated using Weblate (Italian)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (German)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (French)

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Finnish)

Currently translated at 99.2% (272 of 274 strings)

Translated using Weblate (French)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (German)

Currently translated at 100.0% (274 of 274 strings)

Added translation using Weblate (Italian)

Added translation using Weblate (German)

Added translation using Weblate (French)

Translated using Weblate (Finnish)

Currently translated at 99.6% (270 of 271 strings)

Translated using Weblate (French)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (German)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Finnish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (French)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Portuguese)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (German)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (269 of 269 strings)

Translated using Weblate (Belarusian)

Currently translated at 98.8% (266 of 269 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-04-15 12:57:30 +02:00
Luiz-bro
93eaaac084 Added translation using Weblate (Portuguese (Brazil))
Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (268 of 269 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-04-15 12:57:29 +02:00
kuragehime
a60df582a2 Translated using Weblate (Japanese)
Currently translated at 100.0% (8 of 8 strings)

Added translation using Weblate (Japanese)

Translated using Weblate (Japanese)

Currently translated at 100.0% (274 of 274 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (269 of 269 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
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
2022-04-15 12:57:29 +02:00
Oğuz Ersen
aec1d4e0d6 Translated using Weblate (Turkish)
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (274 of 274 strings)

Added translation using Weblate (Turkish)

Translated using Weblate (Turkish)

Currently translated at 100.0% (271 of 271 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (269 of 269 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
2022-04-15 12:57:28 +02:00
Aliaksiej Razumaŭ
507f2e883c Translated using Weblate (Belarusian)
Currently translated at 100.0% (270 of 270 strings)

Co-authored-by: Aliaksiej Razumaŭ <belarusaed@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
2022-04-15 12:57:28 +02:00
Koitharu
5e82c75893 Cleanup extensions 2022-04-15 09:09:40 +03:00
Koitharu
9c9a389aa5 Rethrow CancellationException 2022-04-14 08:07:57 +03:00
Koitharu
1b3af70690 Add WindowInsetHolder view 2022-04-14 08:06:16 +03:00
Koitharu
9d1c4bd660 Add more shikimori api methods 2022-04-11 13:17:26 +03:00
Koitharu
3b357eb509 Merge branch 'devel' into feature/shikimori 2022-04-11 11:06:15 +03:00
Koitharu
786914b1a6 Shikimori authorization 2022-03-10 19:54:28 +02:00
572 changed files with 16696 additions and 5254 deletions

29
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,29 @@
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app (https://github.com/KotatsuApp/Kotatsu/releases/latest)
- If this is an issue with a parser, that I should be opening an issue in https://github.com/KotatsuApp/kotatsu-parsers
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## Device information
* Kotatsu version: ?
* Android version: ?
* Device: ?
## Steps to reproduce
1. First step
2. Second step
## Issue/Request
?
## Other details
Additional details and attachments.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: ⚠️ Source issue
url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new
about: If you have troubles with a manga parser or want to propose new manga source, please open an issue in the kotatsu-parsers repository instead

64
.github/ISSUE_TEMPLATE/report_bug.yml vendored Normal file
View File

@@ -0,0 +1,64 @@
name: 🐞 Bug report
description: Report a bug in Kotatsu
labels: [bug]
body:
- type: textarea
id: summary
attributes:
label: Brief summary
description: Please describe, what went wrong
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
label: Steps to reproduce
description: Please provide a way to reproduce this issue. Screenshots or videos can be very helpful
placeholder: |
Example:
1. First step
2. Second step
3. Issue here
validations:
required: false
- type: input
id: kotatsu-version
attributes:
label: Kotatsu version
description: You can find your Kotatsu version in **Settings → About**.
placeholder: |
Example: "3.3"
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: You can find this somewhere in your Android settings.
placeholder: |
Example: "12.0"
validations:
required: true
- type: input
id: device
attributes:
label: Device
description: List your device and model.
placeholder: |
Example: "LG Nexus 5X"
validations:
required: false
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true

View File

@@ -0,0 +1,24 @@
name: ⭐ Feature request
description: Suggest a new idea how to improve Kotatsu
labels: [feature request]
body:
- type: textarea
id: feature-description
attributes:
label: Describe your suggested feature
description: How can Kotatsu be improved?
placeholder: |
Example:
"It should work like this..."
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true

29
.github/workflows/issue_moderator.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
auto-close-rules: |
[
{
"type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed."
},
{
"type": "body",
"regex": ".*\\* (Kotatsu version|Android version|Device): \\?.*",
"message": "Requested information in the template was not filled out."
}
]

5
.gitignore vendored
View File

@@ -6,9 +6,14 @@
/.idea/dictionaries
/.idea/modules.xml
/.idea/misc.xml
/.idea/discord.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/kotlinScripting.xml
/.idea/deploymentTargetDropDown.xml
/.idea/androidTestResultsUserPreferences.xml
/.idea/render.experimental.xml
.DS_Store
/build
/captures

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_API_S.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-02-19T19:02:37.198775Z" />
</component>
</project>

287
.idea/icon.svg generated Normal file
View File

@@ -0,0 +1,287 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="39.689999"
inkscape:export-xdpi="39.689999"
inkscape:export-filename="/home/admin/Documents/projects/graphics/k/icon4.png"
width="512mm"
height="512mm"
viewBox="0 0 512 512.00002"
version="1.1"
id="svg8"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="icon4.svg">
<defs
id="defs2">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter1266">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1256" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1258" />
<feGaussianBlur
in="composite1"
stdDeviation="3"
result="blur"
id="feGaussianBlur1260" />
<feOffset
dx="6"
dy="6"
result="offset"
id="feOffset1262" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite1264" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter1059">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1049" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1051" />
<feGaussianBlur
in="composite1"
stdDeviation="3"
result="blur"
id="feGaussianBlur1053" />
<feOffset
dx="6"
dy="6"
result="offset"
id="feOffset1055" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite1057" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter1071">
<feFlood
flood-opacity="0.498039"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1061" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1063" />
<feGaussianBlur
in="composite1"
stdDeviation="3"
result="blur"
id="feGaussianBlur1065" />
<feOffset
dx="6"
dy="6"
result="offset"
id="feOffset1067" />
<feComposite
in="SourceGraphic"
in2="offset"
operator="over"
result="composite2"
id="feComposite1069" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#0d47a1"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="0.175"
inkscape:cx="-361.03654"
inkscape:cy="630.78782"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
inkscape:document-rotation="0"
showgrid="false"
inkscape:window-width="1600"
inkscape:window-height="838"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
fit-margin-top="20"
fit-margin-left="20"
fit-margin-right="20"
fit-margin-bottom="20" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-51.12025,-104.74797)">
<g
id="g1028"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1030"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1032"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1034"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1036"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1038"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1040"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1042"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1044"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1046"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1048"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1050"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1052"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1054"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<g
id="g1056"
transform="matrix(6.9754464,0,0,6.9754464,32.42507,404.31391)" />
<path
id="path1128"
d="m 307.12025,310.74755 c -50.53732,0 -91.66608,44.85688 -91.66608,99.99965 0,55.14277 41.12954,99.99964 91.66608,99.99964 50.53653,0 91.66607,-44.85687 91.66607,-99.99964 0,-55.14277 -41.12875,-99.99965 -91.66607,-99.99965 z m -34.21238,78.72707 c -1.46484,2.91327 -4.41092,4.60623 -7.45466,4.60623 -1.25312,0 -2.52265,-0.27656 -3.72733,-0.8789 l -12.9398,-6.4781 -12.9398,6.4781 c -4.13436,2.06718 -9.11481,0.37421 -11.18199,-3.72733 -2.05077,-4.11796 -0.39062,-9.11481 3.72733,-11.18199 l 16.66635,-8.33357 c 2.34374,-1.17187 5.11092,-1.17187 7.45466,0 l 16.66635,8.33357 c 4.11951,2.06718 5.77966,7.06403 3.72889,11.18199 z m 58.33338,-24.99991 c -1.46484,2.91327 -4.41092,4.60623 -7.45466,4.60623 -1.25312,0 -2.52264,-0.27656 -3.72733,-0.8789 l -12.93901,-6.47811 -12.9398,6.47811 c -4.13436,2.06718 -9.11481,0.37421 -11.18199,-3.72733 -2.05078,-4.11796 -0.39063,-9.11482 3.72733,-11.182 l 16.66634,-8.33356 c 2.34375,-1.17187 5.11092,-1.17187 7.45466,0 l 16.66635,8.33356 c 4.11874,2.06718 5.77889,7.06404 3.72811,11.182 z m 54.60606,13.81792 c 4.11795,2.06718 5.7781,7.06403 3.72733,11.18199 -1.46484,2.91327 -4.41092,4.60623 -7.45466,4.60623 -1.25312,0 -2.52265,-0.27656 -3.72733,-0.8789 l -12.9398,-6.4781 -12.9398,6.4781 c -4.11795,2.06718 -9.11481,0.37421 -11.18199,-3.72733 -2.05077,-4.11796 -0.39062,-9.11481 3.72733,-11.18199 l 16.66635,-8.33357 c 2.34374,-1.17187 5.11092,-1.17187 7.45466,0 z"
style="fill:#ffffff;stroke-width:0.781247" />
<path
id="path1130"
d="m 415.36283,274.00237 c -3.48202,-6.90544 -6.92029,-13.41714 -10.20934,-19.37102 l -8.26716,-14.47964 c -6.79607,-11.51871 -12.25699,-19.90305 -14.78354,-23.66554 -0.7164,-43.32797 -19.12415,-53.79356 -21.25617,-54.86777 -3.20624,-1.5789 -7.09607,-0.97656 -9.6195,1.56249 -12.25621,12.25621 -20.23118,24.4632 -24.00695,30.89286 h -40.20141 c -3.77577,-6.42888 -11.75153,-18.63665 -24.00695,-30.89286 -2.52265,-2.53905 -6.39685,-3.14139 -9.6195,-1.56249 -2.13202,1.07421 -20.54055,11.5398 -21.25617,54.86777 -2.52655,3.76327 -7.98669,12.14605 -14.78276,23.66476 l -8.27341,14.49214 c -3.28983,5.95701 -6.73044,12.47105 -10.21403,19.3804 l -7.4445,15.32572 c -17.72572,38.05377 -34.30066,85.4286 -34.30066,129.72766 0,69.32085 58.26776,128.42064 60.75838,130.89485 0.91171,0.91172 2.03437,1.61171 3.25546,2.01796 1.07421,0.35859 26.78974,8.757 69.31928,8.757 2.21327,0 4.32967,-0.8789 5.89217,-2.4414 l 5.89216,-5.89216 h 9.76559 l 5.89217,5.89216 c 1.56249,1.5625 3.67811,2.4414 5.89216,2.4414 42.52954,0 68.24507,-8.39841 69.31929,-8.757 1.22109,-0.40703 2.34374,-1.10703 3.25546,-2.01796 2.48905,-2.47421 60.75681,-61.57322 60.75681,-130.89485 0,-44.29906 -16.57494,-91.67389 -34.30066,-129.72766 z M 348.7865,227.41426 c 4.60624,0 4.41171,7.35466 4.41171,11.96089 4.60623,0 12.25464,0.1 12.25464,4.70624 0,9.19606 -7.47107,16.66634 -16.66635,16.66634 -9.19528,0 -16.66634,-7.47028 -16.66634,-16.66634 0,-9.19606 7.47106,-16.66713 16.66634,-16.66713 z m -57.69823,30.14364 c 1.28593,-3.10858 4.32967,-5.14295 7.69841,-5.14295 h 16.66635 c 3.36952,0 6.41248,2.03437 7.69841,5.14295 1.28593,3.10858 0.56953,6.70545 -1.80703,9.082 l -8.33356,8.33356 c -1.62734,1.62734 -3.76014,2.4414 -5.89217,2.4414 -2.13202,0 -4.26404,-0.81406 -5.89216,-2.4414 l -8.33357,-8.33356 c -2.37421,-2.37655 -3.09061,-5.97342 -1.80468,-9.082 z m -25.63428,-30.14364 c 4.60623,0 4.4117,7.35466 4.4117,11.96089 4.60623,0 12.25465,0.1 12.25465,4.70624 0,9.19606 -7.47107,16.66634 -16.66635,16.66634 -9.19606,0 -16.66635,-7.47106 -16.66635,-16.66634 -7.8e-4,-9.19606 7.47029,-16.66713 16.66635,-16.66713 z m 41.66626,299.99893 c -59.7326,0 -108.33321,-52.34357 -108.33321,-116.66599 0,-64.32243 48.60061,-116.66599 108.33321,-116.66599 59.73259,0 108.3332,52.34356 108.3332,116.66599 0,64.32242 -48.60061,116.66599 -108.3332,116.66599 z"
style="fill:#ffffff;stroke-width:0.781247;filter:url(#filter1059)"
sodipodi:nodetypes="cccccccccccccccsccssccssccsccscsssssssssssccsscsscssssss" />
<g
style="fill:#ffffff"
id="g1138"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)"
inkscape:groupmode="layer" />
<g
style="fill:#ffffff"
id="g1140"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1142"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1144"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1146"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1148"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1150"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1152"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1154"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1156"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1158"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1160"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1162"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1164"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<g
style="fill:#ffffff"
id="g1166"
transform="matrix(0.78124721,0,0,0.78124721,107.12096,160.74809)" />
<path
style="fill:#ef5350;fill-opacity:1;stroke:none;stroke-width:5.18208;stroke-linecap:round;stroke-linejoin:round"
d="m 344.3189,392.83707 c -4.60362,-2.75958 -5.36974,-9.69605 -1.45595,-13.18226 0.54459,-0.48508 5.34567,-3.07035 10.66909,-5.74503 7.5498,-3.79328 10.16725,-4.86303 11.89884,-4.86303 1.73503,0 4.42542,1.10391 12.3172,5.05396 11.72559,5.86898 12.60994,6.68326 12.60994,11.61118 0,3.40408 -0.99553,5.20819 -4.00363,7.25549 -3.08358,2.09867 -5.44113,1.68547 -13.60905,-2.38528 l -7.19926,-3.58796 -7.37198,3.59617 c -8.3911,4.09331 -10.26721,4.39753 -13.8552,2.24676 z"
id="path944" />
<path
style="fill:#ef5350;fill-opacity:1;stroke:none;stroke-width:5.18208;stroke-linecap:round;stroke-linejoin:round"
d="m 285.98437,367.98056 c -3.86343,-2.35557 -5.1524,-8.06518 -2.66781,-11.81734 1.64304,-2.48125 20.719,-12.23981 23.92632,-12.23981 1.56364,0 4.61398,1.26582 12.2153,5.06905 8.53551,4.27064 10.3157,5.36752 11.30239,6.96403 1.75651,2.84207 1.95178,5.62136 0.58856,8.37633 -1.52635,3.08463 -3.36973,4.32306 -6.86644,4.61304 -2.68142,0.22236 -3.36743,-0.003 -10.22731,-3.35873 l -7.35311,-3.59707 -7.04119,3.52834 c -7.90523,3.96133 -10.62609,4.44409 -13.87671,2.46216 z"
id="path946" />
<path
style="fill:#ef5350;fill-opacity:1;stroke:none;stroke-width:5.18208;stroke-linecap:round;stroke-linejoin:round"
d="m 228.11707,393.18031 c -1.0244,-0.54435 -2.42484,-1.80721 -3.11209,-2.80633 -1.05812,-1.53828 -1.2181,-2.32693 -1.04433,-5.14815 0.29039,-4.71472 1.41139,-5.70783 12.90113,-11.42937 7.71258,-3.84061 9.99443,-4.74971 11.92193,-4.74971 1.94819,0 4.22735,0.92952 12.47354,5.08716 8.66324,4.3679 10.26522,5.3693 11.33052,7.08263 3.53608,5.68714 -0.55313,12.95355 -7.28968,12.95355 -1.25225,0 -4.29453,-1.20187 -9.08226,-3.58799 l -7.19927,-3.58796 -7.37197,3.59617 c -8.07507,3.93914 -10.21699,4.34922 -13.52752,2.59 z"
id="path948" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,8 +1,13 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="BooleanLiteralArgument" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="BooleanLiteralArgument" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="Destructure" enabled="true" level="INFO" enabled_by_default="true" />
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="KotlinFunctionArgumentsHelper" enabled="true" level="INFORMATION" enabled_by_default="true">
<option name="withoutDefaultValues" value="true" />
</inspection_tool>
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="TrailingComma" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="quality" value="0.25" />
</component>
</project>

View File

@@ -1,11 +0,0 @@
language: android
dist: trusty
android:
components:
- android-30
- build-tools-30.0.3
- platform-tools-30.0.5
- tools
before_install:
- yes | sdkmanager "platforms;android-30"
script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug

View File

@@ -1,8 +1,8 @@
# Kotatsu
# Kotatsu
Kotatsu is a free and open source manga reader for Android.
![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) ![Kotlin](https://img.shields.io/github/languages/top/nv95/Kotatsu) [![Build Status](https://travis-ci.org/nv95/Kotatsu.svg?branch=master)](https://travis-ci.org/nv95/Kotatsu) ![License](https://img.shields.io/github/license/nv95/Kotatsu) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![4pda](https://img.shields.io/badge/discuss-4pda-2982CC)](http://4pda.ru/forum/index.php?showtopic=697669) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5)
![Android 5.0](https://img.shields.io/badge/android-5.0+-brightgreen) ![Kotlin](https://img.shields.io/github/languages/top/KotatsuApp/Kotatsu) ![License](https://img.shields.io/github/license/KotatsuApp/Kotatsu) [![weblate](https://hosted.weblate.org/widgets/kotatsu/-/strings/svg-badge.svg)](https://hosted.weblate.org/engage/kotatsu/) [![4pda](https://img.shields.io/badge/discuss-4pda-2982CC)](http://4pda.ru/forum/index.php?showtopic=697669) [![Discord](https://img.shields.io/discord/898363402467045416?color=5865f2&label=discord)](https://discord.gg/NNJ5RgVBC5)
### Download
@@ -10,50 +10,48 @@ Kotatsu is a free and open source manga reader for Android.
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/org.koitharu.kotatsu)
Download APK from Github Releases:
Download APK directly from GitHub:
- [Latest release](https://github.com/nv95/Kotatsu/releases/latest)
- [Legacy build](https://github.com/nv95/Kotatsu/releases/tag/v0.4-legacy) (with Android 4.1+ support)
- **[Latest release](https://github.com/KotatsuApp/Kotatsu/releases/latest)**
### Main Features
* Online manga catalogues
* Search manga by name and genre
* Reading history
* Search manga by name and genres
* Reading history and bookmarks
* Favourites organized by user-defined categories
* Downloading manga and reading it offline. Third-party CBZ archives also supported
* Tablet-optimized material design UI
* Standard and Webtoon-optimized reader
* Notifications about new chapters with updates feed
* Available in multiple languages
* Password protect access to the app
* Shikimori integration (manga tracking)
* Password/fingerprint protect access to the app
### Screenshots
| ![Screenshot_20200226-210337](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/1.png) | ![](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/2.png) | ![Screenshot_20200226-210232](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/3.png) |
|-----------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
| ![Screenshot_20200226-210405](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/4.png) | ![Screenshot_20200226-210151](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/5.png) | ![Screenshot_20200226-210223](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/6.png) |
| ![Screenshot_20200226-210337](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/1.png) | ![](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/2.png) | ![Screenshot_20200226-210232](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/3.png) |
|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| ![Screenshot_20200226-210405](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/4.png) | ![Screenshot_20200226-210151](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/5.png) | ![Screenshot_20200226-210223](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/phoneScreenshots/6.png) |
| ![](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/1.png) | ![](https://github.com/nv95/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/2.png) |
|-----------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------|
| ![](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/1.png) | ![](https://github.com/KotatsuApp/Kotatsu/raw/devel/metadata/en-US/images/tenInchScreenshots/2.png) |
|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
### Localization
<a href="https://hosted.weblate.org/engage/kotatsu/">
<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status" />
</a>
[<img src="https://hosted.weblate.org/widgets/kotatsu/-/287x66-white.png" alt="Translation status">](https://hosted.weblate.org/engage/kotatsu/)
Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages, please head over to the Weblate <a href="https://hosted.weblate.org/engage/kotatsu/">project page</a>
Kotatsu is localized in a number of different languages, if you would like to help improve these or add new languages,
please head over to the [Weblate project page](https://hosted.weblate.org/engage/kotatsu/)
### License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
Kotatsu is Free Software: You can use, study share and improve it at your
will. Specifically you can redistribute and/or modify it under the terms of the
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as
published by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html)
### Disclaimer
You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications
to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build &
install instructions.
The developers of this application does not have any affiliation with the content providers available.
### DMCA disclaimer
The developers of this application does not have any affiliation with the content available in the app.
It is collecting from the sources freely available through any web browser.

View File

@@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 32
versionCode 401
versionName '3.1'
versionCode 420
versionName '3.4.8'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -24,6 +24,10 @@ android {
arg 'room.schemaLocation', "$projectDir/schemas".toString()
}
}
// define this values in your local.properties file
buildConfigField 'String', 'SHIKIMORI_CLIENT_ID', "\"${localProperty('shikimori.clientId')}\""
buildConfigField 'String', 'SHIKIMORI_CLIENT_SECRET', "\"${localProperty('shikimori.clientSecret')}\""
}
buildTypes {
debug {
@@ -49,71 +53,90 @@ android {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += [
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
'-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi',
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
'-opt-in=coil.annotation.ExperimentalCoilApi',
]
}
lint {
abortOnError false
disable 'MissingTranslation', 'PrivateResource'
disable 'MissingTranslation', 'PrivateResource', 'NotifyDataSetChanged'
}
testOptions {
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = false
unitTests.includeAndroidResources true
unitTests.returnDefaultValues false
kotlinOptions {
freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
}
}
}
afterEvaluate {
compileDebugKotlin {
kotlinOptions {
freeCompilerArgs += ['-opt-in=org.koitharu.kotatsu.parsers.InternalParsersApi']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:8e23a7fcd4') {
implementation('com.github.KotatsuApp:kotatsu-parsers:85bfe42ddf') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
implementation 'androidx.lifecycle:lifecycle-service:2.4.1'
implementation 'androidx.lifecycle:lifecycle-process:2.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.activity:activity-ktx:1.5.0'
implementation 'androidx.fragment:fragment-ktx:1.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-service:2.5.0'
implementation 'androidx.lifecycle:lifecycle-process:2.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'com.google.android.material:material:1.6.0-beta01'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.7.0-alpha03'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.4.1'
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0'
implementation 'androidx.room:room-runtime:2.4.2'
implementation 'androidx.room:room-ktx:2.4.2'
kapt 'androidx.room:room-compiler:2.4.2'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okio:okio:3.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.squareup.okio:okio:3.2.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation 'io.insert-koin:koin-android:3.1.6'
implementation 'io.coil-kt:coil-base:1.4.0'
implementation 'io.insert-koin:koin-android:3.2.0'
implementation 'io.coil-kt:coil-base:2.1.0'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.github.solkin:disk-lru-cache:1.4'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
implementation 'ch.acra:acra-mail:5.9.5'
implementation 'ch.acra:acra-dialog:5.9.5'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
testImplementation 'io.insert-koin:koin-test-junit4:3.1.5'
testImplementation 'org.json:json:20220320'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:core-ktx:1.4.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'io.insert-koin:koin-test:3.2.0'
androidTestImplementation 'io.insert-koin:koin-test-junit4:3.2.0'
androidTestImplementation 'androidx.room:room-testing:2.4.2'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
}

View File

@@ -0,0 +1,8 @@
{
"id": 4,
"title": "Read later",
"sortKey": 1,
"order": "NEWEST",
"createdAt": 1335906000000,
"isTrackingEnabled": true
}

View File

@@ -0,0 +1,163 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 1552943969433540704,
"name": "1 - 1",
"number": 1,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433540705,
"name": "1 - 2",
"number": 2,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433540706,
"name": "1 - 3",
"number": 3,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433540707,
"name": "1 - 4",
"number": 4,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433540708,
"name": "1 - 5",
"number": 5,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541665,
"name": "2 - 1",
"number": 6,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541666,
"name": "2 - 2",
"number": 7,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541667,
"name": "2 - 3",
"number": 8,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541668,
"name": "2 - 4",
"number": 9,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541669,
"name": "2 - 5",
"number": 10,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433541670,
"name": "2 - 6",
"number": 11,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433542626,
"name": "3 - 1",
"number": 12,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433542627,
"name": "3 - 2",
"number": 13,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 1552943969433542628,
"name": "3 - 3",
"number": 14,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
"source": "READMANGA_RU"
}
],
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,36 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [],
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,136 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"number": 1,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"number": 2,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"number": 3,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"number": 4,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"number": 5,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541665,
"name": "2 - 1",
"number": 6,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"number": 7,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"number": 8,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"number": 9,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"number": 10,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"number": 11,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
"source": "READMANGA_RU"
}
],
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,163 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"number": 1,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"number": 2,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"number": 3,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"number": 4,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"number": 5,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541665,
"name": "2 - 1",
"number": 6,
"url": "/stranstviia_emanon/vol2/1",
"scanlator": "Sup!",
"uploadDate": 1415570400000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"number": 7,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"number": 8,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"number": 9,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"number": 10,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"number": 11,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542626,
"name": "3 - 1",
"number": 12,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542627,
"name": "3 - 2",
"number": 13,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542628,
"name": "3 - 3",
"number": 14,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
"source": "READMANGA_RU"
}
],
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,35 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": null,
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,154 @@
{
"id": -2096681732556647985,
"title": "Странствия Эманон",
"url": "/stranstviia_emanon",
"publicUrl": "https://readmanga.io/stranstviia_emanon",
"rating": 0.9400894,
"isNsfw": true,
"coverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_p.jpg",
"tags": [
{
"title": "Сверхъестественное",
"key": "supernatural",
"source": "READMANGA_RU"
},
{
"title": "Сэйнэн",
"key": "seinen",
"source": "READMANGA_RU"
},
{
"title": "Повседневность",
"key": "slice_of_life",
"source": "READMANGA_RU"
},
{
"title": "Приключения",
"key": "adventure",
"source": "READMANGA_RU"
}
],
"state": "FINISHED",
"largeCoverUrl": "https://staticrm.rmr.rocks/uploads/pics/01/12/559_o.jpg",
"description": "Продолжение истории о загадочной девушке по имени Эманон, которая помнит всё, что происходило на Земле за последние три миллиарда лет. \n<br>Начало истории читайте в \"Воспоминаниях Эманон\". \n<div class=\"clearfix\"></div>",
"chapters": [
{
"id": 3552943969433540704,
"name": "1 - 1",
"number": 1,
"url": "/stranstviia_emanon/vol1/1",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540705,
"name": "1 - 2",
"number": 2,
"url": "/stranstviia_emanon/vol1/2",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540706,
"name": "1 - 3",
"number": 3,
"url": "/stranstviia_emanon/vol1/3",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540707,
"name": "1 - 4",
"number": 4,
"url": "/stranstviia_emanon/vol1/4",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433540708,
"name": "1 - 5",
"number": 5,
"url": "/stranstviia_emanon/vol1/5",
"scanlator": "Sad-Robot",
"uploadDate": 1342731600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541666,
"name": "2 - 2",
"number": 7,
"url": "/stranstviia_emanon/vol2/2",
"scanlator": "Sup!",
"uploadDate": 1419976800000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541667,
"name": "2 - 3",
"number": 8,
"url": "/stranstviia_emanon/vol2/3",
"scanlator": "Sup!",
"uploadDate": 1427922000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541668,
"name": "2 - 4",
"number": 9,
"url": "/stranstviia_emanon/vol2/4",
"scanlator": "Sup!",
"uploadDate": 1436907600000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541669,
"name": "2 - 5",
"number": 10,
"url": "/stranstviia_emanon/vol2/5",
"scanlator": "Sup!",
"uploadDate": 1446674400000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433541670,
"name": "2 - 6",
"number": 11,
"url": "/stranstviia_emanon/vol2/6",
"scanlator": "Sup!",
"uploadDate": 1451512800000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542626,
"name": "3 - 1",
"number": 12,
"url": "/stranstviia_emanon/vol3/1",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542627,
"name": "3 - 2",
"number": 13,
"url": "/stranstviia_emanon/vol3/2",
"scanlator": "Sup!",
"uploadDate": 1461618000000,
"source": "READMANGA_RU"
},
{
"id": 3552943969433542628,
"name": "3 - 3",
"number": 14,
"url": "/stranstviia_emanon/vol3/3",
"scanlator": "",
"uploadDate": 1465851600000,
"source": "READMANGA_RU"
}
],
"source": "READMANGA_RU"
}

View File

@@ -0,0 +1,9 @@
package org.koitharu.kotatsu
import android.app.Instrumentation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun Instrumentation.awaitForIdle() = suspendCoroutine<Unit> { cont ->
waitForIdle { cont.resume(Unit) }
}

View File

@@ -0,0 +1,54 @@
package org.koitharu.kotatsu
import androidx.test.platform.app.InstrumentationRegistry
import com.squareup.moshi.*
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okio.buffer
import okio.source
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.*
import kotlin.reflect.KClass
object SampleData {
private val moshi = Moshi.Builder()
.add(DateAdapter())
.add(KotlinJsonAdapterFactory())
.build()
val manga: Manga = loadAsset("manga/header.json", Manga::class)
val mangaDetails: Manga = loadAsset("manga/full.json", Manga::class)
val tag = mangaDetails.tags.elementAt(2)
val chapter = checkNotNull(mangaDetails.chapters)[2]
val favouriteCategory: FavouriteCategory = loadAsset("categories/simple.json", FavouriteCategory::class)
fun <T : Any> loadAsset(name: String, cls: KClass<T>): T {
val assets = InstrumentationRegistry.getInstrumentation().context.assets
return assets.open(name).use {
moshi.adapter(cls.java).fromJson(it.source().buffer())
} ?: throw RuntimeException("Cannot read asset from json \"$name\"")
}
private class DateAdapter : JsonAdapter<Date>() {
@FromJson
override fun fromJson(reader: JsonReader): Date? {
val ms = reader.nextLong()
return if (ms == 0L) {
null
} else {
Date(ms)
}
}
@ToJson
override fun toJson(writer: JsonWriter, value: Date?) {
writer.value(value?.time ?: 0L)
}
}
}

View File

@@ -1,14 +1,12 @@
package org.koitharu.kotatsu.core.db
import androidx.room.testing.MigrationTestHelper
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.koitharu.kotatsu.core.db.migrations.*
import java.io.IOException
import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
class MangaDatabaseTest {
@@ -16,40 +14,44 @@ class MangaDatabaseTest {
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
MangaDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
MangaDatabase::class.java,
)
private val migrations = databaseMigrations
@Test
@Throws(IOException::class)
fun migrateAll() {
helper.createDatabase(TEST_DB, 1).apply {
// TODO execSQL("")
close()
fun versions() {
assertEquals(1, migrations.first().startVersion)
repeat(migrations.size) { i ->
assertEquals(i + 1, migrations[i].startVersion)
assertEquals(i + 2, migrations[i].endVersion)
}
assertEquals(DATABASE_VERSION, migrations.last().endVersion)
}
@Test
fun migrateAll() {
helper.createDatabase(TEST_DB, 1).close()
for (migration in migrations) {
helper.runMigrationsAndValidate(
TEST_DB,
migration.endVersion,
true,
migration
)
).close()
}
}
@Test
fun prePopulate() {
val resources = InstrumentationRegistry.getInstrumentation().targetContext.resources
helper.createDatabase(TEST_DB, DATABASE_VERSION).use {
DatabasePrePopulateCallback(resources).onCreate(it)
}
}
private companion object {
const val TEST_DB = "test-db"
val migrations = arrayOf(
Migration1To2(),
Migration2To3(),
Migration3To4(),
Migration4To5(),
Migration5To6(),
Migration6To7(),
Migration7To8(),
)
}
}
}

View File

@@ -0,0 +1,65 @@
package org.koitharu.kotatsu.core.os
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.os.Build
import androidx.core.content.getSystemService
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.inject
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.awaitForIdle
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.history.domain.HistoryRepository
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
class ShortcutsUpdaterTest : KoinTest {
private val historyRepository by inject<HistoryRepository>()
private val shortcutsUpdater by inject<ShortcutsUpdater>()
private val database by inject<MangaDatabase>()
@Before
fun setUp() {
database.clearAllTables()
}
@Test
fun testUpdateShortcuts() = runTest {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) {
return@runTest
}
awaitUpdate()
assertTrue(getShortcuts().isEmpty())
historyRepository.addOrUpdate(
manga = SampleData.manga,
chapterId = SampleData.chapter.id,
page = 4,
scroll = 2,
percent = 0.3f
)
awaitUpdate()
val shortcuts = getShortcuts()
assertEquals(1, shortcuts.size)
}
private fun getShortcuts(): List<ShortcutInfo> {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val manager = checkNotNull(context.getSystemService<ShortcutManager>())
return manager.dynamicShortcuts.filterNot { it.id == "com.squareup.leakcanary.dynamic_shortcut" }
}
private suspend fun awaitUpdate() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
instrumentation.awaitForIdle()
shortcutsUpdater.await()
}
}

View File

@@ -0,0 +1,67 @@
package org.koitharu.kotatsu.settings.backup
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.get
import org.koin.test.inject
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import kotlin.test.*
@RunWith(AndroidJUnit4::class)
class AppBackupAgentTest : KoinTest {
private val historyRepository by inject<HistoryRepository>()
private val favouritesRepository by inject<FavouritesRepository>()
private val backupRepository by inject<BackupRepository>()
private val database by inject<MangaDatabase>()
@Before
fun setUp() {
database.clearAllTables()
}
@Test
fun testBackupRestore() = runTest {
val category = favouritesRepository.createCategory(
title = SampleData.favouriteCategory.title,
sortOrder = SampleData.favouriteCategory.order,
isTrackerEnabled = SampleData.favouriteCategory.isTrackingEnabled,
)
favouritesRepository.addToCategory(categoryId = category.id, mangas = listOf(SampleData.manga))
historyRepository.addOrUpdate(
manga = SampleData.mangaDetails,
chapterId = SampleData.mangaDetails.chapters!![2].id,
page = 3,
scroll = 40,
percent = 0.2f,
)
val history = checkNotNull(historyRepository.getOne(SampleData.manga))
val agent = AppBackupAgent()
val backup = agent.createBackupFile(get(), backupRepository)
database.clearAllTables()
assertTrue(favouritesRepository.getAllManga().isEmpty())
assertNull(historyRepository.getLastOrNull())
backup.inputStream().use {
agent.restoreBackupFile(it.fd, backup.length(), backupRepository)
}
assertEquals(category, favouritesRepository.getCategory(category.id))
assertEquals(history, historyRepository.getOne(SampleData.manga))
assertContentEquals(listOf(SampleData.manga), favouritesRepository.getManga(category.id))
val allTags = database.tagsDao.findTags(SampleData.tag.source.name).toMangaTags()
assertContains(allTags, SampleData.tag)
}
}

View File

@@ -0,0 +1,183 @@
package org.koitharu.kotatsu.tracker.domain
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.test.KoinTest
import org.koin.test.inject
import org.koitharu.kotatsu.SampleData
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.parsers.model.Manga
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
class TrackerTest : KoinTest {
private val repository by inject<TrackingRepository>()
private val dataRepository by inject<MangaDataRepository>()
private val tracker by inject<Tracker>()
@Test
fun noUpdates() = runTest {
val manga = loadManga("full.json")
tracker.deleteTrack(manga.id)
tracker.checkUpdates(manga, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(manga.id))
tracker.checkUpdates(manga, commit = true).apply {
assertTrue(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(manga.id))
}
@Test
fun hasUpdates() = runTest {
val mangaFirst = loadManga("first_chapters.json")
val mangaFull = loadManga("full.json")
tracker.deleteTrack(mangaFirst.id)
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assertEquals(3, newChapters.size)
}
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assert(newChapters.isEmpty())
}
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
}
@Test
fun badIds() = runTest {
val mangaFirst = loadManga("first_chapters.json")
val mangaBad = loadManga("bad_ids.json")
tracker.deleteTrack(mangaFirst.id)
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaBad, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
}
@Test
fun badIds2() = runTest {
val mangaFirst = loadManga("first_chapters.json")
val mangaBad = loadManga("bad_ids.json")
val mangaFull = loadManga("full.json")
tracker.deleteTrack(mangaFirst.id)
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assertEquals(3, newChapters.size)
}
assertEquals(3, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaBad, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
}
@Test
fun fullReset() = runTest {
val mangaFull = loadManga("full.json")
val mangaFirst = loadManga("first_chapters.json")
val mangaEmpty = loadManga("empty.json")
tracker.deleteTrack(mangaFull.id)
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaEmpty, commit = true).apply {
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assertEquals(3, newChapters.size)
}
assertEquals(3, repository.getNewChaptersCount(mangaFull.id))
tracker.checkUpdates(mangaEmpty, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFull.id))
}
@Test
fun syncWithHistory() = runTest {
val mangaFull = loadManga("full.json")
val mangaFirst = loadManga("first_chapters.json")
tracker.deleteTrack(mangaFull.id)
tracker.checkUpdates(mangaFirst, commit = true).apply {
assertFalse(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assertEquals(3, newChapters.size)
}
assertEquals(3, repository.getNewChaptersCount(mangaFirst.id))
var chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex - 1) }
repository.syncWithHistory(mangaFull, chapter.id)
assertEquals(1, repository.getNewChaptersCount(mangaFirst.id))
chapter = requireNotNull(mangaFull.chapters).run { get(lastIndex) }
repository.syncWithHistory(mangaFull, chapter.id)
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
tracker.checkUpdates(mangaFull, commit = true).apply {
assertTrue(isValid)
assert(newChapters.isEmpty())
}
assertEquals(0, repository.getNewChaptersCount(mangaFirst.id))
}
private suspend fun loadManga(name: String): Manga {
val manga = SampleData.loadAsset("manga/$name", Manga::class)
dataRepository.storeManga(manga)
return manga
}
}

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.core.parser
import java.util.*
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.*
/**
* This parser is just for parser development, it should not be used in releases
*/
class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("", null)
override val sortOrders: 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> {
TODO("Not yet implemented")
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
TODO("Not yet implemented")
}
override suspend fun getTags(): Set<MangaTag> {
TODO("Not yet implemented")
}
}

View File

@@ -0,0 +1,14 @@
package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.newParser
fun MangaParser(source: MangaSource, loaderContext: MangaLoaderContext): MangaParser {
return if (source == MangaSource.DUMMY) {
DummyParser(loaderContext)
} else {
source.newParser(loaderContext)
}
}

View File

@@ -0,0 +1,3 @@
package org.koitharu.kotatsu.utils.ext
fun Throwable.printStackTraceDebug() = printStackTrace()

View File

@@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application
android:name="org.koitharu.kotatsu.KotatsuApp"
@@ -53,11 +54,20 @@
<activity
android:name="org.koitharu.kotatsu.search.ui.SearchActivity"
android:label="@string/search" />
<activity android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
<activity
android:name="org.koitharu.kotatsu.search.ui.MangaListActivity"
android:label="@string/search_manga" />
<activity
android:name="org.koitharu.kotatsu.settings.SettingsActivity"
android:label="@string/settings" />
android:exported="true"
android:label="@string/settings">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="kotatsu" />
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
@@ -66,11 +76,6 @@
android:name="org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.core.ui.CrashActivity"
android:label="@string/error_occurred"
android:theme="@android:style/Theme.DeviceDefault"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity"
android:label="@string/favourites_categories"
@@ -78,13 +83,14 @@
<activity
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
android:exported="true"
android:label="@string/manga_shelf">
android:label="@string/manga_shelf"
android:theme="@style/Theme.Kotatsu.DialogWhenLarge">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity
android:name="org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity"
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity"
android:label="@string/search" />
<activity
android:name="org.koitharu.kotatsu.main.ui.protect.ProtectActivity"
@@ -95,13 +101,18 @@
android:windowSoftInputMode="adjustResize" />
<activity
android:name="org.koitharu.kotatsu.download.ui.DownloadsActivity"
android:label="@string/downloads"
android:launchMode="singleTop"
android:label="@string/downloads" />
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity"/>
android:theme="@style/Theme.Kotatsu.DialogWhenLarge" />
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity" />
<activity
android:name="org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity"
android:theme="@style/Theme.Kotatsu.DialogWhenLarge" />
<service
android:name="org.koitharu.kotatsu.download.ui.service.DownloadService"
android:foregroundServiceType="dataSync" />
<service android:name="org.koitharu.kotatsu.local.ui.LocalChaptersRemoveService" />
<service
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS" />

View File

@@ -1,17 +1,26 @@
package org.koitharu.kotatsu
import android.app.Application
import android.content.Context
import android.os.StrictMode
import androidx.appcompat.app.AppCompatDelegate
import androidx.fragment.app.strictmode.FragmentStrictMode
import androidx.room.InvalidationTracker
import org.acra.ReportField
import org.acra.config.dialog
import org.acra.config.mailSender
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.koin.android.ext.android.get
import org.koin.android.ext.android.getKoin
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
import org.koitharu.kotatsu.bookmarks.bookmarksModule
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.databaseModule
import org.koitharu.kotatsu.core.github.githubModule
import org.koitharu.kotatsu.core.network.networkModule
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.AppCrashHandler
import org.koitharu.kotatsu.core.ui.uiModule
import org.koitharu.kotatsu.details.detailsModule
import org.koitharu.kotatsu.favourites.favouritesModule
@@ -20,15 +29,14 @@ import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.local.localModule
import org.koitharu.kotatsu.main.mainModule
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.reader.readerModule
import org.koitharu.kotatsu.remotelist.remoteListModule
import org.koitharu.kotatsu.scrobbling.shikimori.shikimoriModule
import org.koitharu.kotatsu.search.searchModule
import org.koitharu.kotatsu.settings.settingsModule
import org.koitharu.kotatsu.suggestions.suggestionsModule
import org.koitharu.kotatsu.tracker.trackerModule
import org.koitharu.kotatsu.widget.WidgetUpdater
import org.koitharu.kotatsu.widget.appWidgetModule
class KotatsuApp : Application() {
@@ -39,12 +47,9 @@ class KotatsuApp : Application() {
enableStrictMode()
}
initKoin()
Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext))
AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme)
registerActivityLifecycleCallbacks(get<AppProtectHelper>())
val widgetUpdater = WidgetUpdater(applicationContext)
widgetUpdater.subscribeToFavourites(get())
widgetUpdater.subscribeToHistory(get())
setupActivityLifecycleCallbacks()
setupDatabaseObservers()
}
private fun initKoin() {
@@ -67,10 +72,58 @@ class KotatsuApp : Application() {
readerModule,
appWidgetModule,
suggestionsModule,
shikimoriModule,
bookmarksModule
)
}
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
initAcra {
buildConfigClass = BuildConfig::class.java
reportFormat = StringFormat.KEY_VALUE_LIST
reportContent = listOf(
ReportField.PACKAGE_NAME,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,
ReportField.PHONE_MODEL,
ReportField.CRASH_CONFIGURATION,
ReportField.STACK_TRACE,
ReportField.SHARED_PREFERENCES
)
dialog {
text = getString(R.string.crash_text)
title = getString(R.string.error_occurred)
positiveButtonText = getString(R.string.send)
resIcon = R.drawable.ic_alert_outline
resTheme = android.R.style.Theme_Material_Light_Dialog_Alert
}
mailSender {
mailTo = getString(R.string.email_error_report)
reportAsFile = true
reportFileName = "stacktrace.txt"
}
}
}
private fun setupDatabaseObservers() {
val observers = getKoin().getAll<InvalidationTracker.Observer>()
val database = get<MangaDatabase>()
val tracker = database.invalidationTracker
observers.forEach {
tracker.addObserver(it)
}
}
private fun setupActivityLifecycleCallbacks() {
val callbacks = getKoin().getAll<ActivityLifecycleCallbacks>()
callbacks.forEach {
registerActivityLifecycleCallbacks(it)
}
}
private fun enableStrictMode() {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()

View File

@@ -9,54 +9,58 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.medianOrNull
import java.io.File
import java.io.InputStream
import java.util.zip.ZipFile
import kotlin.math.roundToInt
object MangaUtils : KoinComponent {
private const val MIN_WEBTOON_RATIO = 2
/**
* Automatic determine type of manga by page size
* @return ReaderMode.WEBTOON if page is wide
*/
suspend fun determineMangaIsWebtoon(pages: List<MangaPage>): Boolean? {
try {
val page = pages.medianOrNull() ?: return null
val url = MangaRepository(page.source).getPageUrl(page)
val uri = Uri.parse(url)
val size = if (uri.scheme == "cbz") {
suspend fun determineMangaIsWebtoon(pages: List<MangaPage>): Boolean {
val pageIndex = (pages.size * 0.3).roundToInt()
val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" }
val url = MangaRepository(page.source).getPageUrl(page)
val uri = Uri.parse(url)
val size = if (uri.scheme == "cbz") {
runInterruptible(Dispatchers.IO) {
val zip = ZipFile(uri.schemeSpecificPart)
val entry = zip.getEntry(uri.fragment)
zip.getInputStream(entry).use {
getBitmapSize(it)
}
}
} else {
val request = Request.Builder()
.url(url)
.get()
.header(CommonHeaders.REFERER, page.referer)
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.build()
get<OkHttpClient>().newCall(request).await().use {
runInterruptible(Dispatchers.IO) {
val zip = ZipFile(uri.schemeSpecificPart)
val entry = zip.getEntry(uri.fragment)
zip.getInputStream(entry).use {
getBitmapSize(it)
}
}
} else {
val request = Request.Builder()
.url(url)
.get()
.header(CommonHeaders.REFERER, page.referer)
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.build()
get<OkHttpClient>().newCall(request).await().use {
runInterruptible(Dispatchers.IO) {
getBitmapSize(it.body?.byteStream())
}
getBitmapSize(it.body?.byteStream())
}
}
return size.width * 2 < size.height
} catch (e: Exception) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
return null
}
return size.width * MIN_WEBTOON_RATIO < size.height
}
suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(file.path, options)?.recycle()
options.outMimeType
}
private fun getBitmapSize(input: InputStream?): Size {
@@ -69,4 +73,4 @@ object MangaUtils : KoinComponent {
check(imageHeight > 0 && imageWidth > 0)
return Size(imageWidth, imageHeight)
}
}
}

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.base.domain
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
fun interface ReversibleHandle {
suspend fun reverse()
}
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default) {
reverse()
}
operator fun ReversibleHandle.plus(other: ReversibleHandle) = ReversibleHandle {
this.reverse()
other.reverse()
}

View File

@@ -26,7 +26,8 @@ import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings
abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(),
abstract class BaseActivity<B : ViewBinding> :
AppCompatActivity(),
WindowInsetsDelegate.WindowInsetsListener {
protected lateinit var binding: B
@@ -42,9 +43,13 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(),
override fun onCreate(savedInstanceState: Bundle?) {
val settings = get<AppSettings>()
val isAmoled = settings.isAmoledTheme
val isDynamic = settings.isDynamicTheme
// TODO support DialogWhenLarge theme
when {
settings.isAmoledTheme -> setTheme(R.style.ThemeOverlay_Kotatsu_AMOLED)
settings.isDynamicTheme -> setTheme(R.style.Theme_Kotatsu_Monet)
isAmoled && isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet_Amoled)
isAmoled -> setTheme(R.style.Theme_Kotatsu_Amoled)
isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet)
}
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
@@ -123,4 +128,4 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(),
super.onBackPressed()
}
}
}
}

View File

@@ -2,18 +2,20 @@ package org.koitharu.kotatsu.base.ui
import android.app.Dialog
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import androidx.appcompat.app.AppCompatDialog
import androidx.core.view.updateLayoutParams
import androidx.viewbinding.ViewBinding
import com.google.android.material.R as materialR
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
import org.koitharu.kotatsu.utils.ext.displayCompat
import com.google.android.material.R as materialR
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
@@ -32,6 +34,20 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
): View {
val binding = onInflateView(inflater, container)
viewBinding = binding
// Enforce max width for tablets
val width = resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
if (width > 0) {
behavior?.maxWidth = width
}
// Set peek height to 50% display height
requireContext().displayCompat?.let {
val metrics = DisplayMetrics()
it.getRealMetrics(metrics)
behavior?.peekHeight = (metrics.heightPixels * 0.4).toInt()
}
return binding.root
}
@@ -41,9 +57,16 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return if (resources.getBoolean(R.bool.is_tablet)) {
AppCompatDialog(context, R.style.Theme_Kotatsu_Dialog)
} else super.onCreateDialog(savedInstanceState)
return AppBottomSheetDialog(requireContext(), theme)
}
fun addBottomSheetCallback(callback: BottomSheetBehavior.BottomSheetCallback) {
val b = behavior ?: return
b.addBottomSheetCallback(callback)
val rootView = dialog?.findViewById<View>(materialR.id.design_bottom_sheet)
if (rootView != null) {
callback.onStateChanged(rootView, b.state)
}
}
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B

View File

@@ -7,10 +7,12 @@ import android.view.View
import android.view.WindowManager
import androidx.viewbinding.ViewBinding
@Suppress("DEPRECATION")
private const val SYSTEM_UI_FLAGS_SHOWN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@Suppress("DEPRECATION")
private const val SYSTEM_UI_FLAGS_HIDDEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
@@ -18,7 +20,8 @@ private const val SYSTEM_UI_FLAGS_HIDDEN = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_FULLSCREEN or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
abstract class BaseFullscreenActivity<B : ViewBinding> : BaseActivity<B>(),
abstract class BaseFullscreenActivity<B : ViewBinding> :
BaseActivity<B>(),
View.OnSystemUiVisibilityChangeListener {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -35,16 +38,19 @@ abstract class BaseFullscreenActivity<B : ViewBinding> : BaseActivity<B>(),
showSystemUI()
}
@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
@Deprecated("Deprecated in Java")
final override fun onSystemUiVisibilityChange(visibility: Int) {
onSystemUiVisibilityChanged(visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0)
}
// TODO WindowInsetsControllerCompat works incorrect
@Suppress("DEPRECATION")
protected fun hideSystemUI() {
window.decorView.systemUiVisibility = SYSTEM_UI_FLAGS_HIDDEN
}
@Suppress("DEPRECATION")
protected fun showSystemUI() {
window.decorView.systemUiVisibility = SYSTEM_UI_FLAGS_SHOWN
}

View File

@@ -6,14 +6,12 @@ import androidx.annotation.CallSuper
import androidx.annotation.StringRes
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :

View File

@@ -1,18 +1,25 @@
package org.koitharu.kotatsu.base.ui
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.*
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.base.ui.util.CountedBooleanLiveData
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
abstract class BaseViewModel : ViewModel() {
val onError = SingleLiveEvent<Throwable>()
val isLoading = MutableLiveData(false)
protected val loadingCounter = CountedBooleanLiveData()
protected val errorEvent = SingleLiveEvent<Throwable>()
val onError: LiveData<Throwable>
get() = errorEvent
val isLoading: LiveData<Boolean>
get() = loadingCounter
protected fun launchJob(
context: CoroutineContext = EmptyCoroutineContext,
@@ -25,20 +32,18 @@ abstract class BaseViewModel : ViewModel() {
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = viewModelScope.launch(context + createErrorHandler(), start) {
isLoading.postValue(true)
loadingCounter.increment()
try {
block()
} finally {
isLoading.postValue(false)
loadingCounter.decrement()
}
}
private fun createErrorHandler() = CoroutineExceptionHandler { _, throwable ->
if (BuildConfig.DEBUG) {
throwable.printStackTrace()
}
throwable.printStackTraceDebug()
if (throwable !is CancellationException) {
onError.postCall(throwable)
errorEvent.postCall(throwable)
}
}
}

View File

@@ -0,0 +1,37 @@
package org.koitharu.kotatsu.base.ui
import android.app.Service
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
abstract class CoroutineIntentService : BaseService() {
private val mutex = Mutex()
protected open val dispatcher: CoroutineDispatcher = Dispatchers.Default
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
launchCoroutine(intent, startId)
return Service.START_REDELIVER_INTENT
}
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch {
mutex.withLock {
try {
withContext(dispatcher) {
processIntent(intent)
}
} finally {
stopSelf(startId)
}
}
}
protected abstract suspend fun processIntent(intent: Intent?)
}

View File

@@ -0,0 +1,29 @@
package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.graphics.Color
import android.view.View
import com.google.android.material.bottomsheet.BottomSheetDialog
class AppBottomSheetDialog(context: Context, theme: Int) : BottomSheetDialog(context, theme) {
/**
* https://github.com/material-components/material-components-android/issues/2582
*/
@Suppress("DEPRECATION")
override fun onAttachedToWindow() {
val window = window
val initialSystemUiVisibility = window?.decorView?.systemUiVisibility ?: 0
super.onAttachedToWindow()
if (window != null) {
// If the navigation bar is translucent at all, the BottomSheet should be edge to edge
val drawEdgeToEdge = edgeToEdgeEnabled && Color.alpha(window.navigationBarColor) < 0xFF
if (drawEdgeToEdge) {
// Copied from super.onAttachedToWindow:
val edgeToEdgeFlags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// Fix super-class's window flag bug by respecting the initial system UI visibility:
window.decorView.systemUiVisibility = edgeToEdgeFlags or initialSystemUiVisibility
}
}
}
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
@@ -12,7 +13,6 @@ import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemStorageBinding
import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.utils.ext.inflate
import java.io.File
class StorageSelectDialog private constructor(private val delegate: AlertDialog) :
@@ -66,7 +66,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
val volumes = getAvailableVolumes(storageManager)
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = convertView ?: parent.inflate(R.layout.item_storage)
val view = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.item_storage, parent, false)
val binding = (view.tag as? ItemStorageBinding) ?: ItemStorageBinding.bind(view).also {
view.tag = it
}

View File

@@ -1,89 +0,0 @@
package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.content.DialogInterface
import android.text.InputFilter
import android.view.LayoutInflater
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.databinding.DialogInputBinding
class TextInputDialog private constructor(
private val delegate: AlertDialog,
) : DialogInterface by delegate {
fun show() = delegate.show()
class Builder(context: Context) {
private val binding = DialogInputBinding.inflate(LayoutInflater.from(context))
private val delegate = MaterialAlertDialogBuilder(context)
.setView(binding.root)
fun setTitle(@StringRes titleResId: Int): Builder {
delegate.setTitle(titleResId)
return this
}
fun setTitle(title: CharSequence): Builder {
delegate.setTitle(title)
return this
}
fun setHint(@StringRes hintResId: Int): Builder {
binding.inputEdit.hint = binding.root.context.getString(hintResId)
return this
}
fun setMaxLength(maxLength: Int, strict: Boolean): Builder {
with(binding.inputLayout) {
counterMaxLength = maxLength
isCounterEnabled = maxLength > 0
}
if (strict && maxLength > 0) {
binding.inputEdit.filters += InputFilter.LengthFilter(maxLength)
}
return this
}
fun setInputType(inputType: Int): Builder {
binding.inputEdit.inputType = inputType
return this
}
fun setText(text: String): Builder {
binding.inputEdit.setText(text)
binding.inputEdit.setSelection(text.length)
return this
}
fun setPositiveButton(
@StringRes textId: Int,
listener: (DialogInterface, String) -> Unit
): Builder {
delegate.setPositiveButton(textId) { dialog, _ ->
listener(dialog, binding.inputEdit.text?.toString().orEmpty())
}
return this
}
fun setNegativeButton(
@StringRes textId: Int,
listener: DialogInterface.OnClickListener? = null
): Builder {
delegate.setNegativeButton(textId, listener)
return this
}
fun setOnCancelListener(listener: DialogInterface.OnCancelListener): Builder {
delegate.setOnCancelListener(listener)
return this
}
fun create() =
TextInputDialog(delegate.create())
}
}

View File

@@ -0,0 +1,20 @@
package org.koitharu.kotatsu.base.ui.list
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
class AdapterDelegateClickListenerAdapter<I>(
private val adapterDelegate: AdapterDelegateViewBindingViewHolder<I, *>,
private val clickListener: OnListItemClickListener<I>,
) : OnClickListener, OnLongClickListener {
override fun onClick(v: View) {
clickListener.onItemClick(adapterDelegate.item, v)
}
override fun onLongClick(v: View): Boolean {
return clickListener.onItemLongClick(adapterDelegate.item, v)
}
}

View File

@@ -0,0 +1,162 @@
package org.koitharu.kotatsu.base.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.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.base.ui.list.decor.AbstractSelectionItemDecoration
import kotlin.coroutines.EmptyCoroutineContext
private const val KEY_SELECTION = "selection"
private const val PROVIDER_NAME = "selection_decoration"
class ListSelectionController(
private val activity: Activity,
private val decoration: AbstractSelectionItemDecoration,
private val registryOwner: SavedStateRegistryOwner,
private val callback: Callback,
) : ActionMode.Callback, SavedStateRegistry.SavedStateProvider {
private var actionMode: ActionMode? = null
private val stateEventObserver = StateEventObserver()
val count: Int
get() = decoration.checkedItemsCount
fun snapshot(): Set<Long> {
return peekCheckedIds().toSet()
}
fun peekCheckedIds(): Set<Long> {
return decoration.checkedItemsIds
}
fun clear() {
decoration.clearSelection()
notifySelectionChanged()
}
fun addAll(ids: Collection<Long>) {
if (ids.isEmpty()) {
return
}
decoration.checkAll(ids)
notifySelectionChanged()
}
fun attachToRecyclerView(recyclerView: RecyclerView) {
recyclerView.addItemDecoration(decoration)
registryOwner.lifecycle.addObserver(stateEventObserver)
}
override fun saveState(): Bundle {
val bundle = Bundle(1)
bundle.putLongArray(KEY_SELECTION, peekCheckedIds().toLongArray())
return bundle
}
fun onItemClick(id: Long): Boolean {
if (decoration.checkedItemsCount != 0) {
decoration.toggleItemChecked(id)
if (decoration.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
notifySelectionChanged()
return true
}
return false
}
fun onItemLongClick(id: Long): Boolean {
startActionMode()
return actionMode?.also {
decoration.setItemIsChecked(id, true)
notifySelectionChanged()
} != null
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onCreateActionMode(mode, menu)
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
return callback.onPrepareActionMode(mode, menu)
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
return callback.onActionItemClicked(mode, item)
}
override fun onDestroyActionMode(mode: ActionMode) {
callback.onDestroyActionMode(mode)
clear()
actionMode = null
}
private fun startActionMode() {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
}
private fun notifySelectionChanged() {
val count = decoration.checkedItemsCount
callback.onSelectionChanged(count)
if (count == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
}
}
private fun restoreState(ids: Collection<Long>) {
if (ids.isEmpty() || decoration.checkedItemsCount != 0) {
return
}
decoration.checkAll(ids)
startActionMode()
notifySelectionChanged()
}
interface Callback : ActionMode.Callback {
fun onSelectionChanged(count: Int)
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean
override fun onDestroyActionMode(mode: ActionMode) = Unit
}
private inner class StateEventObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_CREATE) {
val registry = registryOwner.savedStateRegistry
registry.registerSavedStateProvider(PROVIDER_NAME, this@ListSelectionController)
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.getLongArray(KEY_SELECTION)?.toList().orEmpty())
}
}
}
}
}
}
}

View File

@@ -12,7 +12,7 @@ abstract class AbstractSelectionItemDecoration : RecyclerView.ItemDecoration() {
private val bounds = Rect()
private val boundsF = RectF()
private val selection = HashSet<Long>()
protected val selection = HashSet<Long>()
protected var hasBackground: Boolean = true
protected var hasForeground: Boolean = false

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.base.ui.list.decor
import android.graphics.Rect
import android.util.SparseIntArray
import android.view.View
import androidx.core.util.getOrDefault
import androidx.core.util.set
import androidx.recyclerview.widget.RecyclerView
class TypedSpacingItemDecoration(
vararg spacingMapping: Pair<Int, Int>,
private val fallbackSpacing: Int = 0,
) : RecyclerView.ItemDecoration() {
private val mapping = SparseIntArray(spacingMapping.size)
init {
spacingMapping.forEach { (k, v) -> mapping[k] = v }
}
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val itemType = parent.getChildViewHolder(view)?.itemViewType
val spacing = if (itemType == null) {
fallbackSpacing
} else {
mapping.getOrDefault(itemType, fallbackSpacing)
}
outRect.set(spacing, spacing, spacing, spacing)
}
}

View File

@@ -0,0 +1,34 @@
package org.koitharu.kotatsu.base.ui.util
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import java.util.*
class ActivityRecreationHandle : ActivityLifecycleCallbacks {
private val activities = WeakHashMap<Activity, Unit>()
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
activities[activity] = Unit
}
override fun onActivityStarted(activity: Activity) = Unit
override fun onActivityResumed(activity: Activity) = Unit
override fun onActivityPaused(activity: Activity) = Unit
override fun onActivityStopped(activity: Activity) = Unit
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
override fun onActivityDestroyed(activity: Activity) {
activities.remove(activity)
}
fun recreateAll() {
val snapshot = activities.keys.toList()
snapshot.forEach { it.recreate() }
}
}

View File

@@ -0,0 +1,31 @@
package org.koitharu.kotatsu.base.ui.util
import androidx.annotation.AnyThread
import androidx.lifecycle.LiveData
import java.util.concurrent.atomic.AtomicInteger
class CountedBooleanLiveData : LiveData<Boolean>(false) {
private val counter = AtomicInteger(0)
@AnyThread
fun increment() {
if (counter.getAndIncrement() == 0) {
postValue(true)
}
}
@AnyThread
fun decrement() {
if (counter.decrementAndGet() == 0) {
postValue(false)
}
}
@AnyThread
fun reset() {
if (counter.getAndSet(0) != 0) {
postValue(false)
}
}
}

View File

@@ -16,10 +16,7 @@ class WindowInsetsDelegate(
private var lastInsets: Insets? = null
override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat?): WindowInsetsCompat? {
if (insets == null) {
return null
}
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val handledInsets = interceptingWindowInsetsListener?.onApplyWindowInsets(v, insets) ?: insets
val newInsets = if (handleImeInsets) {
Insets.max(
@@ -49,7 +46,7 @@ class WindowInsetsDelegate(
) {
view.removeOnLayoutChangeListener(this)
if (lastInsets == null) { // Listener may not be called
onApplyWindowInsets(view, ViewCompat.getRootWindowInsets(view))
onApplyWindowInsets(view, ViewCompat.getRootWindowInsets(view) ?: return)
}
}

View File

@@ -5,6 +5,7 @@ import android.os.Parcel
import android.os.Parcelable
import android.os.Parcelable.Creator
import android.util.AttributeSet
import android.view.View
import android.widget.Checkable
import androidx.annotation.AttrRes
import androidx.appcompat.widget.AppCompatImageView
@@ -61,6 +62,12 @@ class CheckableImageView @JvmOverloads constructor(
}
}
class ToggleOnClickListener : OnClickListener {
override fun onClick(view: View) {
(view as? Checkable)?.toggle()
}
}
fun interface OnCheckedChangeListener {
fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)

View File

@@ -17,19 +17,28 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.postDelayed
import org.koitharu.kotatsu.R
import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.databinding.FadingSnackbarLayoutBinding
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
import com.google.android.material.R as materialR
private const val ENTER_DURATION = 300L
private const val EXIT_DURATION = 200L
private const val SHORT_DURATION = 1_500L
private const val LONG_DURATION = 2_750L
private const val SHORT_DURATION_MS = 1_500L
private const val LONG_DURATION_MS = 2_750L
/**
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
*
@@ -40,16 +49,13 @@ private const val LONG_DURATION = 2_750L
class FadingSnackbar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {
private val message: TextView
private val action: Button
private val binding = FadingSnackbarLayoutBinding.inflate(LayoutInflater.from(context), this)
init {
val view = LayoutInflater.from(context).inflate(R.layout.fading_snackbar_layout, this, true)
message = view.findViewById(R.id.snackbar_text)
action = view.findViewById(R.id.snackbar_action)
binding.snackbarLayout.background = createThemedBackground()
}
fun dismiss() {
@@ -62,33 +68,66 @@ class FadingSnackbar @JvmOverloads constructor(
}
fun show(
messageText: CharSequence? = null,
@StringRes actionId: Int? = null,
longDuration: Boolean = true,
actionClick: () -> Unit = { dismiss() },
dismissListener: () -> Unit = { }
messageText: CharSequence?,
@StringRes actionId: Int = 0,
duration: Int = Snackbar.LENGTH_SHORT,
onActionClick: (FadingSnackbar.() -> Unit)? = null,
onDismiss: (() -> Unit)? = null,
) {
message.text = messageText
if (actionId != null) {
action.run {
binding.snackbarText.text = messageText
if (actionId != 0) {
with(binding.snackbarAction) {
visibility = VISIBLE
text = context.getString(actionId)
setOnClickListener {
actionClick()
onActionClick?.invoke(this@FadingSnackbar) ?: dismiss()
}
}
} else {
action.visibility = GONE
binding.snackbarAction.visibility = GONE
}
alpha = 0f
visibility = VISIBLE
animate()
.alpha(1f)
.duration = ENTER_DURATION
val showDuration = ENTER_DURATION + if (longDuration) LONG_DURATION else SHORT_DURATION
postDelayed(showDuration) {
if (duration == Snackbar.LENGTH_INDEFINITE) {
return
}
val durationMs = ENTER_DURATION + if (duration == Snackbar.LENGTH_LONG) LONG_DURATION_MS else SHORT_DURATION_MS
postDelayed(durationMs) {
dismiss()
dismissListener()
onDismiss?.invoke()
}
}
private fun createThemedBackground(): Drawable {
val backgroundColor = MaterialColors.layer(this, materialR.attr.colorSurface, materialR.attr.colorOnSurface, 1f)
val shapeAppearanceModel = ShapeAppearanceModel.builder(
context,
materialR.style.ShapeAppearance_Material3_Corner_ExtraSmall,
0
).build()
val background = createMaterialShapeDrawableBackground(
backgroundColor,
shapeAppearanceModel,
)
val backgroundTint = context.getThemeColorStateList(materialR.attr.colorSurfaceInverse)
return if (backgroundTint != null) {
val wrappedDrawable = DrawableCompat.wrap(background)
DrawableCompat.setTintList(wrappedDrawable, backgroundTint)
wrappedDrawable
} else {
DrawableCompat.wrap(background)
}
}
private fun createMaterialShapeDrawableBackground(
@ColorInt backgroundColor: Int,
shapeAppearanceModel: ShapeAppearanceModel,
): MaterialShapeDrawable {
val background = MaterialShapeDrawable(shapeAppearanceModel)
background.fillColor = ColorStateList.valueOf(backgroundColor)
return background
}
}

View File

@@ -13,12 +13,12 @@ import android.graphics.drawable.shapes.RectShape
import android.util.AttributeSet
import androidx.annotation.AttrRes
import androidx.appcompat.widget.AppCompatCheckedTextView
import androidx.core.content.res.use
import androidx.core.content.withStyledAttributes
import com.google.android.material.ripple.RippleUtils
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
@SuppressLint("RestrictedApi")
class ListItemTextView @JvmOverloads constructor(
@@ -36,8 +36,7 @@ class ListItemTextView @JvmOverloads constructor(
init {
context.withStyledAttributes(attrs, R.styleable.ListItemTextView, defStyleAttr) {
val itemRippleColor = getColorStateList(R.styleable.ListItemTextView_rippleColor)
?: getRippleColorFallback(context)
val itemRippleColor = getRippleColor(context)
val shape = createShapeDrawable(this)
background = RippleDrawable(
RippleUtils.sanitizeRippleDrawableColor(itemRippleColor),
@@ -108,7 +107,7 @@ class ListItemTextView @JvmOverloads constructor(
ta.getResourceId(R.styleable.ListItemTextView_shapeAppearanceOverlay, 0),
).build()
val shapeDrawable = MaterialShapeDrawable(shapeAppearance)
shapeDrawable.fillColor = ta.getColorStateList(R.styleable.ListItemTextView_backgroundTint)
shapeDrawable.fillColor = ta.getColorStateList(R.styleable.ListItemTextView_backgroundFillColor)
return InsetDrawable(
shapeDrawable,
ta.getDimensionPixelOffset(R.styleable.ListItemTextView_android_insetLeft, 0),
@@ -118,9 +117,8 @@ class ListItemTextView @JvmOverloads constructor(
)
}
private fun getRippleColorFallback(context: Context): ColorStateList {
return context.obtainStyledAttributes(intArrayOf(android.R.attr.colorControlHighlight)).use {
it.getColorStateList(0)
} ?: ColorStateList.valueOf(Color.TRANSPARENT)
private fun getRippleColor(context: Context): ColorStateList {
return context.getThemeColorStateList(android.R.attr.colorControlHighlight)
?: ColorStateList.valueOf(Color.TRANSPARENT)
}
}

View File

@@ -0,0 +1,29 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.text.Selection
import android.text.Spannable
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.annotation.AttrRes
import com.google.android.material.textview.MaterialTextView
class SelectableTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = android.R.attr.textViewStyle,
) : MaterialTextView(context, attrs, defStyleAttr) {
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
fixSelectionRange()
return super.dispatchTouchEvent(event)
}
// https://stackoverflow.com/questions/22810147/error-when-selecting-text-from-textview-java-lang-indexoutofboundsexception-se
private fun fixSelectionRange() {
if (selectionStart < 0 || selectionEnd < 0) {
val spannableText = text as? Spannable ?: return
Selection.setSelection(spannableText, text.length)
}
}
}

View File

@@ -0,0 +1,72 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.view.WindowInsets
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.AttrRes
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.WindowInsetsCompat
class WindowInsetHolder @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
private var desiredHeight = 0
private var desiredWidth = 0
@SuppressLint("RtlHardcoded")
override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
val barsInsets = WindowInsetsCompat.toWindowInsetsCompat(insets, this)
.getInsets(WindowInsetsCompat.Type.systemBars())
val gravity = getLayoutGravity()
val newWidth = when (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
Gravity.LEFT -> barsInsets.left
Gravity.RIGHT -> barsInsets.right
else -> 0
}
val newHeight = when (gravity and Gravity.VERTICAL_GRAVITY_MASK) {
Gravity.TOP -> barsInsets.top
Gravity.BOTTOM -> barsInsets.bottom
else -> 0
}
if (newWidth != desiredWidth || newHeight != desiredHeight) {
desiredWidth = newWidth
desiredHeight = newHeight
requestLayout()
}
return super.dispatchApplyWindowInsets(insets)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
super.onMeasure(
if (desiredWidth == 0 || widthMode == MeasureSpec.EXACTLY) {
widthMeasureSpec
} else {
MeasureSpec.makeMeasureSpec(desiredWidth, widthMode)
},
if (desiredHeight == 0 || heightMode == MeasureSpec.EXACTLY) {
heightMeasureSpec
} else {
MeasureSpec.makeMeasureSpec(desiredHeight, heightMode)
},
)
}
private fun getLayoutGravity(): Int {
return when (val lp = layoutParams) {
is FrameLayout.LayoutParams -> lp.gravity
is LinearLayout.LayoutParams -> lp.gravity
is CoordinatorLayout.LayoutParams -> lp.gravity
else -> Gravity.NO_GRAVITY
}
}
}

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.bookmarks
import org.koin.dsl.module
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
val bookmarksModule
get() = module {
factory { BookmarksRepository(get()) }
}

View File

@@ -0,0 +1,29 @@
package org.koitharu.kotatsu.bookmarks.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import org.koitharu.kotatsu.core.db.entity.MangaEntity
@Entity(
tableName = "bookmarks",
primaryKeys = ["manga_id", "page_id"],
foreignKeys = [
ForeignKey(
entity = MangaEntity::class,
parentColumns = ["manga_id"],
childColumns = ["manga_id"],
onDelete = ForeignKey.CASCADE
),
]
)
data class BookmarkEntity(
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
@ColumnInfo(name = "page_id", index = true) val pageId: Long,
@ColumnInfo(name = "chapter_id") val chapterId: Long,
@ColumnInfo(name = "page") val page: Int,
@ColumnInfo(name = "scroll") val scroll: Int,
@ColumnInfo(name = "image") val imageUrl: String,
@ColumnInfo(name = "created_at") val createdAt: Long,
@ColumnInfo(name = "percent") val percent: Float,
)

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.bookmarks.data
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
class BookmarkWithManga(
@Embedded val bookmark: BookmarkEntity,
@Relation(
parentColumn = "manga_id",
entityColumn = "manga_id"
)
val manga: MangaEntity,
@Relation(
parentColumn = "manga_id",
entityColumn = "tag_id",
associateBy = Junction(MangaTagsEntity::class)
)
val tags: List<TagEntity>,
)

View File

@@ -0,0 +1,26 @@
package org.koitharu.kotatsu.bookmarks.data
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
abstract class BookmarksDao {
@Query("SELECT * FROM bookmarks WHERE manga_id = :mangaId AND chapter_id = :chapterId AND page = :page")
abstract fun observe(mangaId: Long, chapterId: Long, page: Int): Flow<BookmarkEntity?>
@Query("SELECT * FROM bookmarks WHERE manga_id = :mangaId ORDER BY created_at DESC")
abstract fun observe(mangaId: Long): Flow<List<BookmarkEntity>>
@Insert
abstract suspend fun insert(entity: BookmarkEntity)
@Delete
abstract suspend fun delete(entity: BookmarkEntity)
@Query("DELETE FROM bookmarks WHERE manga_id = :mangaId AND page_id = :pageId")
abstract suspend fun delete(mangaId: Long, pageId: Long)
}

View File

@@ -0,0 +1,33 @@
package org.koitharu.kotatsu.bookmarks.data
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.*
fun BookmarkWithManga.toBookmark() = bookmark.toBookmark(
manga.toManga(tags.toMangaTags())
)
fun BookmarkEntity.toBookmark(manga: Manga) = Bookmark(
manga = manga,
pageId = pageId,
chapterId = chapterId,
page = page,
scroll = scroll,
imageUrl = imageUrl,
createdAt = Date(createdAt),
percent = percent,
)
fun Bookmark.toEntity() = BookmarkEntity(
mangaId = manga.id,
pageId = pageId,
chapterId = chapterId,
page = page,
scroll = scroll,
imageUrl = imageUrl,
createdAt = createdAt.time,
percent = percent,
)

View File

@@ -0,0 +1,46 @@
package org.koitharu.kotatsu.bookmarks.domain
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.*
class Bookmark(
val manga: Manga,
val pageId: Long,
val chapterId: Long,
val page: Int,
val scroll: Int,
val imageUrl: String,
val createdAt: Date,
val percent: Float,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Bookmark
if (manga != other.manga) return false
if (pageId != other.pageId) return false
if (chapterId != other.chapterId) return false
if (page != other.page) return false
if (scroll != other.scroll) return false
if (imageUrl != other.imageUrl) return false
if (createdAt != other.createdAt) return false
if (percent != other.percent) return false
return true
}
override fun hashCode(): Int {
var result = manga.hashCode()
result = 31 * result + pageId.hashCode()
result = 31 * result + chapterId.hashCode()
result = 31 * result + page
result = 31 * result + scroll
result = 31 * result + imageUrl.hashCode()
result = 31 * result + createdAt.hashCode()
result = 31 * result + percent.hashCode()
return result
}
}

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.bookmarks.domain
import androidx.room.withTransaction
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.bookmarks.data.toBookmark
import org.koitharu.kotatsu.bookmarks.data.toEntity
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.mapItems
class BookmarksRepository(
private val db: MangaDatabase,
) {
fun observeBookmark(manga: Manga, chapterId: Long, page: Int): Flow<Bookmark?> {
return db.bookmarksDao.observe(manga.id, chapterId, page).map { it?.toBookmark(manga) }
}
fun observeBookmarks(manga: Manga): Flow<List<Bookmark>> {
return db.bookmarksDao.observe(manga.id).mapItems { it.toBookmark(manga) }
}
suspend fun addBookmark(bookmark: Bookmark) {
db.withTransaction {
val tags = bookmark.manga.tags.toEntities()
db.tagsDao.upsert(tags)
db.mangaDao.upsert(bookmark.manga.toEntity(), tags)
db.bookmarksDao.insert(bookmark.toEntity())
}
}
suspend fun removeBookmark(mangaId: Long, pageId: Long) {
db.bookmarksDao.delete(mangaId, pageId)
}
}

View File

@@ -0,0 +1,44 @@
package org.koitharu.kotatsu.bookmarks.ui
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.referer
fun bookmarkListAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>,
) = adapterDelegateViewBinding<Bookmark, Bookmark, ItemBookmarkBinding>(
{ inflater, parent -> ItemBookmarkBinding.inflate(inflater, parent, false) }
) {
val listener = AdapterDelegateClickListenerAdapter(this, clickListener)
binding.root.setOnClickListener(listener)
binding.root.setOnLongClickListener(listener)
bind {
binding.imageViewThumb.newImageRequest(item.imageUrl)?.run {
referer(item.manga.publicUrl)
placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_placeholder)
allowRgb565(true)
lifecycle(lifecycleOwner)
enqueueWith(coil)
}
}
onViewRecycled {
binding.imageViewThumb.disposeImageRequest()
}
}

View File

@@ -0,0 +1,30 @@
package org.koitharu.kotatsu.bookmarks.ui
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
class BookmarksAdapter(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>,
) : AsyncListDifferDelegationAdapter<Bookmark>(
DiffCallback(),
bookmarkListAD(coil, lifecycleOwner, clickListener)
) {
private class DiffCallback : DiffUtil.ItemCallback<Bookmark>() {
override fun areItemsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean {
return oldItem.manga.id == newItem.manga.id && oldItem.chapterId == newItem.chapterId
}
override fun areContentsTheSame(oldItem: Bookmark, newItem: Bookmark): Boolean {
return oldItem.imageUrl == newItem.imageUrl
}
}
}

View File

@@ -11,10 +11,11 @@ import android.view.MenuItem
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import com.google.android.material.R as materialR
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import com.google.android.material.R as materialR
@SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
@@ -28,6 +29,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
}
with(binding.webView.settings) {
javaScriptEnabled = true
userAgentString = UserAgentInterceptor.userAgent
}
binding.webView.webViewClient = BrowserClient(this)
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)

View File

@@ -2,14 +2,9 @@ package org.koitharu.kotatsu.browser
import android.graphics.Bitmap
import android.webkit.WebView
import okhttp3.OkHttpClient
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.network.WebViewClientCompat
import android.webkit.WebViewClient
class BrowserClient(private val callback: BrowserCallback) : WebViewClientCompat(), KoinComponent {
private val okHttp by inject<OkHttpClient>(mode = LazyThreadSafetyMode.SYNCHRONIZED)
class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
override fun onPageFinished(webView: WebView, url: String) {
super.onPageFinished(webView, url)

View File

@@ -2,13 +2,14 @@ package org.koitharu.kotatsu.browser
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.ProgressBar
import androidx.core.view.isVisible
import com.google.android.material.progressindicator.BaseProgressIndicator
import org.koitharu.kotatsu.utils.ext.setProgressCompat
private const val PROGRESS_MAX = 100
class ProgressChromeClient(
private val progressIndicator: BaseProgressIndicator<*>,
private val progressIndicator: ProgressBar,
) : WebChromeClient() {
init {
@@ -24,7 +25,7 @@ class ProgressChromeClient(
progressIndicator.isIndeterminate = false
progressIndicator.setProgressCompat(newProgress.coerceAtMost(PROGRESS_MAX), true)
} else {
progressIndicator.setIndeterminate(true)
progressIndicator.isIndeterminate = true
}
}
}

View File

@@ -2,19 +2,19 @@ package org.koitharu.kotatsu.browser.cloudflare
import android.graphics.Bitmap
import android.webkit.WebView
import android.webkit.WebViewClient
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.core.network.AndroidCookieJar
import org.koitharu.kotatsu.core.network.WebViewClientCompat
private const val CF_CLEARANCE = "cf_clearance"
class CloudFlareClient(
private val cookieJar: AndroidCookieJar,
private val callback: CloudFlareCallback,
private val targetUrl: String
) : WebViewClientCompat() {
private val targetUrl: String,
) : WebViewClient() {
private val oldClearance = getCookieValue(CF_CLEARANCE)
private val oldClearance = getClearance()
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
@@ -32,14 +32,14 @@ class CloudFlareClient(
}
private fun checkClearance() {
val clearance = getCookieValue(CF_CLEARANCE)
val clearance = getClearance()
if (clearance != null && clearance != oldClearance) {
callback.onCheckPassed()
}
}
private fun getCookieValue(name: String): String? {
private fun getClearance(): String? {
return cookieJar.loadForRequest(targetUrl.toHttpUrl())
.find { it.name == name }?.value
.find { it.name == CF_CLEARANCE }?.value
}
}

View File

@@ -1,51 +0,0 @@
package org.koitharu.kotatsu.core.backup
import android.content.Context
import java.io.File
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.MutableZipFile
import org.koitharu.kotatsu.utils.ext.format
class BackupArchive(file: File) : MutableZipFile(file) {
init {
if (!dir.exists()) {
dir.mkdirs()
}
}
suspend fun put(entry: BackupEntry) {
put(entry.name, entry.data.toString(2))
}
suspend fun getEntry(name: String): BackupEntry {
val json = withContext(Dispatchers.Default) {
JSONArray(getContent(name))
}
return BackupEntry(name, json)
}
companion object {
private const val DIR_BACKUPS = "backups"
suspend fun createNew(context: Context): BackupArchive = runInterruptible(Dispatchers.IO) {
val dir = context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
}
dir.mkdirs()
val filename = buildString {
append(context.getString(R.string.app_name).lowercase(Locale.ROOT))
append('_')
append(Date().format("ddMMyyyy"))
append(".bak")
}
BackupArchive(File(dir, filename))
}
}
}

View File

@@ -1,14 +1,12 @@
package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
import org.koitharu.kotatsu.parsers.util.json.mapJSON
private const val PAGE_SIZE = 10
@@ -24,11 +22,11 @@ class BackupRepository(private val db: MangaDatabase) {
}
offset += history.size
for (item in history) {
val manga = item.manga.toJson()
val manga = JsonSerializer(item.manga).toJson()
val tags = JSONArray()
item.tags.forEach { tags.put(it.toJson()) }
item.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
manga.put("tags", tags)
val json = item.history.toJson()
val json = JsonSerializer(item.history).toJson()
json.put("manga", manga)
entry.data.put(json)
}
@@ -40,7 +38,7 @@ class BackupRepository(private val db: MangaDatabase) {
val entry = BackupEntry(BackupEntry.CATEGORIES, JSONArray())
val categories = db.favouriteCategoriesDao.findAll()
for (item in categories) {
entry.data.put(item.toJson())
entry.data.put(JsonSerializer(item).toJson())
}
return entry
}
@@ -55,11 +53,11 @@ class BackupRepository(private val db: MangaDatabase) {
}
offset += favourites.size
for (item in favourites) {
val manga = item.manga.toJson()
val manga = JsonSerializer(item.manga).toJson()
val tags = JSONArray()
item.tags.forEach { tags.put(it.toJson()) }
item.tags.forEach { tags.put(JsonSerializer(it).toJson()) }
manga.put("tags", tags)
val json = item.favourite.toJson()
val json = JsonSerializer(item.favourite).toJson()
json.put("manga", manga)
entry.data.put(json)
}
@@ -77,58 +75,54 @@ class BackupRepository(private val db: MangaDatabase) {
return entry
}
private fun MangaEntity.toJson(): JSONObject {
val jo = JSONObject()
jo.put("id", id)
jo.put("title", title)
jo.put("alt_title", altTitle)
jo.put("url", url)
jo.put("public_url", publicUrl)
jo.put("rating", rating)
jo.put("nsfw", isNsfw)
jo.put("cover_url", coverUrl)
jo.put("large_cover_url", largeCoverUrl)
jo.put("state", state)
jo.put("author", author)
jo.put("source", source)
return jo
suspend fun restoreHistory(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
JsonDeserializer(it).toTagEntity()
}
val history = JsonDeserializer(item).toHistoryEntity()
result += runCatching {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.historyDao.upsert(history)
}
}
}
return result
}
private fun TagEntity.toJson(): JSONObject {
val jo = JSONObject()
jo.put("id", id)
jo.put("title", title)
jo.put("key", key)
jo.put("source", source)
return jo
suspend fun restoreCategories(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val category = JsonDeserializer(item).toFavouriteCategoryEntity()
result += runCatching {
db.favouriteCategoriesDao.upsert(category)
}
}
return result
}
private fun HistoryEntity.toJson(): JSONObject {
val jo = JSONObject()
jo.put("manga_id", mangaId)
jo.put("created_at", createdAt)
jo.put("updated_at", updatedAt)
jo.put("chapter_id", chapterId)
jo.put("page", page)
jo.put("scroll", scroll)
return jo
}
private fun FavouriteCategoryEntity.toJson(): JSONObject {
val jo = JSONObject()
jo.put("category_id", categoryId)
jo.put("created_at", createdAt)
jo.put("sort_key", sortKey)
jo.put("title", title)
jo.put("order", order)
return jo
}
private fun FavouriteEntity.toJson(): JSONObject {
val jo = JSONObject()
jo.put("manga_id", mangaId)
jo.put("category_id", categoryId)
jo.put("created_at", createdAt)
return jo
suspend fun restoreFavourites(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val mangaJson = item.getJSONObject("manga")
val manga = JsonDeserializer(mangaJson).toMangaEntity()
val tags = mangaJson.getJSONArray("tags").mapJSON {
JsonDeserializer(it).toTagEntity()
}
val favourite = JsonDeserializer(item).toFavouriteEntity()
result += runCatching {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.favouritesDao.upsert(favourite)
}
}
}
return result
}
}

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.core.backup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.json.JSONArray
import java.io.File
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)
val json = zipFile.getInputStream(entry).use {
JSONArray(it.bufferedReader().readText())
}
BackupEntry(name, json)
}
override fun close() {
zipFile.close()
}
}

View File

@@ -0,0 +1,45 @@
package org.koitharu.kotatsu.core.backup
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okio.Closeable
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.zip.ZipOutput
import org.koitharu.kotatsu.utils.ext.format
import java.io.File
import java.util.*
import java.util.zip.Deflater
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))
}
suspend fun finish() = runInterruptible(Dispatchers.IO) {
output.finish()
}
override fun close() {
output.close()
}
}
private const val DIR_BACKUPS = "backups"
suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptible(Dispatchers.IO) {
val dir = context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
}
dir.mkdirs()
val filename = buildString {
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
append('_')
append(Date().format("ddMMyyyy"))
append(".bk.zip")
}
BackupZipOutput(File(dir, filename))
}

View File

@@ -0,0 +1,62 @@
package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
class JsonDeserializer(private val json: JSONObject) {
fun toFavouriteEntity() = FavouriteEntity(
mangaId = json.getLong("manga_id"),
categoryId = json.getLong("category_id"),
createdAt = json.getLong("created_at"),
)
fun toMangaEntity() = MangaEntity(
id = json.getLong("id"),
title = json.getString("title"),
altTitle = json.getStringOrNull("alt_title"),
url = json.getString("url"),
publicUrl = json.getStringOrNull("public_url").orEmpty(),
rating = json.getDouble("rating").toFloat(),
isNsfw = json.getBooleanOrDefault("nsfw", false),
coverUrl = json.getString("cover_url"),
largeCoverUrl = json.getStringOrNull("large_cover_url"),
state = json.getStringOrNull("state"),
author = json.getStringOrNull("author"),
source = json.getString("source")
)
fun toTagEntity() = TagEntity(
id = json.getLong("id"),
title = json.getString("title"),
key = json.getString("key"),
source = json.getString("source")
)
fun toHistoryEntity() = HistoryEntity(
mangaId = json.getLong("manga_id"),
createdAt = json.getLong("created_at"),
updatedAt = json.getLong("updated_at"),
chapterId = json.getLong("chapter_id"),
page = json.getInt("page"),
scroll = json.getDouble("scroll").toFloat(),
percent = json.getFloatOrDefault("percent", -1f),
)
fun toFavouriteCategoryEntity() = FavouriteCategoryEntity(
categoryId = json.getInt("category_id"),
createdAt = json.getLong("created_at"),
sortKey = json.getInt("sort_key"),
title = json.getString("title"),
order = json.getStringOrNull("order") ?: SortOrder.NEWEST.name,
track = json.getBooleanOrDefault("track", true),
)
}

View File

@@ -0,0 +1,70 @@
package org.koitharu.kotatsu.core.backup
import org.json.JSONObject
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
class JsonSerializer private constructor(private val json: JSONObject) {
constructor(e: FavouriteEntity) : this(
JSONObject().apply {
put("manga_id", e.mangaId)
put("category_id", e.categoryId)
put("created_at", e.createdAt)
}
)
constructor(e: FavouriteCategoryEntity) : this(
JSONObject().apply {
put("category_id", e.categoryId)
put("created_at", e.createdAt)
put("sort_key", e.sortKey)
put("title", e.title)
put("order", e.order)
put("track", e.track)
}
)
constructor(e: HistoryEntity) : this(
JSONObject().apply {
put("manga_id", e.mangaId)
put("created_at", e.createdAt)
put("updated_at", e.updatedAt)
put("chapter_id", e.chapterId)
put("page", e.page)
put("scroll", e.scroll)
put("percent", e.percent)
}
)
constructor(e: TagEntity) : this(
JSONObject().apply {
put("id", e.id)
put("title", e.title)
put("key", e.key)
put("source", e.source)
}
)
constructor(e: MangaEntity) : this(
JSONObject().apply {
put("id", e.id)
put("title", e.title)
put("alt_title", e.altTitle)
put("url", e.url)
put("public_url", e.publicUrl)
put("rating", e.rating)
put("nsfw", e.isNsfw)
put("cover_url", e.coverUrl)
put("large_cover_url", e.largeCoverUrl)
put("state", e.state)
put("author", e.author)
put("source", e.source)
}
)
fun toJson(): JSONObject = json
}

View File

@@ -1,114 +0,0 @@
package org.koitharu.kotatsu.core.backup
import androidx.room.withTransaction
import org.json.JSONObject
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.json.JSONIterator
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON
class RestoreRepository(private val db: MangaDatabase) {
suspend fun upsertHistory(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val mangaJson = item.getJSONObject("manga")
val manga = parseManga(mangaJson)
val tags = mangaJson.getJSONArray("tags").mapJSON {
parseTag(it)
}
val history = parseHistory(item)
result += runCatching {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.historyDao.upsert(history)
}
}
}
return result
}
suspend fun upsertCategories(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val category = parseCategory(item)
result += runCatching {
db.favouriteCategoriesDao.upsert(category)
}
}
return result
}
suspend fun upsertFavourites(entry: BackupEntry): CompositeResult {
val result = CompositeResult()
for (item in entry.data.JSONIterator()) {
val mangaJson = item.getJSONObject("manga")
val manga = parseManga(mangaJson)
val tags = mangaJson.getJSONArray("tags").mapJSON {
parseTag(it)
}
val favourite = parseFavourite(item)
result += runCatching {
db.withTransaction {
db.tagsDao.upsert(tags)
db.mangaDao.upsert(manga, tags)
db.favouritesDao.upsert(favourite)
}
}
}
return result
}
private fun parseManga(json: JSONObject) = MangaEntity(
id = json.getLong("id"),
title = json.getString("title"),
altTitle = json.getStringOrNull("alt_title"),
url = json.getString("url"),
publicUrl = json.getStringOrNull("public_url").orEmpty(),
rating = json.getDouble("rating").toFloat(),
isNsfw = json.getBooleanOrDefault("nsfw", false),
coverUrl = json.getString("cover_url"),
largeCoverUrl = json.getStringOrNull("large_cover_url"),
state = json.getStringOrNull("state"),
author = json.getStringOrNull("author"),
source = json.getString("source")
)
private fun parseTag(json: JSONObject) = TagEntity(
id = json.getLong("id"),
title = json.getString("title"),
key = json.getString("key"),
source = json.getString("source")
)
private fun parseHistory(json: JSONObject) = HistoryEntity(
mangaId = json.getLong("manga_id"),
createdAt = json.getLong("created_at"),
updatedAt = json.getLong("updated_at"),
chapterId = json.getLong("chapter_id"),
page = json.getInt("page"),
scroll = json.getDouble("scroll").toFloat()
)
private fun parseCategory(json: JSONObject) = FavouriteCategoryEntity(
categoryId = json.getInt("category_id"),
createdAt = json.getLong("created_at"),
sortKey = json.getInt("sort_key"),
title = json.getString("title"),
order = json.getStringOrNull("order") ?: SortOrder.NEWEST.name,
)
private fun parseFavourite(json: JSONObject) = FavouriteEntity(
mangaId = json.getLong("manga_id"),
categoryId = json.getLong("category_id"),
createdAt = json.getLong("created_at")
)
}

View File

@@ -5,5 +5,5 @@ import org.koin.dsl.module
val databaseModule
get() = module {
single { MangaDatabase.create(androidContext()) }
single { MangaDatabase(androidContext()) }
}

View File

@@ -10,8 +10,8 @@ class DatabasePrePopulateCallback(private val resources: Resources) : RoomDataba
override fun onCreate(db: SupportSQLiteDatabase) {
db.execSQL(
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`) VALUES (?,?,?,?)",
arrayOf(System.currentTimeMillis(), 1, resources.getString(R.string.read_later), SortOrder.NEWEST.name)
"INSERT INTO favourite_categories (created_at, sort_key, title, `order`, track) VALUES (?,?,?,?,?)",
arrayOf(System.currentTimeMillis(), 1, resources.getString(R.string.read_later), SortOrder.NEWEST.name, 1)
)
}
}

View File

@@ -4,8 +4,17 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import org.koitharu.kotatsu.core.db.dao.*
import org.koitharu.kotatsu.core.db.entity.*
import androidx.room.migration.Migration
import org.koitharu.kotatsu.bookmarks.data.BookmarkEntity
import org.koitharu.kotatsu.bookmarks.data.BookmarksDao
import org.koitharu.kotatsu.core.db.dao.MangaDao
import org.koitharu.kotatsu.core.db.dao.PreferencesDao
import org.koitharu.kotatsu.core.db.dao.TagsDao
import org.koitharu.kotatsu.core.db.dao.TrackLogsDao
import org.koitharu.kotatsu.core.db.entity.MangaEntity
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
import org.koitharu.kotatsu.core.db.entity.MangaTagsEntity
import org.koitharu.kotatsu.core.db.entity.TagEntity
import org.koitharu.kotatsu.core.db.migrations.*
import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
@@ -13,16 +22,24 @@ import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouritesDao
import org.koitharu.kotatsu.history.data.HistoryDao
import org.koitharu.kotatsu.history.data.HistoryEntity
import org.koitharu.kotatsu.scrobbling.data.ScrobblingDao
import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity
import org.koitharu.kotatsu.suggestions.data.SuggestionDao
import org.koitharu.kotatsu.suggestions.data.SuggestionEntity
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 = 12
@Database(
entities = [
MangaEntity::class, TagEntity::class, HistoryEntity::class, MangaTagsEntity::class,
FavouriteCategoryEntity::class, FavouriteEntity::class, MangaPrefsEntity::class,
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class
TrackEntity::class, TrackLogEntity::class, SuggestionEntity::class, BookmarkEntity::class,
ScrobblingEntity::class,
],
version = 9
version = DATABASE_VERSION,
)
abstract class MangaDatabase : RoomDatabase() {
@@ -44,23 +61,28 @@ abstract class MangaDatabase : RoomDatabase() {
abstract val suggestionDao: SuggestionDao
companion object {
abstract val bookmarksDao: BookmarksDao
fun create(context: Context): MangaDatabase = Room.databaseBuilder(
context,
MangaDatabase::class.java,
"kotatsu-db"
).addMigrations(
Migration1To2(),
Migration2To3(),
Migration3To4(),
Migration4To5(),
Migration5To6(),
Migration6To7(),
Migration7To8(),
Migration8To9(),
).addCallback(
DatabasePrePopulateCallback(context.resources)
).build()
}
}
abstract val scrobblingDao: ScrobblingDao
}
val databaseMigrations: Array<Migration>
get() = arrayOf(
Migration1To2(),
Migration2To3(),
Migration3To4(),
Migration4To5(),
Migration5To6(),
Migration6To7(),
Migration7To8(),
Migration8To9(),
Migration9To10(),
Migration10To11(),
Migration11To12(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room
.databaseBuilder(context, MangaDatabase::class.java, "kotatsu-db")
.addMigrations(*databaseMigrations)
.addCallback(DatabasePrePopulateCallback(context.resources))
.build()

View File

@@ -0,0 +1,9 @@
package org.koitharu.kotatsu.core.db
const val TABLE_FAVOURITES = "favourites"
const val TABLE_MANGA = "manga"
const val TABLE_TAGS = "tags"
const val TABLE_FAVOURITE_CATEGORIES = "favourite_categories"
const val TABLE_HISTORY = "history"
const val TABLE_MANGA_TAGS = "manga_tags"

View File

@@ -1,8 +1,8 @@
package org.koitharu.kotatsu.core.db.dao
import androidx.room.*
import org.koitharu.kotatsu.core.db.entity.TrackLogEntity
import org.koitharu.kotatsu.core.db.entity.TrackLogWithManga
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TrackLogWithManga
@Dao
interface TrackLogsDao {
@@ -21,7 +21,7 @@ interface TrackLogsDao {
suspend fun removeAll(mangaId: Long)
@Query("DELETE FROM track_logs WHERE manga_id NOT IN (SELECT manga_id FROM tracks)")
suspend fun cleanup()
suspend fun gc()
@Query("SELECT COUNT(*) FROM track_logs")
suspend fun count(): Int

View File

@@ -1,11 +1,9 @@
package org.koitharu.kotatsu.core.db.entity
import java.util.*
import org.koitharu.kotatsu.core.model.TrackingLogItem
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.longHashCode
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.utils.ext.longHashCode
// Entity to model
@@ -35,13 +33,6 @@ fun MangaEntity.toManga(tags: Set<MangaTag>) = Manga(
fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags())
fun TrackLogWithManga.toTrackingLogItem() = TrackingLogItem(
id = trackLog.id,
chapters = trackLog.chapters.split('\n').filterNot { x -> x.isEmpty() },
manga = manga.toManga(tags.toMangaTags()),
createdAt = Date(trackLog.createdAt)
)
// Model to entity
fun Manga.toEntity() = MangaEntity(

View File

@@ -3,9 +3,10 @@ package org.koitharu.kotatsu.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import org.koitharu.kotatsu.core.db.TABLE_MANGA
@Entity(tableName = "manga")
class MangaEntity(
@Entity(tableName = TABLE_MANGA)
data class MangaEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "manga_id") val id: Long,
@ColumnInfo(name = "title") val title: String,
@@ -18,5 +19,5 @@ class MangaEntity(
@ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?,
@ColumnInfo(name = "state") val state: String?,
@ColumnInfo(name = "author") val author: String?,
@ColumnInfo(name = "source") val source: String
@ColumnInfo(name = "source") val source: String,
)

View File

@@ -3,9 +3,11 @@ package org.koitharu.kotatsu.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import org.koitharu.kotatsu.core.db.TABLE_MANGA_TAGS
@Entity(
tableName = "manga_tags", primaryKeys = ["manga_id", "tag_id"],
tableName = TABLE_MANGA_TAGS,
primaryKeys = ["manga_id", "tag_id"],
foreignKeys = [
ForeignKey(
entity = MangaEntity::class,
@@ -23,5 +25,5 @@ import androidx.room.ForeignKey
)
class MangaTagsEntity(
@ColumnInfo(name = "manga_id", index = true) val mangaId: Long,
@ColumnInfo(name = "tag_id", index = true) val tagId: Long
@ColumnInfo(name = "tag_id", index = true) val tagId: Long,
)

View File

@@ -3,12 +3,13 @@ package org.koitharu.kotatsu.core.db.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import org.koitharu.kotatsu.core.db.TABLE_TAGS
@Entity(tableName = "tags")
class TagEntity(
@Entity(tableName = TABLE_TAGS)
data class TagEntity(
@PrimaryKey(autoGenerate = false)
@ColumnInfo(name = "tag_id") val id: Long,
@ColumnInfo(name = "title") val title: String,
@ColumnInfo(name = "key") val key: String,
@ColumnInfo(name = "source") val source: String
@ColumnInfo(name = "source") val source: String,
)

View File

@@ -0,0 +1,26 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration10To11 : Migration(10, 11) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `bookmarks` (
`manga_id` INTEGER NOT NULL,
`page_id` INTEGER NOT NULL,
`chapter_id` INTEGER NOT NULL,
`page` INTEGER NOT NULL,
`scroll` INTEGER NOT NULL,
`image` TEXT NOT NULL,
`created_at` INTEGER NOT NULL,
PRIMARY KEY(`manga_id`, `page_id`),
FOREIGN KEY(`manga_id`) REFERENCES `manga`(`manga_id`) ON UPDATE NO ACTION ON DELETE CASCADE )
""".trimIndent()
)
database.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_manga_id` ON `bookmarks` (`manga_id`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_bookmarks_page_id` ON `bookmarks` (`page_id`)")
}
}

View File

@@ -0,0 +1,27 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration11To12 : Migration(11, 12) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"""
CREATE TABLE IF NOT EXISTS `scrobblings` (
`scrobbler` INTEGER NOT NULL,
`id` INTEGER NOT NULL,
`manga_id` INTEGER NOT NULL,
`target_id` INTEGER NOT NULL,
`status` TEXT,
`chapter` INTEGER NOT NULL,
`comment` TEXT,
`rating` REAL NOT NULL,
PRIMARY KEY(`scrobbler`, `id`, `manga_id`)
)
""".trimIndent()
)
database.execSQL("ALTER TABLE history ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
database.execSQL("ALTER TABLE bookmarks ADD COLUMN `percent` REAL NOT NULL DEFAULT -1")
}
}

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration9To10 : Migration(9, 10) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE favourite_categories ADD COLUMN `track` INTEGER NOT NULL DEFAULT 1")
}
}

View File

@@ -0,0 +1,3 @@
package org.koitharu.kotatsu.core.exceptions
class CaughtException(cause: Throwable, override val message: String?) : RuntimeException(cause)

View File

@@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.exceptions
import androidx.annotation.StringRes
import okio.IOException
import org.koitharu.kotatsu.R
class CloudFlareProtectedException(
val url: String

View File

@@ -0,0 +1,8 @@
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,3 +0,0 @@
package org.koitharu.kotatsu.core.exceptions
class MangaNotFoundException(s: String? = null) : RuntimeException(s)

View File

@@ -8,9 +8,11 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.suspendCancellableCoroutine
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.exception.NotFoundException
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.utils.TaggedActivityResult
@@ -43,6 +45,10 @@ class ExceptionResolver private constructor(
suspend fun resolve(e: Throwable): Boolean = when (e) {
is CloudFlareProtectedException -> resolveCF(e.url)
is AuthRequiredException -> resolveAuthException(e.source)
is NotFoundException -> {
openInBrowser(e.url)
false
}
else -> false
}
@@ -69,6 +75,11 @@ class ExceptionResolver private constructor(
sourceAuthContract.launch(source)
}
private fun openInBrowser(url: String) {
val context = activity ?: fragment?.activity ?: return
context.startActivity(BrowserActivity.newIntent(context, url, null))
}
private fun getFragmentManager() = checkNotNull(fragment?.childFragmentManager ?: activity?.supportFragmentManager)
companion object {
@@ -77,6 +88,7 @@ class ExceptionResolver private constructor(
fun getResolveStringId(e: Throwable) = when (e) {
is CloudFlareProtectedException -> R.string.captcha_solve
is AuthRequiredException -> R.string.sign_in
is NotFoundException -> if (e.url.isNotEmpty()) R.string.open_in_browser else 0
else -> 0
}

View File

@@ -4,7 +4,5 @@ import org.koin.dsl.module
val githubModule
get() = module {
single {
GithubRepository(get())
}
factory { GithubRepository(get()) }
}

View File

@@ -10,7 +10,7 @@ class GithubRepository(private val okHttp: OkHttpClient) {
suspend fun getLatestVersion(): AppVersion {
val request = Request.Builder()
.get()
.url("https://api.github.com/repos/nv95/Kotatsu/releases/latest")
.url("https://api.github.com/repos/KotatsuApp/Kotatsu/releases/latest")
val json = okHttp.newCall(request.build()).await().parseJson()
val asset = json.getJSONArray("assets").getJSONObject(0)
return AppVersion(

View File

@@ -54,27 +54,23 @@ class VersionId(
return result
}
companion object {
private fun variantWeight(variantType: String) =
when (variantType.lowercase(Locale.ROOT)) {
"a", "alpha" -> 1
"b", "beta" -> 2
"rc" -> 4
"" -> 8
else -> 0
}
fun parse(versionName: String): VersionId {
val parts = versionName.substringBeforeLast('-').split('.')
val variant = versionName.substringAfterLast('-', "")
return VersionId(
major = parts.getOrNull(0)?.toIntOrNull() ?: 0,
minor = parts.getOrNull(1)?.toIntOrNull() ?: 0,
build = parts.getOrNull(2)?.toIntOrNull() ?: 0,
variantType = variant.filter(Char::isLetter),
variantNumber = variant.filter(Char::isDigit).toIntOrNull() ?: 0
)
}
private fun variantWeight(variantType: String) = when (variantType.lowercase(Locale.ROOT)) {
"a", "alpha" -> 1
"b", "beta" -> 2
"rc" -> 4
"" -> 8
else -> 0
}
}
fun VersionId(versionName: String): VersionId {
val parts = versionName.substringBeforeLast('-').split('.')
val variant = versionName.substringAfterLast('-', "")
return VersionId(
major = parts.getOrNull(0)?.toIntOrNull() ?: 0,
minor = parts.getOrNull(1)?.toIntOrNull() ?: 0,
build = parts.getOrNull(2)?.toIntOrNull() ?: 0,
variantType = variant.filter(Char::isLetter),
variantNumber = variant.filter(Char::isDigit).toIntOrNull() ?: 0,
)
}

View File

@@ -1,9 +1,9 @@
package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import java.util.*
import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.*
@Parcelize
data class FavouriteCategory(
@@ -12,4 +12,5 @@ data class FavouriteCategory(
val sortKey: Int,
val order: SortOrder,
val createdAt: Date,
val isTrackingEnabled: Boolean,
) : Parcelable

View File

@@ -3,26 +3,4 @@ package org.koitharu.kotatsu.core.model
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.mapToSet
fun Manga.withoutChapters() = if (chapters.isNullOrEmpty()) {
this
} else {
Manga(
id = id,
title = title,
altTitle = altTitle,
url = url,
publicUrl = publicUrl,
rating = rating,
isNsfw = isNsfw,
coverUrl = coverUrl,
tags = tags,
state = state,
author = author,
largeCoverUrl = largeCoverUrl,
description = description,
chapters = null,
source = source,
)
}
fun Collection<Manga>.ids() = mapToSet { it.id }

View File

@@ -11,4 +11,5 @@ data class MangaHistory(
val chapterId: Long,
val page: Int,
val scroll: Int,
val percent: Float,
) : Parcelable

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.model
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.*
fun MangaSource.getLocaleTitle(): String? {
val lc = Locale(locale ?: return null)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
}

View File

@@ -1,14 +0,0 @@
package org.koitharu.kotatsu.core.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.*
data class MangaTracking(
val manga: Manga,
val knownChaptersCount: Int,
val lastChapterId: Long,
val lastNotifiedChapterId: Long,
val lastCheck: Date?
)

View File

@@ -10,13 +10,18 @@ private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe
class ParcelableManga(
val manga: Manga,
private val withChapters: Boolean,
) : Parcelable {
constructor(parcel: Parcel) : this(parcel.readManga())
constructor(parcel: Parcel) : this(parcel.readManga(), true)
override fun writeToParcel(parcel: Parcel, flags: Int) {
val chapters = manga.chapters
if (chapters == null || chapters.size <= MAX_SAFE_CHAPTERS_COUNT) {
if (!withChapters || chapters == null) {
manga.writeToParcel(parcel, flags, withChapters = false)
return
}
if (chapters.size <= MAX_SAFE_CHAPTERS_COUNT) {
// fast path
manga.writeToParcel(parcel, flags, withChapters = true)
return

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